You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This project serves as a guide to structure Redux for a react app. The goal is to setup Redux in such way that it will cover most (over 90%) of your api needs.
3
+
This project serves as a guide to structure Redux for a react app.
4
4
5
-
> #### _Like this guide?_**Show your support by giving a :star:**
5
+
**The Problem:** Setting up Redux to work for a React app can be quite challenging and quickly result into a lot of boilerplate being repeated.
6
+
7
+
**The Aim:** The aim of this project is to setup Redux in such way that it will reduce the boilerplate to a minimum and cover most (over 90%) of our api needs.
6
8
7
-
**Note:** This code is nearly complete (See [Coming soon](#coming-soon)). It is functional and can be used as is or serve as inspiration. Some actions (delete, create) for multiple entities might not work as expected yet.
9
+
> #### _Like this guide?_**Show your support by giving a :star:**
8
10
9
11
---
10
12
11
-
## Table of Contents
13
+
## Docs
14
+
-[Understanding the Guide](#understanding)
12
15
-[Setup](#setup)
13
16
-[Actions](#actions)
14
17
-[Middlewares](#middlewares)
15
18
-[Reducers](#reducers)
16
19
-[Selectors](#selectors)
17
20
-[react-redux](#react-redux)
21
+
-[Production ready](#production)
18
22
-[Coming soon](#coming-soon)
23
+
-[Help](#help)
19
24
20
25
---
21
26
27
+
## Understanding the Guide
28
+
29
+
There is a Medium article explaining the core concepts of the setup, which you can find [`here`](https://medium.com/@onoufriosm/state-management-with-redux-50f3ec10c10a). See the end of the article for a video of my presentation at the React London meetup on these concept or follow the link [`here`](https://www.youtube.com/watch?time_continue=3231&v=yElOj4R4rdA).
30
+
31
+
I advise you to read the article before diving into the code.
32
+
33
+
You can also run `yarn start` to run a demo application using this code. This relies on some mock api calls found in `src/index.js`, therefore it will return predetermined data and it won't behave as a real world application. Nevertheless, it would be very useful to check the redux devtools to see how the store is structure and how it gets updated in response to different actions.
34
+
35
+
Finally, you can check the tests under `src/redux/__tests__` to understand how the action->middleware->reducer + selector combination works.
36
+
37
+
22
38
## Setup
23
39
24
-
All action creators, reducers and selectors will receive an entityName argument which will be any of the entities type we have in our application (e.g. user, post, comment e.t.c). This means that all of our code is generic and that we only need to write it once and then it will work for any entity in the system without extra boilerplate.
40
+
Quick summary:
41
+
1. Dispatch a `REQUEST` action.
42
+
2. Make the api call in the api middleware.
43
+
3. Normalize response in the normalize middleware.
44
+
4. Store payload in `byId` reducer + update status of api call in one of the other reducers.
45
+
+ Access the actions and the stored payload using a Higher Order Component (connect react with redux).
25
46
47
+
All action creators, reducers and selectors will receive an entityName argument which will be any of the entities type we have in our application (e.g. user, post, comment e.t.c). This means that all of our code is generic and that we only need to write it once and then it will work for any entity in the system without extra boilerplate.
26
48
27
49
## Actions
28
50
29
51
All action creators live under `src/redux/actions`
30
52
53
+
All actions return 4 fields:
54
+
1.`type`. The type of the action (e.g. `REQUEST_READ_USER`)
55
+
2.`params`. These are parameters that will be used by the api service to compute the api endpoint.
56
+
3.`meta`. Meta data to be used by the reducers and the normalizer middleware.
57
+
4.`options`. Extra options. Typically these can include `onSuccess` and `onFail` functions to be called when the api call is done.
58
+
31
59
There are action creators for:
32
60
1. Reading a single entity (e.g. GET /user/1)
33
61
2. Reading multiple entities (e.g. GET /user)
34
62
3. Updating a single entity (e.g. PUT /user/1)
35
63
4. Updating multiple entities (e.g. PUT /user/1,2). This will probably be different in some projects so you can adjust accordingly.
36
64
5. Deleting a single entity (e.g. DELETE /user/1)
37
-
6. Create a single entity (e.g. POST /user)
38
-
7. Add an entity to another in a many to many relationship (e.g. POST /post/1/tag/1)
39
-
7. Remove an entity to another in a many to many relationship (e.g. DELETE /post/1/tag/1)
8. Add an entity to another in a many to many relationship (e.g. POST /post/1/tag/1)
68
+
9. Add multiple entities to another in a many to many relationship (e.g. POST /post/1/tag/1,2)
69
+
10. Remove an entity from another in a many to many relationship (e.g. DELETE /post/1/tag/1)
70
+
11. Remove multiple entities from another in a many to many relationship (e.g. DELETE /post/1/tag/1,2)
40
71
41
72
[⇧ back to top](#table-of-contents)
42
73
43
74
## Middlewares
44
75
45
-
All middlewares live under `src/redux/middlewares`.There are two middlewares:
76
+
All middlewares live under `src/redux/middlewares`.
77
+
78
+
All actions will pass by the middlewares. There are two middlewares:
46
79
47
-
1. Api middleware. This is responsible for doing the api call and responding with success/fail action depending on the type of repsonse
48
-
2. Normalize middleware. This will normalize the payload using the [`normalizr`](https://github.com/paularmstrong/normalizr) library.
80
+
1. Api middleware. This is responsible for doing the api call (depending on the action type) and responding with success/fail action depending on the type of repsonse
81
+
2. Normalize middleware. This will normalize the payload using the [`normalizr`](https://github.com/paularmstrong/normalizr) library and the schema provided by us.
49
82
50
83
[⇧ back to top](#table-of-contents)
51
84
@@ -54,12 +87,71 @@ All middlewares live under `src/redux/middlewares`.There are two middlewares:
54
87
All reducers live under `src/redux/reducers`. There are 6 subreducers for every entity.
55
88
56
89
1.`byId`. All the normalized data will be stored here.
90
+
- On `SUCCESS_CREATE` the id of the created entity(ies) will be added to the parent entity.
91
+
- On `SUCCESS_DELETE` the id of the deleted entity(ies) will be removed from the parent entity.
92
+
- Same for `SUCCESS_REMOVE`, `SUCCESS_ADD`, `SUCCESS_SET` for many to many relationships.
57
93
2.`readIds`. Information about the status of all read calls will be stored here.
94
+
- On `SUCCESS_CREATE` the id of the created entity(ies) will be added to the relevant readId.
95
+
- On `SUCCESS_DELETE` the id of the deleted entity(ies) will be removed from the relevant readId.
58
96
3.`updateIds`. Information about the status of all update calls will be stored here.
59
97
4.`createIds`. Information about the status of all create calls will be stored here.
60
98
5.`deleteIds`. Information about the status of all delete calls will be stored here.
61
99
6.`toggleIds`. Information about the status of all toggle calls will be stored here. Toggle refers to remove/add one entity to another in a many to many relationship.
62
100
101
+
Since the data is stored in a normalized it becomes very easy to update relational data. Consider the following example where the initial state:
102
+
```
103
+
{
104
+
entities: {
105
+
user: {
106
+
1: {
107
+
id: 1,
108
+
posts: [1,2],
109
+
}
110
+
}
111
+
}
112
+
}
113
+
```
114
+
115
+
If we create a post (it will receive the id 3) then in the `byId` reducer we can add the id to the `posts` array under the parent entity (in this case user). The new state will become:
116
+
117
+
```
118
+
{
119
+
entities: {
120
+
user: {
121
+
1: {
122
+
id: 1,
123
+
posts: [1,2. 3],
124
+
}
125
+
}
126
+
}
127
+
}
128
+
```
129
+
130
+
Note that there are two ways to retrieve the posts for a user. We could either load the user and return posts as nested data from our backend, which would lead to the initial state above. Or we might want to return the posts for a specific user_id (Usually the case when we paginate data). In this case the initial state would look like this:
131
+
132
+
```
133
+
{
134
+
entities: {
135
+
post: {
136
+
'{"user_id":1}': { items: [1,2] },
137
+
}
138
+
}
139
+
}
140
+
```
141
+
142
+
And the updated state:
143
+
```
144
+
{
145
+
entities: {
146
+
post: {
147
+
'{"user_id":1}': { items: [1,2, 3] },
148
+
}
149
+
}
150
+
}
151
+
```
152
+
153
+
All these are handle automatically and for all entities, so we don't have to worry about updating relationships anymore.
154
+
63
155
[⇧ back to top](#table-of-contents)
64
156
65
157
## Selectors
@@ -70,7 +162,7 @@ All selectors live under `src/redux/selectors`. The selectors will select either
70
162
71
163
## react-redux
72
164
73
-
All logic for connecting redux and react components live under `src/react-redux`. The mapDispatchToProps and mapStateToProps is moved in to higher order components so that we don't need to redeclare them in every component. You can see how these HOC are use in the example in `src/components`.
165
+
All logic for connecting redux and react components live under `src/react-redux`. The mapDispatchToProps and mapStateToProps is moved in to higher order components so that we don't need to redeclare them in every component. You can see how these HOC are used in the example in `src/components`.
74
166
75
167
Example to read a single entity:
76
168
```
@@ -83,19 +175,28 @@ Example to read a single entity:
83
175
2. Pass the entityName and id props to the HOC.
84
176
3. You get access to the `read` action creator, the `entity` (user) that will be returned from the api call, and `status` (isFetching, error).
85
177
178
+
See `src/components/Main/index.js` for the full example.
179
+
180
+
[⇧ back to top](#table-of-contents)
181
+
182
+
## Production ready
183
+
184
+
This setup is the basis for the Redux setup at [`Labstep`](https://www.labstep.com/). It is used in production and has accelerated the development drastically.
185
+
86
186
[⇧ back to top](#table-of-contents)
87
187
88
188
## Coming soon
89
189
90
190
TODO:
91
191
92
-
1. Fix + make uniform create, delete, toggle for multiple entities
93
-
2. Finish writing unit tests
94
-
3. Write examples for cursor/page based read
95
-
4. Add caching
96
-
5. Add optimistic updates
97
-
6. Allow for rxjs/saga replacement
98
-
7. Finish writing documentation
99
-
8. Publish to npm (I plan to turn this into a package that everyone can use )
192
+
1. Add examples for cursor/page based read
193
+
2. Add example for caching / optimistic updates
194
+
3. Publish to npm (I plan to turn this into a package that everyone can use )
195
+
196
+
[⇧ back to top](#table-of-contents)
197
+
198
+
## Help
199
+
200
+
Feel free to open an issue asking for help. I'll do my best to reply promptly.
0 commit comments