DEV Community

Cover image for Today Finally Used useReducer in Real App — And It Changed How I Think About State
Usama
Usama

Posted on

Today Finally Used useReducer in Real App — And It Changed How I Think About State

A few days ago, I decided to stop doing toy examples and actually build something real with useReducer.

So I built a React Quiz App.

Not a huge app. Not a fancy app.
But a real app with real state, real flows, and real problems.

And this time, I didn’t even hardcode the data.
I used a fake API with json-server to simulate a real backend:

"json-server": "^0.17.3"
Enter fullscreen mode Exit fullscreen mode

So the app fetches questions exactly like a real production app would.

And honestly? I enjoyed this more than I expected.

This was the first time useReducer didn’t feel like “just another hook”…
It finally felt like an architectural tool.


The Problem: My App Was No Longer “Simple State”

This quiz app has:

  • Loading state
  • Error state
  • Ready state
  • Active (playing) state
  • Questions coming from an API (json-server)
  • Current question index
  • Selected answer

That’s not one state.
That’s a state machine.

And this is exactly where useReducer starts to make sense.

Instead of doing this:

const [status, setStatus] = useState("loading");
const [questions, setQuestions] = useState([]);
const [index, setIndex] = useState(0);
const [answer, setAnswer] = useState(null);
Enter fullscreen mode Exit fullscreen mode

I moved everything into one predictable state object:

const initialState = {
  questions: [],
  status: "loading",
  index: 0,
  answers: null,
};
Enter fullscreen mode Exit fullscreen mode

And one central brain to control everything: the reducer.


The Breakthrough: One Place That Controls the Whole App

This part was the real “aha” moment for me.

Instead of updating state from everywhere, I now do this:

function reducer(state, action) {
  switch (action.type) {
    case "setQuestions":
      return { ...state, questions: action.payload, status: "ready" };
    case "setError":
      return { ...state, status: "error" };
    case "setStart":
      return { ...state, status: "active" };
    case "newAnswer":
      return { ...state, answers: action.payload };
    default:
      throw new Error("Unknown action");
  }
}
Enter fullscreen mode Exit fullscreen mode

And in my app:

const [state, dispatch] = useReducer(reducer, initialState);
Enter fullscreen mode Exit fullscreen mode

Now:

  • Components don’t change state directly
  • They just dispatch events
  • The reducer decides what is allowed to happen

This feels very close to real software architecture, not just React tricks.


The Best Part: My UI Became a Pure Reflection of State

Look at this logic:

{status === "loading" && <Loader />}
{status === "error" && <Error />}
{status === "ready" && <StartScreen />}
{status === "active" && <Question />}
Enter fullscreen mode Exit fullscreen mode

I’m not thinking in terms of “what function should I call”.

I’m thinking in terms of:

“What state is the app in right now?”

That mental shift is huge.

Now the app feels:

  • More predictable
  • Easier to debug
  • Easier to extend
  • More professional

The Real Lesson

Before this project, useReducer felt like:

“Powerful, but overkill.”

After this project, it feels like:

“This is how you control complex state like an engineer.”

This quiz app taught me:

  • How to design state, not just store it
  • How to centralize logic
  • How to think in events and transitions, not random setters
  • How to work with a real API flow using json-server

And most importantly:

I’m no longer scared of useReducer.

I’m actually excited to use it again.


Final Thoughts

This wasn’t a big project.

But it was a big step in how I think about React.

I’m slowly moving from:

“Making things work”

to:

“Designing things properly”

And that’s the real progress.

Top comments (1)

Collapse
 
bhavin-allinonetools profile image
Bhavin Sheth

This is a great example of when useReducer actually clicks.
The moment you described the app as a state machine instead of “a few pieces of state” is exactly the mental shift most people miss.

I really like how you framed the reducer as a central brain that decides what’s allowed to happen. That’s the point where React stops feeling like a collection of hooks and starts feeling like real application architecture.

Also +1 for using json-server. Adding async flows and error states is usually what exposes the limits of useState, and you showed that transition very clearly.

This kind of “real app, real problems” write-up is way more helpful than abstract examples. Looking forward to seeing where you apply this pattern next.