7 min read

Why Context Should Not be Used for State Management in React JS

Author

Luka Žagar

Date

Category

Development

Why context should not be used for State Management in React JS

Since the release of the new React Context API, "Context vs Redux" has become one of the most hotly discussed topics in the React community. Unfortunately, most of this "discussion" arises from a misunderstanding of the two tools' purposes and applications. Despite the fact that there have been hundreds of responses to different questions about Context and Redux (see React, Redux, and Context Behavior, A (Mostly) Complete Guide to React Rendering Behavior, and When (and when not) to Reach for Redux), the mystery persists. Given the high volume of inquiries on the topic, this article aims to clarify why Context should not be used in state management.

Let us first explain what react Context is;

What is React Context, and how does it work?

Let's start with the official Context overview from the React docs:

Context allows you to transfer data down the component tree without manually passing props at each stage.

Data is passed top-down (parent to child) in a typical React application through props, but this can be inconvenient for some types of props (e.g., locale preference, UI theme) that are needed by multiple components within an application. Context allows components to share values like these without moving a prop through each level of the tree directly. It's worth noting that it makes no mention of "managing" principles instead of focusing on "passing" and "sharing" them.

React 16.3 introduced the new React Context API ( React.createContext () ). It took the place of the legacy context API, which had been around since the early days of React but had a lot of flaws.

The main issue with legacy context was that if a component missed rendering via shouldComponentUpdate, updates to values passed down via context could be "blocked." ShouldComponentUpdate was used by several components for performance enhancements, rendering legacy background useless for passing down plain data.

createContext() was created to address this problem, ensuring that any changes to a value are reflected in child components even if a component in the middle is not rendered.

function ParentComponent() { const [counter, setCounter] = useState(0); const contextValue = {counter, setCounter}; return ( <MyContext.Provider value={contextValue}> <SomeChildComponent /> </MyContext.Provider> ); }

Now can call useContext and read the value:

functionNestedChildComponent() { const { counter, setCounter } = useContext(MyContext); }

Why Should Context not Be Used in "State Management"?

Any data that defines an application's actions are referred to as "state." If we wanted to, we might categorize it as "server state," "communications state," or "location state," but the point is that data is being processed, read, modified, and used.

"State management," according to David Khourshid, "is how state shifts over time."

As a result, we may define "state management" as having the ability to:

  • • keep a starting value
  • • Get the most recent value
  • • Change a value

There's usually a way to be informed when the current value changes as well.

State management is demonstrated by React's useState and useReducer hooks. You can do the following for any of those hooks:

  • • Store an initial value by calling the hook.
  • • By using the hook, you can also get the current value.
  • • Call the supplied setState or dispatch function to change the value.
  • • Since the item re-rendered, know that the value has been modified.

Redux and MobX, on the other hand, are simply state management:

  • • By naming the root reducer, Redux stores an initial value and allows you to read the current value with store.getState() uses the store to change the value.
  • • Dispatch (action) and notifies listeners that the store through the store has been changed.
  • • Become a member (listener).

MobX assigns field values in a store class to store an initial value, allows you to read the current value by accessing the store's fields, updates values by assigning to those fields, and notifies you of changes through autorun() and computed ().

Server caching tools such as React-Query, SWR, Apollo, and Urql may also be considered "state management" because they store initial values based on fetched data, return the current value via hooks, enable updates via "server mutations," and notify of changes by re-rendering the component.

These requirements are not met by React Context. As a result, Context isn't a "statecontrol" application.

Context, as we previously said, does not "store" anything. The parent component that makes a MyContext.Provider> is in charge of determining what value is passed into the Context, which is usually determined by React component state. The useState/useReducer hook is where the real "state management" happens.

Context is how a state (which already exists somewhere) is shared with other components, as David Khourshid also said.

State administration is unaffected by Context. I think Context is more like secret props than an abstracted state, as a recent tweet put it.

Let us think about it this way. We might have used the same useState/useReducer code but prop-drilled the data and update the feature deeper into the component tree.

The app's overall conduct would have remained the same. Context simply allows one to bypass the prop-drilling.

Comparison between Context and Redux

Let's take a look at the features that Context and React+Redux offer:

