Do you want to replace Redux with Context? Read this.

Martin Schnurer
5 min readDec 6, 2020

Okay so you read the whole redux documentation, understood the concept. You successfully dispatched your first action. Then you implemented action-creators, then assigned each action type as a string to constants so you don’t duplicate yourself in the redux ecosystem.

And then your application started to grow. Eventually, typescript was added so you have better safety over your code.

You probably have seen this at least once:

FETCH_USERS_STARTED
FETCH_USERS_SUCCEEDED
FETCH_USERS_FAILED

For all of these, you need to write unique action types, maybe another action creators, another reducer and switch-cases. Oh yes, and typescript types.

Oh… why this code I wrote doesn’t work? I forgot to dispatch the action! Shit.

Hah. Wait! There’s more. You read somewhere that you need to handle asynchronous code with redux-thunk or redux-saga. Thinking about thunks is not always straightforward. Not for junior developers.

Realisation

Eventually, your redux-based application grew so much and you’re looking on each reducer, how much of junk code is there. So many switch-cases that started to be unreadable. Have you ever forgot to write breakafter some case? I did.

How many actions and action creators are there? You are telling yourself, do I really need to write all that code to just fetch freaking data from server? Or just simply set some variable globally? Is this all necessary?

Maybe you need all that code

Maybe your application needs that. I can’t generalise over each codebase on the earth. But… from my experience. People (coders) just want to update their store and get all the freaking components listening on the store to get updated. Developers want to avoid prop-drilling.

Context is here. Let’s rewrite to Context. Goodbye Redux

I saw a lot of articles after context was released.

  • “Why you should avoid using Redux and use React Context instead”
  • “Why you should use React Context”
  • “Use React Context whenever possible!” (joke)

Context is nice little and simple way of sending data down from Provider to a Consumer . Nothing more. This innocent sentence almost convinced even me to move my application to React Context.

Context pitfalls:

  • Every component listening on a specific Provider gets re-rendered when a Provider value changes. This leads to performance issues if there’re a lot of components consuming context.
  • With context, you send your functions down to the consumer within your values. You need to create these methods/functions in the provider. They’re redefined each time provider gets re-rendered (You set a new state from consumer). It’s not such a big problem, but it just isn’t the nicest solution either.
  • Sharing state between providers is difficult. What if you need to share 2 states between each other? A needs to read from B. B needs to read from A ?
  • You can end up with a big Provider. Big object, too many useStates, too many functions definitions which you send down to components. In comparison with actions creators, it’s a lot harder to split up these functions out to files, because they need to live in the React tree and consume useStatedata in their closure.

Some issues are easy to solve, but you need to think about them in advance and are not optimized by default. For instance, to prevent re-rendering issue you can look at different solutions provided here. Still, these solutions are not there by default in comparison with Redux. Let’s see how redux solved re-rendering issue.

const usersCount = useSelector(state => state.users.length)

If redux state changes, it runs all functions listening on changes. Now useSelector will force component to re-render only, and only if the returned value from this function is different from the previous one. This solution is elegant in the way, that it’s the default way of reading from state and in most cases don’t need further optimizations around it.

(In most cases. Because even if your values in a store haven’t changed, you can return object from the selector. This object is different on each call, so we would need to implement a custom equality checker which checks if the new value returned from the function is different from the previous one)

You can use Redux in a different way

If you want simplicity and just move your data down efficiently, this may help you and I save you from moving to Context. Because after all, Context may not solve your problem better.

A lot of people will disagree with me and my approach and probably will look like these mad people in the The Simpsons:

What’s the approach?

I have only one action in my redux application.

{
type: "SET_STATE",
payload
}

I actually didn’t like dispatching actions, so I created my custom store setter.

const setState = (payload) => store.dispatch({
type: "SET_STATE",
payload
})

What’s inside the payload and what’s inside the reducer?

Payload is a function or a state. Function which accepts old state as the argument and returns a new state. In typescript terms, it’d look like this:

type payload = AppState | ((prevState: AppState) => AppState)

I’ve got only one reducer:

If payload is a function, it executes the function and will push the current state as the first argument to the function. Then reducer returns result of the function as the new state and voila, we have our new state. However, if developer decides to set a new state immediately, he can do so.

How this works in practice

Consuming:

Setting state:

I strongly recommend to use 2 more things

  • Typescript — whenever you want to get or set state, typescript makes you dead sure you’re doing allowed things. This makes things much easier, because when you add, remove or rename attribute in your store, typescript will automatically inform you if you’re somewhere using old names or doing not allowed operations because of the change.
  • Immutability tool — You need to keep state immutable. If your state gets too complex, readability of your code with vanilla javascript will suffer. To keep things cleaner, I strongly recommend to use immer or immutability-helper, but of course you can use whatever you want. Just make sure your state is immutable

What to do if you already have old-complex-way-redux approach?

You still can use both approaches. You probably combined all your reducers with combineReducers in an object and then you created the store with them.

The main reducer looks quite similar like in my unusual approach with a little change:

Conclusion

If you or your team are super exhausted by redux, context won’t magically solves your issues. Maybe some of them, but it brings different challenges. So please, consider continue using redux. With a little change, you may achieve what you otherwise would achieve with context, but without rewriting your app and even better and efficiently. Thanks!

--

--