Managing State in React Without Redux: Simpler and Smarter Alternatives

Yahya DahirYahya Dahir
4 min read

If you have worked with React before, you‘ve probably heard of Redux. It’s a state management tool that helps keep track of data ( or “state” ) in your React apps. While Redux is a powerful, it can feel a bit heavy, especially for smaller projects or if you’re just starting out.

In this blog, we’re going to explore some simpler ways to manage state in React without needing Redux. Let’s break it down .

What is State in React?

Imagine you’re playing a video game. The score you’re tracking is like the “state” of the game. Every time you get points, the score changes, and the game shows the updated number of scores. In React, state is just like that. It’s the current information that your app is showing, and it can change based on what the user does.

Why not Redux?

Redux is like currying a huge backpack, when all you need is a small pencil case. It’s got a lot of features, but sometimes it can feel like too much for smaller projects. It makes sense for larger, complex apps, but if you’are building something simpler, you probably don’t need all that extra weight.

For this React has some built-in and lightweight tools you can use instead of Redux.

  1. Using React’s useState Hook

useState allows you to store and update values directly inside a React component. It’s probably the easiest and most straightforward way to manage local state in React.

How it Works:

You create a state variable and a function to update it. When the state changes, React will re-render the component. Here is an Example

import React, { useState } from 'react';

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

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

In this example, count is our state, and setCount is how we update it. Each time we click the "Increment" button, we add one to our count and the component re-renders to display the updated count

  1. React’s useContext Hook

useContext allows you to create a context ( basically a global store ) and share values across components without explicitly passing props. The useContext is perfect for sharing state between components without having to pass props down multiple levels. it works great for global state that needs to be accessible by many components.

How it Works:

You create a context and provide it to components. Any component can access and update the context’s values by using the useContext hook.

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

const CountContext = createContext();

function CountProvider({ children }) {
  const [count, setCount] = useState(0);
  return (
    <CountContext.Provider value={{ count, setCount }}>
      {children}
    </CountContext.Provider>
  );
}

function DisplayCount() {
  const { count } = useContext(CountContext);
  return <p>Count: {count}</p>;
}

function IncrementButton() {
  const { setCount, count } = useContext(CountContext);
  return <button onClick={() => setCount(count + 1)}>Increment</button>;
}

function App() {
  return (
    <CountProvider>
      <DisplayCount />
      <IncrementButton />
    </CountProvider>
  );
}

In this example, count is our state, and setCount is how we update it. We create a context to share count and setCount across components. The CountProvider component makes this state available to DisplayCount and IncrementButton. Each time we click the "Increment" button in IncrementButton, setCount updates the shared count, and both DisplayCount and IncrementButton re-render with the new count.

  1. React’s useReducer Hook

useReducer is like useState, but it allows you to handle complex state logic by reducing state changes down to one action at a time. The useReducer hook is a bit more advanced, but it’s still much simpler than Redux. It’s great when your state updates are more complex, like when multiple actions need to change the state in different ways.

How it Works:

You create a reducer function ( just like in Redux ) that tells React how to update the state based on the type of action dispatched. Here is an Example

import React, { useReducer } 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:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

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

In this example, state.count is our state, and dispatch is how we update it. Each time we click the "Increment" or "Decrement" button, we send an action to the reducer. The reducer function determines how the state should change based on the action type, updating the count accordingly and re-rendering the component with the new state.

Conclusion

Managing state in React doesn’t have to be complicated. While Redux is powerful, it’s not always necessary, especially for smaller projects. With React’s built-in hooks like useState, useContext, and useReducer , you can handle state efficiently and keep your code simple.

Each of these tools has it’s strengths. If you’re building a small app, start with useState. For apps that need to share state globally, useContext works like a charm. And if you need something bit more advanced, useReducer is a solid choice.

1
Subscribe to my newsletter

Read articles from Yahya Dahir directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Yahya Dahir
Yahya Dahir