March 18, 2021

Choosing between useState and useReducer

This is one of the things that you should see with your eyes, so I'd recommend watching me go through the advantages and disadvantages of different ways of using hooks for dealing with state in React.

I start with useState, show a bug in the below code, then refactor to useReducer, add redux/toolkit to it, and then finally refactor to "use-complex-state". I'm leaving the source code here, so you can play around with it:


function App() {
  const [counter, setCount] = useState(0);
  return (
    <div className="App">
      <header className="App-header">
        {counter}
        <div
          onClick={() => {
            console.log("2 points");
            setCount(counter + 2);
          }}
        >
          2 points!
          <button
            onClick={() => {
              console.log("1 point");
              setCount(counter + 1);
            }}
          >
            1 point
          </button>
        </div>
      </header>
    </div>
  );
}

useState with a bug



const initialState = { counter: 0 };

function reducer(
  state = initialState,
  action: { type: string; payload?: any }
) {
  switch (action.type) {
    case "increment":
      return { counter: state.counter + 1 };
    case "incrementBy":
      return { counter: state.counter + action.payload };
    default:
      throw new Error();
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div className="App">
      <header className="App-header">
        {state.counter}
        <div
          className="Points"
          onClick={() => {
            console.log("div clicked add two points payload");
            dispatch({ type: "incrementBy", payload: 2 });
          }}
        >
          <br />
          <button
            onClick={() => {
              console.log("button clicked add one point payload");
              dispatch({ type: "increment" });
            }}
          >
            1 point
          </button>
        </div>
      </header>
    </div>
  );
}

useReducer - everything works, but verbose code


import { createSlice, PayloadAction } from "@reduxjs/toolkit";

const initialState = { count: 0 };

const {
  actions: { increment, incrementByTwo },
  reducer,
} = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment: (state) => {
      state.count += 1;
    },
    incrementBy: (state, action: PayloadAction<number>) => {
      state.count += action.payload;
    },
  },
});

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

  return (
    <div className="App">
      <header className="App-header">
        {state.count}
        <div
          onClick={() => {
            dispatch(incrementBy(2));
          }}
        >
          2 points!
          <button
            onClick={() => {
              dispatch(increment());
            }}
          >
            1 point
          </button>
        </div>
      </header>
    </div>
  );
}

useReducer with Redux Toolkit


import { PayloadAction } from "@reduxjs/toolkit";
import { useComplexState } from "use-complex-state";
import "./App.css";

const initialState = { count: 0 };

export default function App() {
  const [state, { incrementBy, increment }] = useComplexState({
    initialState,
    reducers: {
      increment: (state) => {
        state.count += 1;
      },
      incrementBy: (state, action: PayloadAction<number>) => {
        state.count += action.payload;
      },
    },
  });

  return (
    <div className="App">
      <header className="App-header">
        {state.count}
        <div
          className="Points"
          onClick={() => {
            console.log("increment by 2 with complex state");
            incrementBy(2);
          }}
        >
          2 points!
          <br />
          <button
            onClick={() => {
              console.log("increment with complex state");
              increment();
            }}
          >
            1 point
          </button>
        </div>
      </header>
    </div>
  );
}

useComplexState hook

Let me know if you have any questions or thoughts in the comments below.

Keep reading