DEV Community

Cover image for State management options in React
Chrisspotless
Chrisspotless

Posted on

State management options in React

State management is one of the most critical aspects of building scalable and maintainable applications in React. It refers to the process of storing, managing, and updating the data that drives the behavior and rendering of a React application. In this article, we will discuss the various state management options available in React and their trade-offs.

Understanding the use of state management options in React
Enter fullscreen mode Exit fullscreen mode
  1. Props and State: Props are read-only and state is mutable. Props and state are two essential concepts in React, a popular JavaScript library for building user interfaces. These concepts are used to manage the data and behavior of components in React. Props, short for properties, are data passed down from a parent component to a child component. They are read-only and used to customize the appearance and behavior of a component. Props are passed to a component as arguments in its JSX syntax, and they can be accessed within the component using the props object. For example, a component can receive a prop named "title" and render it as a heading on the page. State, on the other hand, is a way to manage the local data of a component that can change over time. Unlike props, state is mutable and can be updated by the component itself, based on user interactions or other events. State should be used judiciously and with care, as it can cause components to re-render and affect performance if not managed properly.

Here's an example of how to use props and state in a React component:


import React, { useState } from 'react';

function ExampleComponent(props) {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>{props.title}</h1>
      <p>The count is {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increase count
      </button>
    </div>
  );
}

