A Best Practice for Developing Small React Redux Apps for Newbies

This post describes a recommendation for beginners who develop small React/Redux apps. This is based on my experience, and it may not be very common at this point yet.

Background

React/Redux has got attention pretty much, even for beginner developers. Although my first perspective to React/Redux is that it is for large-scale apps or apps developed by a team, given a big ecosystem around React/Redux, it’s also now useful for a single developer to build a small app. I think there should be a simpler way of using React/Redux for a single developer or a small team, which could be different from the way for a team with many developers.

Simple conventions

Here’s a list of conventions I recommend for developing small React/Redux apps.

  1. No action creators
  2. No mapDispatchToProps
  3. No redux-thunk
  4. Simple combineReducers strategy

The following describes these in detail.

No action creators

Typically, you have two options to write logic in Redux; reducers and action creators. This is flexible and developers can design a system as they want. For beginners, there should be less flexibility. There seem to be two types of groups in the Redux community; one prefers action creator to reducers to write logic, and the other prefers reducers for it. I recommend to use reducers only, and do not use action creators at all. Reducers are required to be pure functions unlike action creators and this restriction of purity gives us more testability. If you put logic in actions creators, reducers become very thin like (state, action) => ({ ...state, ...(ation.type === 'foo' ? action.newState : {}) }), but this doesn’t seem to use the full power of Redux to me.

No mapDispatchToProps

In a typical React/Redux app, it’s supposed to use react-redux, especially connect, with mapStateToProps and mapDispatchToProps. Because we don’t use action creators, we simply use the raw dispatch which should be more straightforward. For example:

const Foo = ({ dispatch }) => (
<div>
<button onClick={() => dispatch({ type: 'COUNT_UP', value: 3 })}
Count up by 3
</button>
</div>
);
export default connect()(Foo);

This would be much easier to follow the behavior of actions, because we don’t have any action creators and logic is only in reducers.

Another discussion here is about the action type. Here, the bare string is used, but surely it can be defined somewhere in common and imported here.

If the action is not that simple, we can put it outside of the JSX:

const Foo = ({ dispatch }) => {
const countUp = () => dispatch({
type: 'COUNT_UP',
target: 'counter_A',
value: 3,
});
return (
<div>
<button onClick={countUp}>Count up by 3</button>
</div>
);
};

You can also separate it like an action creator, but I would put it in the same file:

const countUp = dispatch => value => () => dispatch({
type: 'COUNT_UP',
target: 'counter_A',
value,
});
const Foo = ({ dispatch }) => ({
<div>
<button onClick={countUp(dispatch)(3)}>
Count up by 3
</button>
</div>
});

We still need to use mapStateToProps in connect for performance reasons, but it should be straightforward as I describe later.

No redux-thunk

Redux reducers don’t allow to have side effects and async calls. Redux provide a mechanism to support middleware which should handle side effects and async calls. Redux-thunk and similar middleware is commonly used for async calls, but it’s too flexible for beginners. As we don’t use action creators, we should use more specific middlware for each purpose. One common use of async calls is API calls through the network. One of the redux middleware to support API calls is redux-api-middleware, like the following example.

import { RSAA } from 'redux-api-middleware';
const Bar = ({ dispatch }) => {
const fetchPrice = () => dispatch({
[RSAA]: {
endpoint: '/api/price',
method: 'GET',
types: ['REQUEST_API_PRICE', 'SUCCESS_API_PRICE', 'FAILURE_API_PRICE'],
},
});
return (
<div>
<button onClick={fetchPrice}>Get Price</button>
</div>
);
};

As for side effects and other impurity, we depend on middleware instead of action creators. For example, we can’t use Date.now() in reducers, hence I made tiny middleware for it: https://github.com/dai-shi/redux-date-now-middleware

Simple combineReducers strategy

In Redux, the way you split reducers and combine them by combineReducers is very flexible and it’s hard to design it nicely if you are a beginner. I’d suggest to think the other way round. Let’s make mapStateToProps simple and design reducers to make it so. Yes, I mean, don’t put logic in mapStateToProps, but it should just extract state that the component needs. The state to extract is only one level deep. In the end, the mapStateToProps should look like the following:

const MyCounter = ({ counters }) => (
<ul>
<li>Counter A: {counters.a}</li>
<li>Counter B: {counters.b}</li>
<li>Counter C: {counters.c}</li>
</ul>
);
const mapStateToProps = ({ counters }) => ({ counters });
export default connect(mapStateToProps)(MyCounter);

It might not be the most performant way to extract only one-level-deep state as-is, but it tends to avoid putting logic in it. Our target is a small app and usually the performance of this level is not so critical.

You could certainly map several parts of state to props:

const mapStateToProps = ({
partA,
partB,
partC,
}) => ({
partA,
partB,
partC,
});

Likewise, we split reducers as we want to connect it to components by mapStateToProps, and combine reducers:

import { combineReducers } from 'redux';
import partA from './partA';
import partB from './partB';
import partC from './partC';
import partD from './partD';
import partE from './partE';

export default combineReducers({
partA,
partB,
partC,
partD,
partE,
});

There should also be discussions about the way how to write reducers, but it’s out of the scope of this post. I’d like to write another post if I come up with a nice idea.

Summary

The whole conventions that I described in this post is based the idea to reduce the too much flexibility that React/Redux provides. In some cases, especially developing small apps, having too many separated *.js files is troublesome because you need to go back and forth to follow the code to understand its behavior. To this end, my suggestion is to put business logic only in reducers, eliminating action creators, using bare dispatch, leveraging Redux middleware, and simplifying mapStateToProps.

I wish to develop a library such that it forces or helps these conventions, but being not sure how they are acceptable, I wrote this post. So, feedbacks are welcome.