Javier Casas

A random walk through computer science

3 Common Beginner React/Redux mistakes

Learning Redux is one of the rites of passage that many frontend developers eventually have to go through. Even though Redux is easy, until it clicks into your mind, it can only be seen as excessive boilerplate for very little gain. Here is a small list of issues you may find on your journey to Redux:

Using Redux for absolutely everything

One of the common mistakes rookie developers make is pretending Redux is for handling absolutely all of the state of the application. Redux is for the global state of the application, not for each minor detail on every form of the application. Common symptoms:

  • Triggering a dispatch on every onChange.
  • Your login form retains the previous username and password, and you have to dispatch one (or several) actions in componentDidMount in order to clean it up.
  • App is slow because every minor operation triggers a re-render of everything because an action was dispatched.

Redux should be used as a way to intercommunicate different parts of the application, not to communicate a single part of the application with itself. If your application is small, it is likely that it is made of a single part, and therefore Redux should be overkill. When using Redux, you should think in terms of major operations and events that happen, and how that affects the global state. Minor operations that eventually lead to major operations should be handled with internal state local to the component. Going back to the login screen that retains the previous username and password:

This is a symptom because it means we are storing the current contents of the username/password input fields in Redux, therefore when we show again the form, the Redux store still has the previous username and password, and that's what we show. But, in order to have it stored, we probably need SET_USERNAME and SET_PASSWORD actions, whose only purpose is modifying a field in the store so that the re-render cycle picks it up and shows it. That's way too much complexity for having two input fields. Instead:

  • The login form should have the username/password as internal state. If we need to have a default username ("remember my username" feature), you do that by having the form accept a default/initial username prop, and send that from LocalStorage or something.
  • Clicking the submit button in the login form should dispatch an action {type: "LOGIN_FORM_SUBMITTED", username, password}, and this action should trigger effects, sagas, thunks or whatever is required.

With this change, we remove a lot of action/reducer complexity, and move the state to the form where it is more cohesive, and at the same time we focus on using Redux for the big things, such as "the user submitted the form with all the fields passing initial validation".

Not extracting only fields used from the store

When you are working on your React/Redux application, it's often the case that you connect your component (or useState) to extract the whole store and pass it as a prop. This is wrong on three levels:

  • It couples your component to the store, even before connecting it. By making your component receive a store, your component now has to know that it runs under a specific Redux store, and has to know about the fields and sub-fields that it needs to read. The TopBar component that shows your username now has to know that the username is stored in the field store.user.name of the global Redux store.
  • It defeats the changed-props detection of React. Now every change in the store will cause the component to be re-rendered, especially those changes that are unrelated to the component, and then your React app is slow as hell.
  • It fails because the global store in Redux is not reconstructed on every action dispatched, thus causing you to be confused because components are not being updated when the state is changing (mutating, but you are not aware of that).

Your component should receive props in the simplest format possible. In the TopBar component above, the props should have a field username, another field avatarUrl and any other fields out of simple types, instead of a field state that receives a store. Now, in the connect call you use a mapStateToProps that constructs a props {username, avatarUrl} out of the store by reaching to the corresponding fields. And now your component is not coupled to the store (the connect call does the adaptation) and your component only re-renders when the corresponding fields in the store change. Also, it's way more obvious how your component should be used because it receives an username and an avatarUrl, which are way more descriptive than receiving a store that contains effectively the whole application.

Executing side-effects in actions, reducers or connect calls, instead of in thunks/sagas/effects

When you start working with Redux, it's hard to know where to put side effects. Where do I call the backend? Where do I store something in LocalStorage? The documentation doesn't point you to understand that you need something to handle side effects. So you start putting these things in actions or reducers. This causes several issues:

  • You learn that you can put anything anywhere in JavaScript, thus opening a can of worms. You then put more side effects all over the place, thus turning your app into bolognese spaghetti.
  • Testing your app becomes significantly harder. You cannot test actions and reducers anymore because they trigger other actions that you have to deal with. Therefore, you stop testing your app, and push a little lower your quality.
  • You start to leverage accidental details of Redux, such as the fact that the connect elements get called when the component is mounted (and then on every change), and now your system depends on React/Redux working in a very specific way. Now this way of working may change with the next React/Redux release, and you are angry at the system not working the way you want.

You have to invest early on understanding a way for implementing side effects. The most basic can be running the effects from the component itself, and then dispatching actions as different steps of the effect happens, and then you can upgrade to thunks or sagas. If you need to run stuff in Redux when the application loads, that's an excellent case for dispatching actions in componentDidMount. Whatever you do, don't run effects in actions, reducers or connect calls.

Back to index
Copyright © 2018-2023 Javier Casas - All rights reserved.