React Sharing Information

React relies on a simple system of props and callbacks to share information. You pass down information to child components with props and you use callbacks to go from the child to the parent. This simple mechanism can become messy If you have multiple components that need the same information or if you have to provide information from a high level to a much lower one. There is another mechanism however, the context provider. In this blog post we will see how a context provider works when you have to share information with many components.

The problem with using props is that you have to pass a lot of data down to the components and use callbacks to keep it up to date since it is information that is shared across components. A context provider allows for each component to get the information themselves. This makes a context a very easy way to communicate information between different components, but it is less direct in that sense you need to know that a component needs to be wrapped in the right provider, and it can be unclear whether or not you are already inside of such a provider.

Recently I was looking into redoing some communication regarding list information. The current way was by using an object (an actual instance of a class) that provided the needed information. Since re-renders would only be triggered when the actual object changes, I still had to rely on the callback mechanism to inform the 'root' component about the updated, but at leas it wrapped all the logic in a nice way. To clean up the code, I wanted to use a context provider for this logic instead.

To give a better image of what kind of information needed to be kept track of, let me give you an example. Below you see an overview of a table where each row can be selected. Selected rows can be moved up or down, removed and you can filter the rows as well. The table header allows for a select all and does indicate this correctly when either all are selected or some.

Which information is required to be able to handle such a table?

  • Each row needs to know whether it is visible, enabled and selected.

  • The action buttons need to know whether at least one element is selected, whether the first and/or last is selected (for move up and down)

Interacting with the table can cause elements to become selected, which is easy. How to determine whether a row is enabled or visible is tricky since this depends on some criteria that can not be generalized. I ended up with a provider that takes ids and functions to handle enabled/visible as props. For each id I keep track of all the information, using the provided functions for enabled and visible. I created specific hooks for the different parts, being the row, the header and the actions.

After having this implemented I tested this with a huge list and noticed that performance was horrible. While it makes sense that the select all operation would incur a heavy re-render of all rows, no such thing should be required when selecting a single row. Nevertheless, I noticed that it did and the reason for this is that the row uses the context and selecting a row causes the context to update which causes all the users to re-render. I tried out my old and more traditional way, and by default it behaves the same, but by memoizing the components you can avoid re-rendering every row when only a single one is selected. Unfortunately since the cause of the re-render is different, the same approach will not work when using the context provider.

At least, not if you let the row use the context itself. I tried a couple of things and having the table pass the information as props to the row, such that the row does not depend on the context anymore, allows you again to memoize that component. This means that even when using the context provider, you can design your components to handle lots of data if a context update would cause a heavy re-render. Currently I don't use this approach since I believe the real problem here is simply too much data being shown and that should be handled in a different way, but it is good to know that such a hybrid approach between context and props is a feasible solution.

In the future I am expecting to extract a lot more things into a context provider to clean up some of my original code. Note that I do not advocate to put everything in contexts and pass all information like that. I still like the standard props and callback approach for direct communication between components. In this case however it was information that was shared over multiple components at different levels of granularity.