DEV Community

loading...
Cover image for Small update on Redux usage

Small update on Redux usage

Lex Swed
Software Engineer in love with UI Engineering
惻Updated on 惻4 min read

So the first Redux version was developed somewhere around 5 years from now and a lot has changed since then.

redux-first-releast

Not only React improved the way our code works for the end-users but also we, as developers, changed the way we use React. Best practices come and go, leaving us with the code that other, new developers, might not understand the purpose of. Remember that PureComponent thing? That it was added in the time where everyone was obsessed with React performance before even having any issues with that? Or smart and dumb components?

Why those practices change is another topic, I guess more related to the way software products are developed rather than tied to React or Front End. So here I just wanted to share a few best practices I believe made Redux usage better over the years.

Folder structure

So far I've mostly seen a separation of files based on their purpose:

redux folders separated to actions/reducers/selectors

Then Redux Ducks came into play, making people to separate files and folders by their features or model they work with. This is a great approach suggested by the Redux Style Guide

Redux Feature Folders structure

Notice that files were separated only when the feature became too big to manage in one file. First, actions.ts were separated from index.ts file. Then came others, making the index to export createReducer only, with imported reducer and initial state. This enables to dispatch specific actions from the specific feature. Why this approach works well? I will agree with Monica Lent that constraints make software easier to maintain. In this case, we constrain code to their features.

But it never works like that in real life! Domain models are interconnected! Actions to one module can cause changes in another! That's real life!

Alright, that's correct. So how about...

Event-driven reducers

Event-driven reducers mean that instead of making action type names to describe what you want to change in the store, you describe what just happened. E.g. instead of ADD_ITEM you say ITEM_ADDED and bum your reducer now reacts to the event, that item was added to the basket. Then it means that if you want some other reducer to change the state based on this action, you just add to that another reducer one more switch clause. If you want to know more, check out this talk by Yazan Alaboudi.

And small example from Redux Style Guide

Compare this two:

{ type: "food/orderAdded",  payload: {pizza: 1, coke: 1} }
Enter fullscreen mode Exit fullscreen mode

with

{
    type: "orders/setPizzasOrdered",
    payload: {
        amount: getState().orders.pizza + 1,
    }
}

{
    type: "orders/setCokesOrdered",
    payload: {
        amount: getState().orders.coke + 1,
    }
}
Enter fullscreen mode Exit fullscreen mode

With more complicated examples it gets even more beneficial, not only on the code sources side, but on the cognitive load as well, perceiving your reducers not as "for this action -> update this field", but rather "when this happens -> this updates".

It's all interconnected šŸ”

So you probably don't need const ADD_ITEM_TO_THE_BASKET = 'ADD_ITEM_TO_THE_BASKET';. If your store is strictly separated by domain models or features and not by your UI code, you can separate events that occur in your system by such features. So your events can look like

{ type: 'food/orderAdded', payload: { /* order info */ }
{ type: 'posts/clapped', payload: { times: 11 } }
Enter fullscreen mode Exit fullscreen mode

This way your actions are safe. Logic changes, how we handle events can change as well, but events doesn't. Or at least not that often. You can read about it there.

Second, you can easily react to the event from multiple places in your store. So instead of having:

dispatch({ type: 'ADD_CLAP' });
dispatch({ type: 'SET_COMMENTS_AVAILABLE', payload: true });
Enter fullscreen mode Exit fullscreen mode

you can have multiple reducers reacting to the same event:

dispatch({ type: 'posts/clapped', payload: { /* item */ });

// in your reducers

// posts.js
  case 'posts/clapped':
    return { ...state, claps: state.claps + 1 };

// comments.js
  case 'posts/clapped':
    return { ...state, commentsAvailable: true }; 

Enter fullscreen mode Exit fullscreen mode

And so we're moving our logic to reducers, instead of making calculations in components' code, redux-thunks, or redux-sagas. We know the logic belongs to the reducer, so we move it there. You can read more about it there.

So as you can see, best practices, which enable us to write maintainable Redux store are all interconnected: with one thing you can easier do another, without sacrificing on anything. This post wasn't intended to be "look what I found", or "one more best practice in Redux". These are just simple and short rules you can incorporate (you probably did already, partially, unconsciously, because it's a natural approach) to your Redux store architecture. And there are more of these already described in Redux Style Guide, concluded by developers to make your life easier. Please, take a quick look, maybe you can find something you have question about to ask here!

Be good, smile and tell jokes to each other!

Cover image reference: React Redux Tutorial for Beginners: Simply Explained

Discussion (1)

Collapse
markerikson profile image
Mark Erikson

Glad to see that the Style Guide page I wrote is proving useful!

Also, be sure to check out our new official Redux Toolkit package. It includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once:

redux-toolkit.js.org