function App() {
  return (
    <div>
      <ExampleComponent title="Example Component" />
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

In this example, ExampleComponent receives a title prop from its parent component, App, which is used to render a heading on the page. The component also uses useState to manage its local state, count, which represents a number that can be increased by clicking a button. The state is updated using the setCount function, which is passed as a callback to the onClick event of the button.
It's worth noting that state should only be used for values that change within the component, while props are used for values that are passed down from a parent component. In this example, the title prop is passed down from App to ExampleComponent, while the count state is managed locally within ExampleComponent.

Class-based state management is one of the ways to manage state in React, which is a popular JavaScript library for building user interfaces. In this approach, the state is managed using class-based components in React, which are also known as stateful components.
A class component is defined by creating a class that extends the React.Component class. The state in a class component is stored as an instance property, and can be updated using the setState method.

Here's an example of a class-based component that manages its own state:


import React, { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  increment = () => {
    this.setState({
      count: this.state.count + 1,
    });
  }

  decrement = () => {
    this.setState({
      count: this.state.count - 1,
    });
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
        <button onClick={this.decrement}>Decrement</button>
      </div>
    );
  }
}

export default Counter;

Enter fullscreen mode Exit fullscreen mode

In this example, the Counter component keeps track of its state using the count property in the state object. The increment and decrement methods update the state using the setState method.
Class-based state management provides a lot of flexibility and is often used in larger, more complex applications. However, it can also make it more difficult to manage the state if it's not used correctly. Additionally, class-based components are more verbose than functional components, which can make them harder to read and understand, especially for newer React developers.

Function-based state management with the use of hooks is a modern and popular way to manage state in React. Hooks are a feature in React that allow you to add state and other React features to functional components.
In function-based state management, the state is managed using the useState hook, which returns an array containing the current state value and a function to update the state. The useState hook takes an initial value as an argument, and the returned state can be destructured and assigned to variables.

Here's an example of a functional component that manages its own state using the useState hook:


import React, { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  const decrement = () => {
    setCount(count - 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}> Decrement</button>
    </div>
  );
};

export default Counter;

Enter fullscreen mode Exit fullscreen mode

In this example, the useState hook is used to manage the count state in the Counter component. The increment and decrement functions are used to update the state by calling the setCount function.
Function-based state management with the use of hooks is considered to be more straightforward and easier to understand, especially for newer React developers. Additionally, functional components with hooks are more concise and easier to read than class-based components, which can make the codebase easier to maintain.
However, in some cases, class-based state management may be more appropriate, particularly for larger and more complex applications, where class-based components can provide more flexibility.

  1. The use of Context API for state management in React:The Context API is a feature of React that allows developers to easily manage and share state across components. It provides a way to pass data down the component tree without having to pass props down manually at every level. This makes it a powerful tool for state management in React applications.
    The Context API consists of two main components: the Context Provider and the Context Consumer. The Context Provider is used to store and manage the state, while the Context

    Consumer is used to access the state from within a component.

    To use the Context API, you first create a new context using the React.createContext function. This function takes an optional default value as an argument, which will be used if no Context Provider is present in the component tree.

    Next, you create a Context Provider component, which is used to wrap the component tree that needs access to the context. The Context Provider component takes the state as a prop, and makes it available to any Context Consumers within its component tree.

    Finally, you use the Context Consumer component within the components that need access to the state. The Context Consumer component takes a function as a child, which is used to access the state from the Context Provider.

    Here's an example of how you can use the Context API for state management in a React application:


import React, { createContext, useState } from 'react';

// Create the context
const CounterContext = createContext();

// Create the Context Provider component
const CounterProvider = ({ children }) => {
  const [count, setCount] = useState(0);

  return (
    <CounterContext.Provider value={{ count, setCount }}>
      {children}
    </CounterContext.Provider>
  );
};

// Create a component that uses the Context Consumer
const CounterDisplay = () => {
  return (
    <CounterContext.Consumer>
      {({ count, setCount }) => (
        <div>
          <p>Count: {count}</p>
          <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
      )}
    </CounterContext.Consumer>
  );
};

// Use the Context Provider to wrap the component tree
const App = () => (
  <CounterProvider>
    <CounterDisplay />
  </CounterProvider>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

In this example, we create a context called CounterContext using React.createContext. We then create a Context Provider component called CounterProvider, which wraps the component tree that needs access to the context. The CounterProvider component takes the state (count and setCount) as a prop, and makes it available to any Context Consumers within its component tree.

  1. The use of useReducer and useContext Hooks:

useReducer is a Hook that allows you to manage the state of your component by dispatching actions that change the state. It's similar to using Redux, but much simpler and more lightweight. With useReducer, you can define a reducer function that takes in the current state and an action, and returns the new state. The useReducer Hook takes in the reducer function as its first argument and the initial state as the second argument. You can then dispatch actions using the dispatch method that is returned from the Hook.

useContext is a Hook that allows you to access data stored in a context object within your component. Context is a way to pass data through the component tree without having to pass props down manually at every level. This can be especially useful when you have data that is needed in multiple parts of your component tree. The useContext Hook takes in a context object as its argument, and returns the current value of that context.

Both useReducer and useContext are essential for managing state and context in React, and they can help make your code more modular, scalable, and maintainable. Whether you're building a small app or a large enterprise-level application, these Hooks are a must-have in your toolkit.

Here's a simple example of how you could use these hooks together:


import React, { useReducer, useContext } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

const CountContext = React.createContext();

function Counter()

Enter fullscreen mode Exit fullscreen mode

To manage the state in the Counter component. The useReducer hook returns an array containing the current state and a dispatch function, which we use to update the state by calling dispatch with a specific action.
Next, we use the useContext hook in both the Display and Controls components to access the state and dispatch function from the context. We use the useContext hook by passing in the CountContext context object that we created using React.createContext().
In this example, we're using the useReducer hook to manage the state of a counter, and the useContext hook to make the state and dispatch function available to other components in the tree. This allows us to share state and logic between components without having to pass props down manually through every level of the component tree.

  1. The use of Redux:
    Based on the concept of a global store, which is a single source of truth for the entire state of the application. This store can be updated using actions, which are simple objects that describe changes to the state. Actions are processed by reducers, which are pure functions that update the state based on the action.

    One of the key benefits of Redux is that it makes it easy to debug and understand the state of an application. The global store and the action/reducer structure ensure that the state of the application is predictable and can be easily traced through the code. This makes it easier to find and fix bugs, and it also makes it easier to maintain the code over time.

    Another benefit of Redux is that it makes it possible to implement complex functionality in a modular and reusable way. Reducers and actions can be defined once and used in multiple parts of the application, which reduces duplication and makes it easier to manage the code.

    Here's an example of how you might use Redux in a React application:


// store.js
import { createStore } from 'redux';

const initialState = {
  count: 0
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

export const store = createStore(reducer);

// App.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';

export default function App() {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
    </div>
  );
}

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Enter fullscreen mode Exit fullscreen mode

In this example, we have a store with an initial state of { count: 0 }, and a reducer that can handle two types of actions: INCREMENT and DECREMENT. The App component uses the useSelector hook from react-redux to access the current value of count from the store, and the useDispatch hook to dispatch actions to update the state. Finally, the Provider component from react-redux is used to wrap the App component and provide access to the store throughout the application.

  1. The use of MobX: is a popular state management library for JavaScript applications, primarily used in React applications. It was created with the idea of making state management simple, scalable, and fast.

    MobX uses a reactive programming approach to state management. This means that it observes the state and automatically updates the components that depend on it. This eliminates the need for manual updates and reduces the potential for bugs. Additionally, MobX uses an optimisation technique called transactional updates, which ensures that the minimum number of updates are made, even when a large number of state changes occur simultaneously.
    MobX also integrates well with React, and you can use it together with other popular libraries, such as React Router or Apollo.

    Here's an example of how you might use MobX to manage the state of a simple counter in a React application:


import React, { useState } from "react";
import { observer } from "mobx-react";
import { useStore } from "./store";

const Counter = observer(() => {
  const store = useStore();

  return (
    <div>
      <h1>{store.count}</h1>
      <button onClick={store.increment}>Increment</button>
      <button onClick={store.decrement}>Decrement</button>
    </div>
  );
});

const store = new Store();

function App() {
  return (
    <div className="App">
      <Counter />
    </div>
  );
}

class Store {
  @observable count = 0;

  @action
  increment = () => {
    this.count += 1;
  };

  @action
  decrement = () => {
    this.count -= 1;
  };
}

export default App;

Enter fullscreen mode Exit fullscreen mode

In this example, the Store class is decorated with the @observable decorator, which tells MobX that the count property should be reactive. The increment and decrement methods are decorated with the @action decorator, which tells MobX that these methods modify the state and should be treated as a single transaction. The Counter component is decorated with the observer decorator, which makes it reactive and causes it to automatically re-render whenever the state changes.

Tips for choosing the right state management solution for your React project.

When choosing a state management solution for your React project, here are some tips to keep in mind:

  1. Complexity of the project: Consider the size and complexity of your project. If it's a small or simple project, using React's built-in state management may be sufficient. But if it's a larger and more complex project, you may want to consider using a more robust solution like Redux or MobX.

  2. Performance: Consider the performance of the solution you choose. Some state management solutions may have a performance overhead and may slow down your app if not used properly.

  3. Ease of use: Consider the ease of use and learning curve of the solution. Some solutions may have a steeper learning curve, but offer more features and power. Others may be easier to learn, but may not have all the features you need.

  4. Community support: Consider the community support for the solution. A well-supported solution is more likely to have a large and active community of developers who can provide support and contribute to its development.

  5. Integration with other libraries: Consider the compatibility of the state management solution with other libraries and tools that you are using in your project.

  6. Testability: Consider the testability of the solution. Some state management solutions may make it easier to write and maintain tests for your app.

  7. Development experience: Consider the development experience offered by the solution. Some solutions may offer a more streamlined and efficient development experience, while others may be more cumbersome to work with.

    Ultimately, the right state management solution for your React project will depend on the specific needs and requirements of your project. It's important to evaluate different solutions and weigh the pros and cons of each before making a decision.

conclusion: state management is an important aspect of developing applications with React, and there are several options available for managing state in React, including the built-in state, Context API, Redux, and MobX. Each option has its own advantages, ranging from simplicity and ease of use to scalability and performance. The choice of state management option will depend on the specific requirements of your application, as well as your personal preferences. React's built-in state is suitable for small to medium-sized applications, while the Context API is ideal for sharing data globally. Redux is a good choice for managing state in large, complex applications, while MobX is well-suited for applications that require high performance. Ultimately, the right state management option will make your application more maintainable, scalable, and efficient.

Top comments (10)

Collapse
 
intermundos profile image
intermundos

Nanostores all the way.

Collapse
 
chrisspotless profile image
Chrisspotless

Yeah sure ,feel free to engage.

Collapse
 
intermundos profile image
intermundos

Sure. React has so many options available to manage the state, it will take years do dig into each of them.

However, nanostores, in my opinion, is the one that allows to have great flexibility while offering small footprint and doesn't wire you to react only. Write your logic once, use it everywhere - Vue, svelte, solid, or vanilla.

Much recommended ❤️

Thread Thread
 
chrisspotless profile image
Chrisspotless

Most Definitely, I never have thought about it that way, I feel your opinion on nanostores would do a great deal, thanks for recommending,I will employ it to use more adequately in my next projects ♥️♥️♥️

Collapse
 
wraith profile image
Jake Lundberg • Edited

So happy to see MobX on the list. It's been my go to for years when using React!

Collapse
 
chrisspotless profile image
Chrisspotless

Sure, I'm glad you found something useful.

Collapse
 
malise5 profile image
Halkano Malise

React-context-Api

Collapse
 
fjones profile image
FJones

This reads an awful lot like it was generated by ChatGPT. It even repeats the completely useless assertion that class-based components are stateful...

Collapse
 
chrisspotless profile image
Chrisspotless

Regardless,thank you for Engaging.

Collapse
 
romeerez profile image
Roman K

No way man, ChatGPT does it better.