DEV Community

Cover image for ๐Ÿš€ React Best Practices for Scalable Frontends: Part 2 โ€“ State Management
El Mahfoud Bouatim
El Mahfoud Bouatim

Posted on

๐Ÿš€ React Best Practices for Scalable Frontends: Part 2 โ€“ State Management

Introduction

State management is one of the foundational pillars of building robust and scalable React applications. As your app grows, handling state effectively becomes more challenging, and React offers a rich ecosystem of tools and patternsโ€”from built-in hooks like useState and useContext to libraries such as Redux.

However, with so many options available, itโ€™s easy to feel overwhelmed or make architectural choices that might not scale well.

In this section, weโ€™ll demystify state management, break it down into actionable concepts, explore common pitfalls, and share best practices for managing state effectively.

Letโ€™s dive in.


๐Ÿ“š 3.1 What is State in React?

State in React represents the current data and behavior of your application. Think of it as a snapshot of your app at a given point in time.

Examples of State:

  • Whether a button is disabled or enabled.
  • The count of active users displayed on your dashboard.
  • Whether a modal window is open or closed.

๐Ÿง  Reactive vs Non-Reactive State

React state can be divided into two main types:

  • Reactive State: Changes trigger a component re-render (e.g., useState).
  • Non-Reactive State: Changes persist across renders but donโ€™t trigger re-renders (e.g., useRef).

๐Ÿšจ The Pitfall of Prop Drilling

One of the first techniques you learn in React is lifting state up to a parent component and passing it down as props to child components. While this approach works for simple cases, it quickly becomes problematicโ€”a phenomenon known as prop drilling.

Why Prop Drilling is Problematic:

  • Makes components tightly coupled and harder to maintain.
  • Causes unnecessary re-renders across child components.

The solution? Use the right tool for the right job.

In the following sections, weโ€™ll start with local state management and gradually move towards global state solutions.


โš›๏ธ 3.2 Local State with useState

Local state refers to state managed within a single component. The useState hook is the primary tool for managing this type of state in React.

๐Ÿ“ When to Use useState?

  • For UI-specific state (e.g., toggle switches, modal visibility).
  • For simple local logic that doesnโ€™t need to be shared between components.

โœ… Best Practices for useState

  • Avoid deeply nested state objects with useState.
  • Prefer derived state when possible to reduce redundancy.
  • Keep local state truly localโ€”avoid managing global logic with useState.

Example:

import { useState } from 'react';

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

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

Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ก Pro Tip: If you find yourself lifting state to higher components repeatedly, it might be time to consider useContext or redux.


๐Ÿ“ฆ 3.3 Non-Reactive State with useRef

While useState is great for reactive state, there are scenarios where you need to persist values across renders without triggering re-renders. Thatโ€™s where useRef comes into play.

๐Ÿ“ When to Use useRef?

  • To store references to DOM elements (e.g., input focus).
  • To persist mutable values across renders (e.g., timers, counters).

โœ… Best Practices for useRef

  • Donโ€™t use useRef for state that affects rendering logic.
  • Use it sparingly and only for cases where reactivity isnโ€™t required.

Example:

import { useRef } from 'react';

function TextInput() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ก Pro Tip: Use useRef for performance optimizations and avoiding unnecessary renders.


๐Ÿค 3.4 Shared State with useContext + useReducer

As your app grows, youโ€™ll encounter scenarios where state needs to be shared across multiple components. For these cases, React provides Context API and useReducer.

๐Ÿ“ When to Use useContext + useReducer?

  • When multiple components need access to the same state.
  • When state logic involves complex transitions.

โœ… Best Practices for useContext

  • Split contexts logically (e.g., AuthContext, ThemeContext).
  • Prevent unnecessary re-renders by memoizing Context.Provider values.

Example:

import { createContext, useReducer } from 'react';

const AuthContext = createContext();

function authReducer(state, action) {
  switch (action.type) {
    case 'LOGIN':
      return { ...state, user: action.payload };
    default:
      return state;
  }
}

function AuthProvider({ children }) {
  const [state, dispatch] = useReducer(authReducer, { user: null });

  return (
    <AuthContext.Provider value={{ state, dispatch }}>
      {children}
    </AuthContext.Provider>
  );
}

Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ก Pro Tip: Donโ€™t use a single global context for everythingโ€”split them by functionality.


๐Ÿš€ 3.5 Global State with Redux

When state management requirements surpass what Context API can handle, itโ€™s time to consider dedicated global state libraries like Redux.

๐Ÿ“ When to Use Redux?

  • For application-wide state shared across unrelated components.

โœ… Best Practices for Global State

  • Keep the state modular and logically structured.
  • Use middleware (e.g., Redux Thunk, Redux Saga) for side effects.
  • Avoid storing derived or UI-specific state in global state.

๐Ÿ’ก Pro Tip:

  • Avoid making API calls directly in Context or Redux reducers.
  • Use server-state libraries like React Query for efficient data fetching and caching.

๐Ÿ Conclusion

State management is a crucial aspect of building scalable React applications. In this article, we covered:

  • Local State with useState
  • Non-Reactive State with useRef
  • Shared State with useContext and useReducer
  • Global State Management with libraries like Redux

Thank you!.

Top comments (0)