My Awesome React Redux Structure PDF
My Awesome React Redux Structure PDF
If you don’t care for Classes, then check out the other source code examples:
• React/Redux (TypeScript — classes)
. . .
Overview
This article will cover the following topics:
• Views (simplified)
• Stores (Redux)
Application Workflow
I’ve worked on many applications throughout my career. From ActionScript to
the most modern JavaScript frameworks and libraries. I’ve learned a lot along
the way.
What follows are some patterns, strategies, and opinions I’ve developed along
the way.
• Effect handles API calls and sanitizes incoming data or returns an error.
• Reducer receives the models or an error from the Action and adds them to
the store.
• Selector takes the Reducers data and creates specific data for the Views .
• View gets data straight from the store or a Selector and just displays it.
(The rest is internal communication and state management)
. . .
Folder Structure
I really like the below folder structure and it’s great for any size project.
React/Redux Folder Structure
environments.
• stores :
. . .
Views
Views should be simple and free from logic that transforms data.
All your views need to do is display data and manage themselves. Other parts of
your application can do the heavy lifting.
One thing I strive for is to “make your code read like a book”. It’s nice to start at
the top, reading left to right of a file to understand what the code is doing.
Starting at the top like a book, you can see what data the view will be using with
1 const mapStateToProps = (state, ownProps) => ({
mapStateToProps . Then, looking at componentDidMount , you will see that the view
2 someData: selectSomething(state),
will
3 dispatch a SomeAction.requestSomething
otherData: state.otherReducer.other, action.
4 });
After
5 that, we come to the important render method code and, finally, any
helper
6
methods like _onClick are placed towards the bottom of the file leaving
class SomePage extends React.Component {
7
the important code at the top of the file.
8 componentDidMount() {
9 this.props.dispatch(SomeAction.requestSomething());
Parent
10 components
} stop doing everything for your children. Let your child
components
11 get their own data and make their own action calls.
12 render() {
As13long asconst
the {components are not} going
someData, otherData to be re-used in other components, I
= this.props;
14
prefer to connect components rather than having the parent get the data and
15 return (
pass
16 in props.
<div className={styles.wrapper}>
17 <h1>{someData.header}</h1>
If18you think of components as Lego blocks, it is much easier to move them
<main>{someData.description}</main>
19
around if the<button
parentonClick={this._onClick}>{otherData.buttonLabel}</button>
is not passing data or managing callbacks for the child.
20 </div>
21 );
Looking at the component below, we can easily move <Actors /> to another
22 }
component
23 without changing any logic in the HomePage .
24 _onClick = (event) => {
25 console.log(`I was clicked`);
26class
} HomePage extends React.Component {
27 render() {
28 } return (
29
<div className={styles.wrapper}>
<MainOverview />
<Actors />
</div>
);
}
}
One thing you can do to make you child components more independent is to use
Selectors to create data that is specific to the view’s need and limiting the
amount of logic code in your views.
. . .
Stores (Redux)
In Redux, the store is a single object that keeps track of the current state of the
application. You use reducers to manage sections of the overall store .
┣━ src/
┃ ...
┃ ┗━ stores/
┃ ┣━ shows/
┃ ┃ ┣━ models/
┃ ┃ ┣━ ShowsAction.ts
┃ ┃ ┣━ ShowsEffect.ts
┃ ┃ ┗━ ShowsReducer.ts
┃ ┣━ episodes/
┃ ┃ ┣━ models/
┃ ┃ ┣━ EpisodesAction.ts
┃ ┃ ┣━ EpisodesEffect.ts
┃ ┃ ┗━ EpisodesReducer.ts
┃ ┣━ rootReducers.ts
┃ ┗━ rootStore.ts
┃ ...
I like doing it this way because, if a feature of that app gets removed, I can
remove a single folder rather than going to top-level folders (actions, reducers,
etc.) and remove files individually.
. . .
Actions
Actions are the core communication within your application and provide a way
to trigger side-effects and pass data to your reducers.
1 {
2 type: 'SomeAction.ACTION_NAME', // Required
3 payload: 'anything', // Optional
4 error: false, // Optional
5 meta: null, // Optional
6 }
• payload (optional): The data you want to send with the action and the
property may be any type of value.
• error (optional): If set to true , the action represents an error and the
• meta (optional): Intended for any extra information that is not part of the
payload and the property may be any type of value. For example: an “ID”
that may not be included in the payload itself.
An action must not include properties other than type , payload , error , and
this.props.dispatch(SomeAction.normalAction());
With the example below, it is not clear which store section of action it belongs to
and the code doesn’t read like a book:
this.props.dispatch(normalAction());
Also, when using it in the reducer, it is clear which action and action group are
used:
switch (action.type) {
case SomeAction.NORMAL_ACTION:
return {
...state,
someProperty: action.payload,
};
default:
return state;
}
default values:
1 export default class SomeAction {
2
13 export default
static class ActionUtility
NORMAL_ACTION {
= 'SomeAction.NORMAL_ACTION';
24 static THUNK_ACTION = 'SomeAction.THUNK_ACTION';
35 static createAction(type, payload = undefined, error = false, meta = null) {
46 returnnormalAction()
static { type, payload,
{ error, meta };
57 } return {
68 type: SomeAction.NORMAL_ACTION,
79 } }
10 }
ActionUtility.createAction.js hosted with by GitHub view raw
11
12 static thunkAction() {
Let’s update the normalAction method to use ActionUtility.createAction .
13 return async (dispatch, getState) => {
14 const model = await SomeEffect.requestIt();
1
15 // Before
2
16 staticdispatch({
normalAction() {
3
17 returntype:
{ SomeAction.THUNK_ACTION,
4
18 type: SomeAction.NORMAL_ACTION,
payload: model
5
19 } })
6
20 } };
7
21 }
8
22 // After
9
23 static
} normalAction() {
10 return ActionUtility.createAction(SomeAction.NORMAL_ACTION);
11 }
SomeAction.REQUEST_SOMETHING
Then, with an action that starts with a REQUEST_ and ends with _FINISHED , the
SomeAction.REQUEST_SOMETHING_FINISHED
This way, you can create logic to determine if the loading state is either true or
false for an action. As I am using the flux standard action, mentioned above,
we can also set the error property to true/false .
This allows us to show or remove an error message when the action has finished
or restarted. You can see this implemented in the sample application.
action and then, once I get the data back from the request, I dispatch the
finished action.
Also notice how I set the error state. My ShowsEffect will either return a valid
model or a HttpErrorResponseModel .
Now we can use this convention/pattern for all our request actions. One thing
we can do is abstract some of this boilerplate code to a utility. Let’s create
another method on the ActionUtility called createThunkEffect .
Let’s refactor the requestShow action method with the
ActionUtility.createThunkEffect .
Check out the full ShowsAction below from the corresponding sample code:
. . .
Let’s take a look at a snippet below of the ShowsEffect . Notice that there is an
endpoint, it makes a GET request and from the response it either returns
successful data from the API or a HttpErrorResponseModel .
Effects also should sanitize your data and that is what the ShowModel is doing.
Check out my article Protect Your JavaScript Applications from Api Data to learn
more.
Once again, let’s refactor and minimize the amount of boilerplate code we need
to write.
I’ve created an EffectUtility class that contains a getToModel method. The
method makes a GET request and creates a new model with the response data.
Don’t modify API data for a specific view. Pass it straight to the reducer and let
Selectors transform the API data for views.
I’ve experienced the chaos when API data has been changed for a specific view,
only to realize a few months later that the same API data is going to be used for a
completely different view.
Do yourself a favor and keep a one-to-one mapping of what the API gives you so
it is easier for everyone to view data from the original structure.
You might have noticed HttpUtility in some of the examples. I am not going to
go over it but behind the scenes it uses axios.
. . .
Reducers
Reducers are smaller sections of code that manage the global store by adding,
removing, or editing data via actions.
The reducers are simple, they listen for actions to add, remove or edit data in a
section of the store.
There is an ErrorReducer that will handle errors and you can read my article
React/Redux API Loading & Errors.
1 export default class ShowsReducer {
2 static initialState = {
3 currentShowId: '74',
Let’s
1 make
export the above
default reducer
class cleaner
ActionUtility { and faster. We are going to use inheritance
4
2 show: null,
where
5
weepisodes:
abstract[],
some of the logic and concentrate on what’s necessary.
3 static async createThunkEffect(dispatch, actionType, effect, ...args) {
6
4 actors: [],
dispatch(ActionUtility.createAction(actionType));
Below,
7
5
notice
}; how ShowsReducer extends BaseReducer . This allows us to move
the86 constmethod
reducer model = in theeffect(...args);
await above example to its own file ( BaseReducer ) so that
9 static reducer(state = ShowsReducer.initialState, action) {
not7 all reducer files have
const isError to include
= model it.HttpErrorResponseModel;
instanceof
10
8 if (action.error) {
11
9 return state;
dispatch(ActionUtility.createAction(`${actionType}_FINISHED`, model, isError));
One
12
important
}
difference from the above is that I have removed the static
10
prefix,
13
11 making
return it a normal class so it can extend the
model; BaseReducer . Notice how we
use
12 the }action
14 switch constant
(action.type)
type { as the method name:
15
13 case ShowsAction.REQUEST_SHOW_FINISHED:
16
14 staticreturn {
createAction(type, payload, error = false, meta = null) {
[ShowsAction.REQUEST_SHOW_FINISHED](state, action){}
17
15 return...state,
{ type, payload, error, meta };
18
16 } show: action.payload,
19
17 };
20
18 } case ShowsAction.REQUEST_EPISODES_FINISHED:
21 return {
22 ...state,
23 episodes: action.payload,
If you look at the above BaseReducer , you will see the reducer method.
• Line 5 : Gets access to the class method that matches the action.type .
rootReducer
7 const isError = model instanceof HttpErrorResponseModel;
8
Below
9
is the file that uses Redux’s combineReducers and on line 12 you can see
dispatch(ActionUtility.createAction(ShowsAction.REQUEST_SHOW, model, isError));
how
10 to set
}; up/add reducers that use BaseReducer .
11 }
12
1 import { combineReducers } from 'redux';
13 // After
2 import { connectRouter } from 'connected-react-router';
14 static requestShow() {
3 import RequestingReducer from './requesting/RequestingReducer';
15 return async (dispatch, getState) => {
4 import ErrorReducer from './error/ErrorReducer';
16 await ActionUtility.createThunkEffect(dispatch, ShowsAction.REQUEST_SHOW, ShowsEffect
5 import ShowsReducer from './shows/ShowsReducer';
17 };
6
18 }
7 export default (history) => {
8 const reducerMap = {
9 error: ErrorReducer.reducer, // static reducer version
10 requesting: RequestingReducer.reducer, // static reducer version
11 router: connectRouter(history),
12 shows: new ShowsReducer().reducer, // reducers that extends BaseReducer
13 };
1
14 export default class ShowsReducer extends BaseReducer {
2
15 initialState = {
return combineReducers(reducerMap);
3
16 }; currentShowId: '74',
4 show: null,
5 episodes: [],
I try
6 to keep my [],
actors: reducers simple and leave the heavy code lifting to Selectors .
7 };
If you
8 are going to need to do some extra logic in your reducers, create a helper
9 [ShowsAction.REQUEST_SHOW_FINISHED](state, action) {
method like ( _complicatedToSimple ) below, or abstract it to a utility file where it
10 return {
can
11 be reused.
...state,
12 show: action.payload,
13 }
14 }
. . .
Selectors are where you put most of your logic to generate different data
objects for your views.
If you look at the code below, I am using a static class to organize my logic and at
the very bottom, I have a Reselect createSelector .
I like separating the two so I can clearly see which reducers createSelector
cares about and having the static class just care about what it needs to do to the
data.
If there is one thing I want to stress, it is to always try to do most of your logic in
Selectors and the rest of your app is just passing and displaying data.
Thanks for reading. Leave me a comment if you see something you liked.
I’ve covered a lot in this article but if you want to go into more details check out
my related articles: