Photo by Greg Shield on Unsplash

Comparison of state management solutions for React

Daniel Schulz
Sep 18, 2018 · 9 min read

Update 2020/06

React added with 16.08 support for React Hooks. Especially the useContext-hook became one that replaced all my need for other state management-solutions.

import React, { useState, useContext } from "react"
import * as helper from "../helper"
import { TodoList } from "../components/TodoList"
import { CreateTodoInput } from "../components/CreateTodoInput"
export const todoContext = React.createContext({
todos: [],
actions: {
createTodoItem: () => undefined,
updateTodoItem: () => undefined,
removeTodoItem: () => undefined,
export const TodoProvider = ({ children, initialTodos }) => {
const [todos, setTodos] = useState(initialTodos)
const removeTodoItem = (todoId: number) => {
setTodos(helper.remove(todos, todoId))
const createTodoItem = (todoTitle: string) => {
setTodos(helper.create(todos, new Todo(todoTitle)))
const updateTodoItem = (todoId: number, todo: ITodo) => {
setTodos(helper.update(todos, todoId, todo))
return (
actions: {
const ContextHookTodoList = () => {
const { actions, todos } = useContext(todoContext)
return (
<h1>Context Hook Todo</h1>
<CreateTodoInput createTodoItem={actions.createTodoItem} />
export const ContextHookTodoApp = ({ initialState: { todos } }) => {
return (
<TodoProvider initialTodos={todos}>
<ContextHookTodoList />


The component-based approach of React and other frontend frameworks like Vue and Angular has changed the way our web looks like today. One massive part of their success story is the way components communicate and share state with each other. This empowers the developer to create maintainable software by separating different parts of logic and state into dedicated components that pushes our future web.

Why do we need state at all

In an application, state is the interface between your data from any kind of backend or local change and the representation of this data with UI-elements in the frontend. State is able to keep the data of different components in sync because each state update will rerender all relevant components. State can be a medium to communicate between different components aswell.

The Setting

To inject the different libraries seamlessly I have built a few helper functions to abstract the usage of those libraries. The mountHelper class wraps the todo list component with all those providers and settings you would ideally place at the root-level of your application. I also wanted to provide an initial state to each state management library so that i have got the possibility to write integration tests against each library with the todo application.

Helper methods for mounting state management libraries with an initial State
Helper methods for performing the necessary changes to the `todos`-object

Component state

React includes several ways of managing state in an application. The most straight forward way is to define a state inside a component. The state of a component is like the props which are passed to a component, a plain JavaScript object containing information that influences the way a component is rendered. In comparison, to the props, the state can be changed by the component itself by calling setState which will trigger a re-render of the component. The state API of React is really simple at all and doesn’t add too much complexity to your application. Besides all other state management solutions, the component state is preferred to not be replaced at all because you should always keep your state as close to where it is needed to avoid unnecessary complexity. Because managing the state of an single input in a global state isn’t what you are aiming for.

Implementation of the todo list with Component State

Context API

The Context API was added to React in version 16.3.0 earlier this year. The Context API React provides an internal solution for passing state to where it is needed and avoids the possibility of prop drilling.

<Provider value={/* some value */}> 
{value => /* render something based on the context value */}
Implementation of the todo list with React Context API


Unstated by Jamie Kyle is a state management library that uses the Context API internally.

import { Container } from 'unstated';type CounterState = {
count: number
class CounterContainer extends Container<CounterState> {
state = {
count: 0

increment() {
await this.setState({ count: this.state.count + 1 });
console.log("count",this.state.count) // this works with Unstated

decrement() {
this.setState({ count: this.state.count - 1 });
function Counter() {
return (
<Subscribe to={[CounterContainer]}>
{counter => (
<button onClick={() => counter.decrement()}>-</button>
<button onClick={() => counter.increment()}>+</button>
Implementation of the todo list with Unstated


The preceding libraries and API’s work very well but if you want to have control over what is happening and especially why something is happening in your application it can be hard to debug certain state updates. In this case, Redux may help you by forcing you to work in a certain form.

Simple Counter example with Redux
Implementation of the todo list with Redux

Redux Thunk

When using Redux you are able to add middleware to your store. With middleware you are able to extend the behavior of React. For example, you can add a logger which logs all dispatched actions to the console or a lot more, just have a look at the ecosystem.

Redux Thunk actions of the Redux Thunk todo list
Implementation of the todo list with Redux + Redux Thunk middleware

Apollo Link State

Peggy Rayzis from Apollo implemented a library for managing local state as well to avoid the necessity for using a state management library from above when managing the state from the GraphQL Server in your app. You do not even need to consume a GraphQL API to use the expressive query language. You can simply query and mutate your local application state.

export const mountWithApollo = (initialState, Component) => {
const cache = new InMemoryCache();
// define the initial Store
const defaultState = { todos: [] };
const stateLink = withClientState({
defaults: initialState ? initialState : defaultState
const client = new ApolloClient({
link: stateLink
return (
<ApolloProvider client={client}>
<Component />
const TODO_QUERY = gql`
todos @client {
<Query query={TODO_QUERY}>
{({ client, data }) => (
<Component data={data} />
updateTodoItem = (cache, queryData) => (todoId: number, todo: ITodo) => {
const currentTodos = queryData.todos;
data: {
todos: helper.update(currentTodos, todoId, todo)
Implementation of the todo list with Apollo Link State


To wrap things up I have made the following observations. The more complex your app becomes the more complex your state management will become. This is the case because there are a lot more components that need to be managed and the amount will increase over time. However, there is a complementary relationship between your app complexity and your state management complexity because the more you care about state management the more complex your application will become.


JavaScript news and opinion.