Passing ID vs. Entity as props in React

Marco
Webtips
Published in
4 min readAug 7, 2020
Passing ID vs. Entity as props in React
Königssee, Bavaria, Germany — Dec 2019

Passing props between components is inevitable when writing React applications. But have you ever wonder:

Should the parent component pass the domain entity/object or just the ID of the object into the child component?

And how would this affect the boundaries of smart vs. dumb components?

Recap on Smart vs. Dumb components

There are a lot of great articles covering this pattern and illustrating the advantages. I have also briefly mentioned the concept of smart and dumb components in my previous post, but let’s do a quick recap here.

In the early days, class components have been doing the heavy-lifting works such as data fetching, state mutation, asynchronous tasks with lifecycle methods, etc., while the function components are more simple, render-only components. And thus, the terms “smart components” and “dumb components” are contrived in the React community.

Smart components are usually the parent components that contain business logic and have more knowledge on the application — they know where to fetch data, when to update state and child components, which context/store to subscribe, what actions can be dispatched, etc.

Dumb components, on the other hand, are relatively “dumb” and agnostic to more of the application states and domain knowledge. They just do one thing and do one thing well) — take props and render correctly.

The problem

Imagine we are creating a page showing the user’s name and groups that the user belongs to. We have UserContext storing the user object and GroupContext with all the available groups. The schema of user and group look like:

type User = {
name: string,
groupIds: string[],
}
type Group = {
id: string,
name: string,
}

Note that the user object only has an array with group IDs but not the group objects. It is in the normalized form and we have to use the groupId to lookup the group object somewhere in the page or components we are going to build.

That’s the problem we are facing — when should we do the lookup with groupId and whether we should pass down groupId to the child components of the group object?

Ideally the user object should be in the denormalized form so we can easily iterate all groups via user.groups.map(...), but sometimes there are limitations (e.g. subject to API design or performance concerns) that we can’t denormalize every related object and put into the React context.

First trial — passing ID into child components

A very straightforward approach would look like the following:

  • A UserPage component that retrieves user from the UserContext, render the user.name, iterate user.groupIds and pass each groupId to the child component Group
  • A Group component that receives a group id as props, retrieves all available groups from the GroupContext, lookup the required group by id and render information.

As you can see, the parent UserPage component didn’t do anything else except passing whatever it has to the child components Group. So the Group component has to i) find the group and ii) render it. It could be even more complicated if it needs to fetch from API or perform some data transformation before rendering.

The child component has to do more jobs because the parent component does less. It also makes the lower-level component needs to care more about the data source and the data schema. Kinds of obfuscating the boundaries between smart and dumb components.

So how can we do better?

There was a similar discussion on StackOverflow by Dan Abramov from the React team exploring different approaches for this puzzle. Although the post was back in 2014 and the example used was React Flux, the concept is akin:

All components can read their own data

vs.

All data is read once at the top level and passed down to components

Eventually, Dan proposed two principles:

  1. No component ever receives ID as a prop; all components receive their respective objects.
  2. If child components need an entity, it’s parent’s responsibility to retrieve it and pass as a prop.

In short: The child components should receive its data (entity) as a prop but not ID. It is the dumb component whose only job is to render the entity without caring for the data source.

Second trial — passing entity into child components

Back to our example, how do we follow this pattern and make sure the Group component receives the group entity instead of groupId as props?

If the parent UserPage component only has the groupIds list, we may make use of HOCs as the “middle layer” to retrieve the group entities for the child component. In this case, the HOC is the smart component while keeping the child component dumb.

Simplified with hooks

Lastly, with the thriving hooks and GraphQL, we may ditch the HOC and React context to further simplify the codes.

I found keeping this principle in mind leads to a better component structure — it makes a clear between the responsibility of parent and child components; embraces “domain object/entity”; and facilitates reusability of the child components.

This may sound like a trivial problem at first, but there is a deeper mental model underlying and it could have a huge impact on how we organize our components as the codebase expands. I hope you found this puzzle interesting and please let me know your thoughts!

--

--

Marco
Webtips
Writer for

Software Engineer | Hongkonger 🇭🇰 Passion for Craftsmanship