DEV Community

Cover image for State Management in React: A Beginner's Guide
Lovish Duggal
Lovish Duggal

Posted on

State Management in React: A Beginner's Guide

Introduction

State management is a critical concept in React, essential for building dynamic and interactive applications. As a junior developer, understanding how to effectively manage state can significantly enhance your ability to create responsive and maintainable React applications. In this blog post, we'll cover the basics of state management in React, with simple examples and explanations for beginners.

What is State in React?

In React, state refers to data that can change over time and belongs to a component. State allows components to create and manage their own data, which can change over time and influence the component's rendering. Unlike props, which are passed down from parent to child components, state is managed within the component itself.

State is essential because it allows React components to respond to user inputs, server responses, or other events by re-rendering with new data. For instance, a component that displays a list of items might use state to keep track of the currently selected item.

Basic State Management with useState Hook

The useState hook is the simplest way to add state to a functional component in React. Introduced in React 16.8, the useState hook lets you declare a state variable and a function to update it.

Syntax:

const [state, setState] = useState(initialState);
Enter fullscreen mode Exit fullscreen mode

Here's a simple example of a counter component using useState:

Example: Counter Component

import { useState } from 'react';

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

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, the count state variable is initialized to 0. Each time the button is clicked, the setCount function updates the count value, causing the component to re-render and display the new count.

State updates with useState are not immediately reflected in the component's state. Instead, React schedules these updates and processes them before the next render. This batching behaviour helps optimize performance by reducing the number of re-renders.

Understanding this behaviour is important, especially in more complex scenarios where multiple state updates might occur. It helps to know that the state value may not change immediately after calling the update function.

CodeSandBox

When to Lift State Up

Lifting state up refers to moving state from a child component to a common parent component, so that it can be shared among multiple child components. This technique is useful when several components need to reflect the same changing data.

Example: Sharing State Between Parent and Child Components


function ParentComponent() {
  const [sharedState, setSharedState] = useState('');

  return (
    <div>
      <ChildComponent1 sharedState={sharedState} setSharedState={setSharedState} />
      <ChildComponent2 sharedState={sharedState} />
    </div>
  );
}

function ChildComponent1({ sharedState, setSharedState }) {
  return (
    <input 
      type="text" 
      value={sharedState} 
      onChange={(e) => setSharedState(e.target.value)} 
    />
  );
}

function ChildComponent2({ sharedState }) {
  return (
    <p>The shared state is: {sharedState}</p>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, the sharedState is managed in the ParentComponent, and passed down to ChildComponent1 and ChildComponent2 via props. This ensures that both child components are in sync with the same state.

CodeSandBox

Context API for Global State Management

The Context API provides a way to pass data through the component tree without having to pass props down manually at every level. This is particularly useful for global state management, such as user authentication status or theme settings.

When to Use Context API

  • When state needs to be accessible by many components at different levels.

  • To avoid prop drilling (passing props through many layers of components).

Example: Theme Toggler Using Context API

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

const ThemeContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

function ThemeToggler() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button onClick={toggleTheme}>
      Switch to {theme === 'light' ? 'dark' : 'light'} theme
    </button>
  );
}

function App() {
  return (
    <ThemeProvider>
      <ThemeToggler />
    </ThemeProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, the ThemeProvider component manages the theme state and provides it to any child components via the ThemeContext. The ThemeToggler component consumes the context to display and toggle the theme.

While the Context API is powerful, it's not always the best solution for all state management needs, especially in larger applications where performance can be an issue.

CodeSandBox

Advanced State Management with Redux

Redux is a popular state management library for React, designed to handle complex state interactions in large applications. Redux provides a predictable state container, making state management more consistent and easier to debug.

Key Concepts:

  • Store: The single source of truth for the application's state.

  • Actions: Plain objects describing the type of change.

  • Reducers: Functions that determine how the state changes in response to actions.

Example: Counter Application Using Redux

  1. Install Redux and React-Redux:
install redux react-redux
Enter fullscreen mode Exit fullscreen mode
  1. Create Actions:
export const increment = () => ({ type: 'INCREMENT' });
export const decrement = () => ({ type: 'DECREMENT' });
Enter fullscreen mode Exit fullscreen mode
  1. Create Reducer:
const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

export default counterReducer;
Enter fullscreen mode Exit fullscreen mode
  1. Set Up Store:
import { createStore } from 'redux';
import counterReducer from './reducer';

const store = createStore(counterReducer);

export default store;
Enter fullscreen mode Exit fullscreen mode
  1. Connect React Components:
import React from 'react';
import { Provider, useDispatch, useSelector } from 'react-redux';
import store from './store';
import { increment, decrement } from './actions';

function Counter() {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Using Redux adds an extra layer of structure and predictability to your state management. However, it also introduces complexity and boilerplate code, making it more suitable for large applications with complex state requirements.

CodeSandBox

Choosing the Right State Management Approach

Choosing the right state management solution depends on several factors:

  • Application size: Smaller apps might be fine with useState and lifting state up.

  • State complexity: More complex state interactions might benefit from Context API or Redux.

  • Performance considerations: Context API can lead to unnecessary re-renders, while Redux provides more control over state updates.

Use Cases:

  • useState: Simple, component-level state.

  • Context API: Medium complexity, app-wide state.

  • Redux: High complexity, large-scale applications.

Conclusion

State management is a foundational concept in React, essential for creating dynamic and interactive applications. By understanding and utilizing different state management techniques, you can build more robust and maintainable React applications. Experiment with useState, Context API, and Redux to find the best fit for your projects, and continue exploring additional resources to deepen your knowledge.

For further learning, consider exploring the official React documentation, tutorials, and community resources. If you'd like, you can connect with me on Twitter. Happy coding!

Thank you for Reading :)

Top comments (2)

Collapse
 
oculus42 profile image
Samuel Rouse

Thanks for putting this information together! State management can be difficult.

The phrase "state refers to a built-in object that stores property values" struck me as very odd, mostly because of the term "built-in".

I went looking to see if I could find more detail around "built-in object" to see if I had missed something, and while an article on Kinda Technical aligned closely, there were other mentions on on freeCodeCamp and a few other sites where that built-in object refers specifically to the class-based React components.

I don't think the idea of "built-in" applies anymore. We don't get state management like this "for free" when we use functional components and hooks.

Collapse
 
lovishduggal profile image
Lovish Duggal

You're absolutely correct! The concept of a "built-in object" for state definitely applies more to class-based components in React.

In functional components with hooks, state management works differently. We leverage hooks like useState to manage state variables. These hooks don't provide a single built-in object, but rather a way to create and update individual state variables within the component.