After the First Taste of Redux I worked through the Building React Applications with Idiomatic Redux course. Here are my notes and source code (branch per lesson) for it. Here is also my React Countries app source code, refactored with new knowledge. You can see how it looks here.
Now we have a fake API simulating a backend with all the operations and data encapsulated there. Fetching countries, adding, removing, updating, it’s all there. All methods return Promises and have a delay (500ms) so that it feels like a real backend. In user countries fetching logic, I’ve introduced a probability of 50% to get an error, to simulate error handling.
I’ve introduced colocating selectors in reducers. These are just methods, usually simple getters, for getting some part of the state. Each reducer is responsible for providing its part of the state, so you will frequently see delegating between reducers. So now, components don’t have to know anything about the state shape, they just call selectors providing the state and get what they need. With this approach, we can refactor our reducers and state shape without modifying components at all.
Normalizing the State Shape
It’s important to normalize the state shape, so that part of it looks more like a database. Read this article for more information. I am currently not using Normalizr, but it’s a good idea when you have a normalized state and data from the server is complex/nested. This tool helps you normalize data from the server so that you can apply it directly to your state.
By default, when you dispatch an action, it’s a simple object with type and other information, and its nature is synchronous. It immediately reaches reducers. But what if you want to plug in some logic in between so that you can do something after action dispatching and before reaching reducers? Middleware! When you create a Redux store, you can pass an array of middlewares to be applied. For example, I am using the redux-logger, redux-promise, and redux-thunk middlewares in my app. The logger middleware is a cross-cutting thing – on every action, it logs the state before, the action, and the state after in the development environment, so that’s really convenient for debugging. If you want to dispatch some action in an async manner so that you can reach the reducer once the promise is resolved – there is no way to do it by default. Fortunately, you can use the promise middleware that will enhance Redux to support promises. Thus, you can dispatch a Promise resolving a simple action object, and the Promise middleware will handle the rest. Even more powerful is Thunk middleware. In this case, you dispatch a function that receives the
getState functions as parameters. This way, you can invoke dispatch multiple times in the action, sync/async, and also do it conditionally since you have access to the state object, so you can receive anything from the state, without passing the data around. An alternative to Thunk is the Saga middleware, which seems to be more powerful, but probably more difficult to understand/learn since it uses ES6 Generators.
Updating the State
In an earlier version of my app, I was fetching all user’s countries and then did filtering on the UI. But this doesn’t scale well because if we have a lot of data, we simply can’t fetch it all to the client. In this version of the app, I am fetching only what has to be displayed on the screen and keep that in the state. If you toggle the country visited filter in the toolbar, the app will either fetch/keep all user’s countries or only visited ones. Another important thing – when we add/delete/update the country we now call the fake API, and we update the UI only after we get the response from the server because that means that operation was successful, we have return data, and now we can update the state.
In the previous article, I didn’t know how to handle the case of displaying the success message when some async operation is done. This is now easy with Thunk and the API. In the action, we simply call the async operation on the API, and when it resolves, we dispatch another action to update the UI – show the message.
It’s important to handle error messages gracefully. For example, when we call the API, sometimes we can get errors. We can use the error handler in the Promise to dispatch the failure action; then reducers can receive that to maintain error messages, etc.
OK, so let’s take a look at the big picture. In the React-Redux app, we have many different small pieces that work together. It sometimes feels that we have unnecessarily many files and code, but I somehow like the fact that modules are small and doing their part of the job.
- We have presentational components that are dumb and concerned only with UI rendering. It’s perfectly fine to have some UI logic here like checking if an input field is not empty before calling some method, etc.
- Container components provide data and behavior for presentational components, and they invoke actions and selectors from reducers.
- Actions – the purpose of action dispatching is to express a desire to do something (update something, delete something, show a message, etc.). Using Thunks, we can dispatch actions in an (a)sync way. Actions can call the API and, typically, dispatch simple action objects to update the state when the operation is completed and we have the result. We can also call reducer selectors from actions.
- Reducers – their only purpose is to update the current state from the data they receive from actions. They will handle things like immutability.
- API – we have an interface to the backend(s)
There are so many more things to learn, like React Router, testing, Saga, etc., but the general approach is already solid knowledge, and it’s time to enter the real world and face new React-Redux challenges! 😀