How to handle state management in a React application

By Richard O

How are you to supposed to handle state management in a React application?  This topic has been vigorously debated and discussed, and there is no one-size-fits-all. This article is mainly describing a short story of one of our use cases for the React Context API.

Here at SGSI, we use three main tools to handle state management in React:

  1. Use the useState hook when dealing with state that’s localized to only a few components
  2. Use Redux Toolkit to store truly global state needed across the entire application such as a User’s ID, name, etc
  3. When appropriate, use React’s Context API to manage state that’s in between the above two.

Oftentimes it is not immediately clear from the start which tools and strategies would be best in each scenario. In my opinion, it’s good practice to plan what a given feature may look like to gain a better picture of the options available, but, at the same time, be considerate of the fact that as an application grows and changes, the state management strategy may also need to adapt. Let’s look at a particular use case.

I wrote a web page with a deeply nested component tree with a sizeable number of state values that were prop drilled across a pyramid of child components. The page has a submit button for a form, but there wasn’t any feedback to the user indicating that the job had been submitted. I wanted to disable the button and display a spinner while the form was being submitted.

The submit function was at the top of the page’s component tree and would need to prop drill the loading state through around 10 components. It was not a state value needed across the entire app (case #2 above), and it would be inconvenient and messy to pass down a simple loading state amongst the other props we were already drilling. The best solution was creating a React Context and Provider.

The best solution was to create a React Context, a Provider, export a useContext hook within the Provider, then destructure one of the needed states like so:

const { isLoading } = usePageContext();

Here’s what it looked like to wrap the page Provider only around the page that needed it:

<ThemeProvider>

  <SomeUnrelatedPage/>

  <TheProviderForThePageWeNeed>

    <RelevantPage />

  </TheProviderForThePageWeNeed>

</ThemeProvider>

Some have argued, as a best practice, to store all state in Redux while using the Redux browser devtools to foster a clearer debugging workflow. I could have solved the issue just using Redux, but, in my opinion, it’s better not to clutter the Redux store with non-global state. If you’ve never used Redux before and have heard complaints about how cumbersome it can be to develop with it, I encourage you to reconsider and check out Redux Toolkit! Both the boilerplate code needed, and the complexity has been drastically reduced.