8 ways to handle React state effectively

The React ecosystem boasts many powerful state management options. Here are eight state management approaches, with info on when to consider each.

Author: Cory House


React began as a simple component library but has slowly grown into a robust application framework. Along the way, the React team added many powerful and unique state management features. Today, the React ecosystem boasts many powerful state management options, so picking the ideal state management approach for each feature is challenging.

In this post, I’ll outline eight state management approaches and clarify when to consider each.

1. Local state

I’ll begin with a solid default option: Local state. Local state is declared in function components using useState or useReducer hooks. In React, these hooks are a way of saying, “Hey React, when these values change, re-render.”

Prefer useState for most of your state. Consider useReducer, or powerful third party options like xState for more complex state. State transitions declared via useReducer or tools like xState can be tested in isolation.

Note that I labeled this section “Local state.” These approaches are useful when the state is used by a few components. Local state can be passed down to child components via props.

2. Lifted state

So what if a peer or parent component needs the state? Then “lift” the state to the parent. Lifting state sounds complicated. It’s not.

To lift state:

  • Cut the state and paste it into the common parent.
  • Pass the state down to the children that need it via props.
  • If a child component needs to be able to set the state, pass the state setter down it via props.

You can build just about anything with this workflow:

  • Declare local state.
  • If children need the state, pass it down via props.
  • If a parent needs the state, lift the state to the parent.

It also scales well. However, at some point, you may find you’re passing the same state down through many components via props; this can become tedious and noisy. The solution? Global state.

3. Global state

If you need to declare data that’s used by your entire app, or a large portion of your app, consider global state. React offers built-in support for global state and functions via context. When state declared in context changes, child components re-render. The nice thing with context is you don’t have to manually pass this state down. Any component can “subscribe” to the state using the context consumer.

You don’t have to use React’s built-in context for global state. Redux is a popular and scalable third-party alternative.

4. Derived state

A simple rule for declaring state: Any state that can be derived, should be derived.

For example, if I’m storing firstName and lastName in state, I shouldn’t store fullName in state. That can be derived!

In React, derived state is accomplished via plain JavaScript.

// Derived state
return (
  Full name: {firstName} {lastName}
)

This is a simple example. If it’s used multiple times, declare the derived state above your JSX.

// Derived state
const fullName = firstName + " " + lastName;
return (
  Full name: {fullName}
)

If you need to do something complex, call a function. React will call the function on each render, so you can trust your derived state will always be “in-sync”. If the function is expensive (in other words, slow), consider useMemo to memo-ize the result so that it’s only recalculated when it changes.

5. Remote state

Nearly every React app fetches data from a server via an HTTP call to a web service. This is remote state, because it originates from a server. Many apps store this state as local state, or via context if it’s used globally. But today, modern third-party libraries specialize in handling remote state.

Two popular examples are react-query and SWR . These libraries implement the stale-while-revalidate standard. This standard uses caching to balance two opposing concerns: Immediacy and freshness.

Here’s how it works:

  • Your app fetches data.
  • React-query/swr cache the data behind the scenes.
  • The next time your app needs the same data, react-query/swr immediately serve the cached data.
  • Behind the scenes, they fetch fresh data. If newer data exists, your app is notified.

The result? Your app feels fast. Previously loaded pages reload instantly, and if there’s new data, it shows up a moment later. Plus, you save bandwidth. These libraries offer a variety of other benefits including polling, refetch on reconnect and refocus, and prefetching.

React-query and swr aren’t just for REST endpoints. They work with GraphQL too. Though if you’re working in GraphQL, you may prefer libraries like Apollo or Urql, which provide their own unique caching strategies.

These libraries make remote state easily accessible throughout your app. So in some ways, they compete with global state tools like React’s context and Redux. In many apps, these remote state tools largely eliminate the need for separate global state tooling.

6. Web storage

This isn’t React specific, but the browser provides built-in storage for state that needs to persist between page reloads. Examples include localStorage, webStorage and cookies. Consider storing settings such as user preferences here. Avoid storing anything sensitive in web storage. Sensitive or critical data is best stored on a server and fetched via a web service.

7. URL state

It’s important to avoid declaring the same state in multiple spots. Doing so can lead to needless complexity and out-of-sync bugs. So if the state is location-related, consider storing it in the URL.

Examples of URL state include search queries, anchors, deep links and any settings that I may want to share with fellow users. React Router is the most popular library for handling URL state today.

8. Refs

Think of useState and useReducer as ways to tell React “When this stuff changes, redraw the screen.” Refs are a way of saying “Hey React, store this value, but don’t re-render if it changes.” So, if you need to store data that doesn’t need to trigger a re-render, consider refs.

Ref is short for reference. Refs are declared in function components using the useRef hook. Refs were originally intended to reference underlying DOM elements. But refs are also more generally useful for storing any state that isn’t displayed, and thus, doesn’t need to trigger a re-render.

Summary

This image summarizes the options outlined above and when to consider each. To see how to implement realistic examples of each of these approaches in an app, check out Managing React State on Pluralsight.

Happy coding!



Related tags:

programming   javascript   react  
About the author

Cory is the principal consultant at reactjsconsulting.com, where he has helped dozens of companies transition to React. Cory has trained over 10,000 software developers at events and businesses worldwide. He is a seven time Microsoft MVP, and speaks regularly at conferences around the world. Cory lives in Kansas City and is active on Twitter as @housecor.

10-day free trial

Sign Up Now