Imagine you’re tasked with updating a small feature in a older React project. Something simple: add a spinner to the submit button when a post is added.

Instead of simply toggling some local state, you find yourself navigating through dense layers of Redux boilerplate. It starts with an action —SET_LOADING_STATE— and from there, you journey deeper, updating reducers to handle this single task. You press on, thinking you’re close, but then the path takes a twist. As you delve into the codebase, you encounter Redux Saga, a bewildering sight of generator functions handling side effects in ways you’ve never seen before. Just when you think it couldn’t get more complex, Redux Form enters the picture, with actions and reducers for managing even simple form inputs. You pause, scratching your head at this unexpected complexity, but you carry on, determined to finish what seemed like a simple task. After writing line after line of code, scattered across multiple files, you finally step back and can’t help but feel perplexed. What should have been a straightforward task turned into an unnecessarily epic journey.

Maybe I’ve exaggerated the scenario for comedic effect, but honestly, Redux is legacy code.

As the saying goes, “When you have a hammer, everything looks like a nail.” Redux, once the hammer for managing state —global state, form state, fetched data, url state, local state, I've seen it all— has become an over-complicated solution. There are better options now.

Phasing out Redux is often the first step toward a more maintainable and readable codebase. It’s time to unravel this maze, ensuring future developers won’t get lost in the same tangle of complexity.

Moving global state to the URL

One common realisation from reviewing older React projects is that not all global state needs to live in a Redux store. For example, settings like filters, pagination, or view modes are often unnecessarily stored in a state management library. These types of state can be moved to the URL, which acts as the simplest state manager of your application. The URL brings built-in benefits like browser historybookmarking, and shareable state without any extra effort.

Tools like nuqs makes syncing global state with the URL seamless, allowing you to eliminate unnecessary Redux code. By moving state to the URL, you reduce the complexity of your codebase and enhance the user experience while doing so, win-win.

Simplifying data state with modern tools

For data fetching and caching—tasks traditionally handled by thunks or sagas in Redux, often combined with normalised data structures—modern tools like Tanstack Query and Server Components / Server functions offer far more efficient alternatives.

Tanstack Query simplifies data fetching, caching, and synchronization, eliminating the need for writing complex asynchronous logic in Redux.

Server Components take it a step further by fetching data directly on the server, reducing the need for client-side state management. You even can pass promises to client components and unwrap them with React 19's use hook, leveraging Suspense, useOptimistic and useTransition / useActionState to enhance the user experience. Additionally, Next.js’s (unstable, for now) cache offers a smarter way to manage server-side data that doesn’t need frequent refetching. 

These tools allow you to offload complex data management tasks from Redux and keep the client-side state focused on UI logic, creating a cleaner, more maintainable architecture.

Form state

In many older React projects, form state was often handled globally through libraries like Redux Form. However, form state doesn’t need to be global by default—it’s typically localised to the form itself. Using Redux for this adds unnecessary complexity, forcing you to manage input changes through actions, reducers, and selectors when simpler solutions exist.

Modern tools like react-hook-form offer a more efficient way to manage forms on the client. By pairing it with a validation library like Zod, you can handle form validation with strong typing directly in your components.

But I still have some global state

React’s Context API might be the easiest solution here. If your app only needs to share state between a few components, using React Context (alongside useReducer) can often replace Redux entirely. This keeps state management localised, avoiding the complexity of a global store and cutting down on boilerplate.

However, if you need a more scalable solution, Zustand or Jotai are excellent modern state managers. Both libraries strip away the need for actions, reducers, and the complexities of Redux, offering a streamlined way to manage global state.

  • Zustand lets you manage global state with a minimal API, making it easier to use than Redux while still offering flexibility. 
  • Jotai, with its atomic approach, allows you to manage state as independent atoms, which keeps state updates isolated and performant. 

Conclusion

Even with the improvements of Redux Toolkit. Redux has become legacy. Redux once solved critical problems in large React apps, providing a robust structure for state management. However, modern approaches like hooks and tools like nuqs, react-hook-form paired with Zod, Zustand / Jotai, Tanstack Query, and Next.js Server Components offer simpler, more elegant solutions.