Back

You don't need a state management library -- use useState plus Context

You don't need a state management library -- use useState plus Context

When it comes to state management in React, you have a ton of options. You can integrate a variety of state management libraries into your React applications. However, since the release of React version sixteen, the context hook has been much more powerful and straightforward. Installing state management libraries enables you to manage states globally without complicating your application.

You may occasionally find yourself developing applications that may or may not require real-time updates, in which case the question of whether you need a state management system, when to implement it, how to implement it, and whether useState and useContext hooks can suffice should be addressed first, before deciding which state management library to use.

In this article, I’ll go over everything you need to know about using the useState and context hook to manage states, and soon it’ll become one of your best options for state management.

What is State Management?

The term ‘state’ is almost unavoidable when working with front-end frameworks or libraries. You would undoubtedly hear about changing states and how it affects the application, but what exactly are states?

Think of state as the current value of a variable in a React application that changes over time when an action or event occurs. It is a major concern of any react application because the only time a react application re-renders is when its state changes.

In reality, you are currently reading this article and may decide to grab a meal, return to work, or do something relaxing once you are finished. As a result, your current state will change almost every time you decide to do something new, and just like your state changes, variables in React applications can also change, which is why state management is crucial.

State management is the process of managing states. Sometimes, you may have a single component, but as your project grows, you may decide to refine your application into several components to make your code easier to maintain. These components may need the same states, and instead of rewriting the logic, you could simply share the states with them.

Props drilling comes into play here because it allows us to pass states from one component to the next. However, with the context hook built into React, we can share states without having to drill them using props or even install state management libraries.

The UseState Hook

If you’ve ever visited an e-commerce website to buy a pricey item you’ve had your eye on, the first thing that comes to mind is to add it to your cart and proceed to checkout. When you click the add-to-cart button, the value of the cart increases by one, and if you choose to buy multiple items instead, the value will continue to increase as long as you keep clicking that button.

The cart value can be thought of as a React state. It is merely a value that evolves in response to user actions or events. In light of this, the useState hook can be regarded as the correct way to define states in functional components.

Well, as you can see, it’s not that complicated. Still, it might be a little because with a pure vanilla JavaScript project, all we had to do was define our variables, create the functionalities, constantly update the variables in between the functionalities, and we were good to go. But not with React. When variables change over time, you must explicitly define them using the useState hook to ensure React re-renders its changes to the Document Object Model (DOM).

Enough with the story; let’s look at some examples. What if we try to build a simple counter app, but this time without states, just like you would with vanilla JavaScript? Let’s see what it’ll look like.

export default function App() {

 //defining the variable count
 let count = 0;

 //the increment functionality
 const increment = () => {
   count = count + 1;
 }

 //the decrement functionality
 const decrement = () => {
   count = count - 1;
 }
 return (
   <div className="App">
     <h1>Welcome to our counter App</h1>
     <p>{count}</p>
     <button onClick={decrement}>subtract</button>
     <button onClick={increment}>Add</button>
   </div>
 )}

Run this code

In this case, I created a count variable and set its value to zero. The increment function increases the count by one, while the decrement function decreases the count by one. These two functions are passed to an onClick event handler on the button, which executes the functionality immediately after a click is detected.

However, because React does not consider the variable ‘count’ to be a reactive value that changes over time, attempting to alter the value count will never result in a decrease or increase. We must properly declare variables that change over time using the useState hook to correct this. So let’s do exactly that.

In React, there are three steps to using states;

  • Import the state
  • Define the state
  • Update the state

The useState hook has a distinct syntax. It uses an array destructuring containing the state variable and a setter function that can be used to change the initial state value set within the hook. Here is an illustration:

//import the state

import { useState } from "react";

function State() {
 //defining the state
 const [count, setCount] = useState(0);

 //update the state
 const increment = () => {
   setCount(count + 1);
 };

 //update the state
 const decrement = () => {
   setCount(count - 1);
 };

 return (
   <div className="App">
     <h1>Welcome to our counter App</h1>
     <p>{count}</p>
     <button onClick={decrement}>-</button>
     <button onClick={increment}>+</button>
   </div>
 );
}

export default State;

Run this code

Great! It worked, and the current state value is correctly rendered to the Document Object Model whenever the increment or decrement button is clicked. Alright, now let’s talk about the context hook.

Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

What is the Context API?

According to React documentation, React Context provides a way to pass data through the component tree without having to pass props down manually at every level.