Features of Context:

  • • It doesn't "manage" or "store" anything.
  • • Only in React components does it work.
  • • A single value, which may be anything, is passed down (primitive, objects, classes, etc.)
  • • Allows you to read a single value.
  • • It's possible to prevent prop-drilling by using this method.
  • • For both, it displays the current background meaning.
  • • The React DevTools display the Provider and Consumer elements, but not the history of how that value changed over time.
  • • When the background value changes, it updates consuming elements, so there is no way to miss updates.
  • • It is solely for rendering components and does not have any mechanism for side effects.

React+Redux

  • • A single value is stored and managed (which is typically an object)
  • • Works with any UI, even those that aren't React-based.
  • • This function allows you to read a single value.
  • • It's possible to stop prop-drilling by using this technique.
  • • Using action and reducers, you can change the value.
  • • DevTools that display the history of all dispatched acts and state changes over time are available.
  • • Allows app code to cause side effects using middleware.
  • • Allows components to receive store updates, remove basic pieces of the storestate, and then re-render when those values change.

Frankly speaking, these are two very different tools, each with its own set of capabilities. "Can be used to stop prop-drilling" is the only thing they have in common.

Context and useReducer

One of the problems with the "Context vs Redux" debates is that some people say things like "they use useReducer to manage their state, and they use Context to transfer the meaning down." Despite this, they are unaware that they can state things differently; all they say is "I'm using Context."

And, unfortunately, these are the most common sources of our misunderstanding, which is unfortunate because it helps to reinforce the myth that Context "manages state."

Let us discuss more detail about the Context + useReducer pair. Redux + React-Redux looks a lot like Context + useReducer. Both of these have the following traits in common:

  • • a value that has been saved
  • • a function that reduces
  • • Execution of acts
  • • A method of passing the value down and reading it in nested components.

However, as explained below, there is a range of major differences between Context + useReducer and Redux + React-Redux in terms of their capabilities and behaviors:

Usage + background Context is used by Reducer to transfer the current state value.Context is used by React-Redux to transfer the current Redux store case. When useReducer returns a new state value, all components subscribed to that Context are forced to re-render, even if they only care about a portion of the data. Depending on the size of the state value, how many components are subscribed to that data, and how often they re-render, this may cause performance issues.

Components may subscribe to particular pieces of the store state and then re-render when those values shift, thanks to React-Redux.

There are a few other significant variations as well:

Usage + Context

The Reducer is a React function, so they can't be found anywhere else. Since a Redux store is independent of any UI, it can be used independently of React.

The React DevTools allows you to see the current context value but not previous values or changes over time. All actions that were dispatched, the contents of each action, the state as it occurred after each action was processed, and the changes between each state over time are all visible using the Redux DevTools. Since useReducer lacks middleware, it's possible to do some side-effects with useEffect in conjunction with useReducer, and some have even attempted to wrap useReducer in something that resembles middleware, but both are seriously restricted in comparison to Redux middleware's functionality and capabilities.

Many articles advocate creating several separate contexts for various bits of state, both to reduce needless re-renders and to address scope issues.

Some of them also recommend creating your own "context selector components," which necessitates the use of React and other libraries. memo(), useMemo(), and breaking it up so that each section of the state has two different contexts (one for the data and one for the updater functions). Sure, you can write code that way, but you're just re-inventing React-Redux in a less-than-ideal way.

So, while Context + useReducer appears to be similar to Redux + React-Redux at first glance, they are not entirely equal to Redux and cannot completely replace it.

Choosing the Right Tool

As I previously said, it is essential to understand what problems a tool solves and beware of your own issues to choose the best tool to solve your problems.

Use Case Summary

Let's go over the scenarios for each of these:

Context :

Using prop-drilling to transfer a value down to nested components Reducing agent Moderately difficult A reducer function is used to control the state of React components.

Context + useReducer :

Complexity is moderate. Using a reducer feature to control state in React components and passing the state value down to nested components without prop-drilling

Redux

Reducer functions are used to control states ranging from moderate to highly complex. Where, why, and how condition changed over time is traceable.

Do you want to keep the state management logic separate from the UI layer?

Different UI layers can share state management logic.

When actions are dispatched, you can use the power of Redux middleware to add additional logic.

The ability to save parts of the Redux state.

Adding the ability for developers to replay bug reports.

During creation, logic and user interface can be debugged more quickly.

Redux + React-Redux

All of Redux's use cases, as well as interacting with the Redux store via React components

Again, these are various resources that address multiple issues.

When to use Context?

When you want to make a value available to a section of your React component tree without passing it down as props across each level of components, it is better and most advisable to use Context.