Context limitations when imitating Redux in web-app
By: Lumbardh Agaj
Tags: JavaScript, React, React Context, Programming, Redux

4.Context Performance

The most important bit to understand about Context is that it will re-render all components that are descendants of the context provider, whether or not they consume the context when a state change happens. The only exception to this rule is when the context provider’s children do not change and only the components that consume the context will re-render. This is the main reason we pass the children prop to components that provide a context instead of creating components inside them (Please read James Nelson blog post on avoiding unnecessary Context renders).

The bottom line is that we end up with components that are re-rendered, even though they may have not changed, whereas we would ideally want to re-render only those to which changes did occur. At least that’s the case with Redux when used in accord with library guidelines and practices.

The best strategy to reduce unnecessary re-renders is to split the context into other contexts (or create new ones from scratch). A strong case to do so is when:

Part of the state changes frequently and we want to isolate it.

Components are only interested to consume a part of the application state.

Part of the state changes infrequently and components may consume both the part and the whole state.

To carefully demonstrate the scenarios when we might want to split the context, let’s first describe a common page of our application. We have the fridge page which at the page header has a form that saves the ingredients and a fridge ingredients list as page body. Moreover, when a user successfully deletes a fridge ingredient it displays a notification message saying “Fridge ingredient was successfully deleted.”

Deleting a fridge ingredient

We can gain slightly better performance by trying to reduce unnecessary re-renders whenever a notification happens.

Currently, when a user completes an action that displays a notification the entire page is re-rendered twice — once upon the addition of notification on the screen and again on its removal. Splitting the notifications substate from the store context state falls into the scenario of isolating the frequent changes (an overwhelming number of user actions issue a notification) and where the components are interested to consume only the part of the state.

As we did with StoreContextProvider , we’ll implement the NotificationContextProvider component.

NotificationContextProvider component

Our Root component will create all context providers. We have removed the notifications reducer from appReducer and instead we pass it as a prop value to the newly created NotificationContextProvider component.

Root component with store and notification contexts

Finally, the NotificationToast component (which is responsible for managing notifications) will solely consume the notification context state and its dispatcher function, which we named “notify.”

NotificatonToast component after creating notification state and update contexts

Now, whenever a component will dispatch a notification it will re-render only the NotificationToast component and not the whole application.

Using React dev profiler to record delete fridge ingredient action

Using React dev profiler to record our action we can see that when the notification happens only the NotificationToast component is affected (please look at the third and fourth render from the recorded session of profiler). Thus, not only did we improve our application performance (this comes with a maintainability cost which we will see in the next section) but more importantly we’ve re-rendered only the components that have changed (this applies for NotificationToast component only).

We will only discuss the case when substate changes are infrequent and components may consume state from both the part and the whole application state. It’s becoming increasingly common practice for applications to have a theme where a user selects his application view preference. This part of the state is not supposed to change often. As a result, we can split the theme state and its dispatch function into separate contexts. Components will be interested to consume both the theme context and store context provider but because theme state changes are infrequent, in this case, we will not be bothered with components’ unnecessary re-renders (another similar scenario is user authentication).