State Management in ReactJS

Getting Started Jun 21, 2023

As a frontend engineer, you're constantly faced with the challenge of managing application state effectively. The state of your application includes various data elements that are crucial for its proper functioning. In this blog post, we will explore the concept of state, dive into the realm of state management, and provide you with a comprehensive list of recommended state management solutions. Finally, we will discuss essential factors to consider when choosing the right state management approach for your frontend projects.

State Management

Before we delve into state management, let's start by defining what state means in the context of web development. In simple terms, state represents the current condition or data of your application at a given moment. It includes variables, objects, or any other data structures that hold information necessary for your application's behavior and user interactions.

State management refers to the process of handling and manipulating application state in a consistent and efficient manner. It involves techniques and tools that allow you to control how state is accessed, modified, and shared across various components or modules of your frontend application.

Things like, "Whether modal is open or closed" and "message content in toolbox" are piece of state. In project, mau be you name it isModal where the app set to open or close and messageToolbox with string message.

We won't discuss when or why we need to use State Management in this article, as it requires a separate blog post. However, the simple reason for using state management is to avoid props drilling.

Recommendation for State Management

There are several popular state management solutions available for frontend engineers. Here's a list of some widely used options:

React Context API:

React Context API is built-in React that provide simple way to manage state. This is alternative to How to cope "prop drilling" in simple way. React Context often to use for anage state for UI. Things like Modal, Sidebar, Tooltips or something like that.  

Example

Step 1: Creating and Exporting the Context Create a context for your data. Place this code outside of any components, typically at the top level of a file, and export it for later use.

export const MyDataContext = React.createContext();

Step 2: Providing Data in the Component In the component where you want to provide the data, import the context and use the Provider component to pass the data down to the child components.

function TheComponentWithState() {
  const [state, setState] = useState('whatever');
  return (
    <MyDataContext.Provider value={state}>
      {/* Component's content goes here */}
      <ComponentThatNeedsData />
    </MyDataContext.Provider>
  );
}

Step 3: Accessing Data in a Subtree Within any component located under the Provider in the component tree, you can access the data by using the useContext hook.

function ComponentThatNeedsData() {
  const data = useContext(MyDataContext);
  // Use the data here
}

By utilizing this approach, you can create a context, provide data using the Provider, and access that data within any component under the Provider using the useContext hook.

Redux

Redux is a powerful and widely adopted state management library for JavaScript applications. It follows a predictable state container pattern, allowing you to centralize and manage complex application states efficiently.

You create global store with immutability that hold all of app state. There will be reducer function that receive action that you dispatch from your components,and respond by returning a new copy of state.

But as one of Redux Maintenancer, we recommendation to use Redux Toolkit for simplify boilerplate code.

Example

Step 1: Setting Up the Redux Store Create a Redux store using Redux Toolkit. This involves defining a slice with the initial state and a reducer function.

import { createSlice, configureStore } from '@reduxjs/toolkit';

const myDataSlice = createSlice({
  name: 'myData',
  initialState: 'whatever',
  reducers: {
    updateData: (state, action) => {
      return action.payload;
    },
  },
});

const store = configureStore({
  reducer: {
    myData: myDataSlice.reducer,
  },
});

Step 2: Providing Data in the Component In the component where you want to provide the data, import the necessary Redux Toolkit functions. Use the useDispatch hook to dispatch actions to update the data.

import { useDispatch } from 'react-redux';
import { updateData } from './myDataSlice';

function TheComponentWithState() {
  const dispatch = useDispatch();

  const handleDataUpdate = () => {
    dispatch(updateData('new data'));
  };

  return (
    <>
      {/* Component's content goes here */}
      <button onClick={handleDataUpdate}>Update Data</button>
      <ComponentThatNeedsData />
    </>
  );
}

Step 3: Accessing Data in a Subtree Within any component located under the Redux Provider, you can use the useSelector hook to access the data from the Redux store.

import { useSelector } from 'react-redux';

function ComponentThatNeedsData() {
  const data = useSelector((state) => state.myData);

  // Use the data here

  return <div>{data}</div>;
}

By utilizing Redux Toolkit, you can create a Redux store, dispatch actions to update the data, and access that data within any component using the useSelector hook.

Zustand

Zustand, a lightweight state management library tailored for React, stands out from React's context API. One advantage is that it eliminates the need for a provider, reducing the chances of encountering errors. Additionally, Zustand minimizes boilerplate code commonly found in other state management systems. It embraces the use of hooks as the primary pattern for interacting with the state, providing a simpler and more intuitive approach.

Example

  1. Import the create function from Zustand in your component:
import create from 'zustand';

2. Use the create function to create a store with an initial state and functions to update the state:

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

In this example, we create a store with an initial state of { count: 0 } and two functions, increment and decrement, to update the count.

3.Use the useStore hook to access the store in your component:

function Counter() {
  const { count, increment, decrement } = useStore();

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

In this example, we use the useStore hook to access the store in our Counter component. The count, increment, and decrement values returned by the hook can be used to display the current count and update it when the buttons are clicked.

Recoil

Recoil is a state management library specifically designed for React applications. Recoil lets you create a data-flow graph that flows from atoms (shared state) through selectors (pure functions) and down into your React components. Atoms are units of state that components can subscribe to. Selectors transform this state either synchronously or asynchronously.  

Recoil is similar to Zustand in that it is tailored for React. It is React-centric, with state changes flowing from the roots of the graph (atoms) through pure functions (selectors) and into components.

Example

Open the index.js file and create a Recoil store.

import { RecoilRoot } from 'recoil';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <RecoilRoot>
    <App />
  </RecoilRoot>,
  document.getElementById('root')
);

Create a new ReactJS component and use Recoil.

import React from 'react';
import { useRecoilState } from 'recoil';
import { countState } from './store';

function Counter() {
  const [count, setCount] = useRecoilState(countState);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increase</button>
      <button onClick={() => setCount(count - 1)}>Decrease</button>
    </div>
  );
}

export default Counter;

Finally, import the Counter component into your main App component and render it.

import React from 'react';
import Counter from './Counter';

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

export default App;

Choosing State Management

When selecting a state management solution for your frontend project, consider the following factors:

a. Project Size and Complexity: Assess the size and complexity of your application. Smaller projects with simple state requirements might benefit from lightweight solutions like React Context API or Zustand, while larger projects may require more robust options like Redux Toolkit

b. Learning Curve: Evaluate the learning curve associated with each state management solution. Consider the familiarity of your team with a particular library and its documentation, as it will impact development time and maintenance.

c. Ecosystem and Community Support: Take into account the availability of resources, third-party integrations, and community support for the state management library. A vibrant ecosystem ensures easy access to tutorials, libraries, and best practices.

d. Developer Experience: Consider how well the state management solution integrates with your existing development tools, IDEs, and debugging utilities. A seamless developer experience can greatly improve productivity and ease of maintenance.

Reference

React State Management Libraries and How to Choose
An overview of the best state management libraries and how to choose the right state management strategy for your app.
A guide to choosing the right React state management solution

Tag

Faldi

Manusia pada umumnya

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.