You may have encountered various E-commerce applications, and I’m sure you’ve noticed how the number of items in the cart is displayed on every page. Do you think the cart’s functionality was created on each application page, or could it have been passed as props to various components, or could it have a global state accessible by all components of the application?

Honestly, it could be any, but creating the functionality over and over will go against the concept of functional programming, which is referred to as ‘DRY Programming.’ DRY stands for Do Not Repeat Yourself, and structuring your code in this manner is undoubtedly you repeating yourself.

You might say, “How about I just pass the states as props?” That is also not a bad idea, but it can become very messy and chaotic, especially when working with multiple components.

With React version sixteen, the context hook has been extremely useful in managing states. We can now write shorter codes and keep our applications as simple as possible. Let’s see what it’ll look like.

The Context API + The UseState Hook

Similarly to how you built a simple counter app with the useState hook, you could recreate it, but this time we’d try to make the count variables, as well as the increment and decrement functionalities, globally accessible variables. Then try to utilize these variables in sub-components nested within the root component.

To implement this, there are two major steps to keep in mind.

  • Create globally accessible variables
  • Access globally created variables

Creating globally accessible variables

The context hook has an approach to creating globally accessible variables. It may appear complicated at first, but it becomes much easier with practice. To create globally accessible variables, simply follow these steps:

  • Determine your parent component
  • Import the createContext and useState hook
  • Assign a variable to the initialized createContext hook and export it
  • Define the state value with the useState hook
  • After the return statement, Wrap the components inside the initialized context variable provider and pass the global variables as a value
//Import the createContext and useState hook from react
import { useState, createContext} from "react";


//Assign a variable to the initialized createContext hook and export it
export const CountContext = createContext(0);

function Parent() {

 //defining the state
 const [count, setCount] = useState(0);

 //update the state
 const increment = () => {
   setCount(count + 1);
 };

 //update the state
 const decrement = () => {
   setCount(count - 1);
 };

 return (
   <main className="App">

      {/* After the return statement, Wrap the components inside the initialized context variable provider, and pass the global variables as a value. */}

     <CountContext.Provider value={{ count, decrement, increment }}>
       <h1>Welcome to our counter App</h1>
       <Child />
     </CountContext.Provider>
   </main>
 );
}

export default Parent;

The CountContext Provider can also be built into separate components, depending on how you want to structure your code. We have three global variables to pass to the child component in this case, so using an object is the only option. If it were just a single value, there would be no need for another object in its syntax. Next, let’s try to access these variables.

Accessing globally created variables

The only thing left to do is to access and use the global variables that have been created. Follow the steps listed below to wrap this up:

  • import the useContext and initialized variable context hook from its specified component
  • Destructure the object and initialize the useContext hook while passing the context variable.
  • Utilize the global variable
//import the useContext and initialized variable context hook from its specified component

import { useContext } from "react";
import { CountContext } from "./App.js";

function Child() {

//Store the initialized useContext hook in a variable and pass the initialized context variable.

 const { count, increment, decrement } = useContext(CountContext);

 return (
   <section>
      {/* Utilize the global variable */}
     <p>{count}</p>
     <button onClick={decrement}>-</button>
     <button onClick={increment}>+</button>
   </section>
 );
}

export default Child;

Run this code

Perfect! As you can see, the context API simplifies and makes our code easier to read. In comparison to props drilling and complex state management libraries, it provides a better approach to sharing states between components, allowing any nested component below the root tree to safely access these provided states.

Does the context hook replace state management libraries?

Certainly not! Although the context hook has the potential to be used for state management when combined with state hooks, it is not a state management library. A state management library is software specifically designed to handle state requests and mutation.

The context hook + useState, on the other hand, can manage states just as well as various state management libraries without adding additional software complexities to your application. So, when it comes to basic and scalable projects that require minimal states, the context API is your best bet, and with memoization, you can quickly speed up your application run time simply and concisely.

Nevertheless, when working with large teams on projects that require many global shared states, I recommend using state management libraries to ensure that the work is done correctly and the application is effective.

Conclusion

The context API is an essential concept of React because it allows components to easily share states without the need for large dependencies. Remember, your application’s simplicity and performance is always something to consider, so the context API should always be your first choice before integrating any state management library.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs and track user frustrations. Get complete visibility into your frontend with OpenReplay, the most advanced open-source session replay tool for developers.

OpenReplay