After First Taste of Redux I did Building React Applications with Idiomatic Redux course. You can find notes and source code (branch per lesson) for it. Here is my React Countries app source code, refactored with new knowledge. You can see how it looks.
Now we have fake API simulating backend with all operations and data encapsulated there. Fetching countries, adding, removing, updating, it’s all there. All methods return Promises and have delay (500ms), so that it feels like real backend. In user countries fetching logic I’ve introduced probability of 50% to get an error, to simulate error handling.
I’ve introduced, so called, 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 good idea when you have normalized state and data from the server are complex/nested. Tool helps you to 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 Redux store, you can pass an array of middlewares to be applied. For example, I am using redux-logger, redux-promise and redux-thunk middlewares in my app. Logger middleware is cross-cutting thing – on every action it logs the state before, action and the state after, in development environment, so that’s really convenient for debugging. If you want to dispatch some action in async manner, so that you can reach reducer once promise is resolved – there is no way to do it by default. Fortunately, you can use promise middleware that will enhance Redux to support promises. Like that, you can dispatch a Promise resolving a simple action object. Promise middleware will handle the rest. Even more powerful is Thunk middleware, in this case you dispatch a function that receives dispatch and 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. Alternative to Thunk is Saga middleware which seems to be more powerful, but probably more difficult to understand/learn since it uses ES6 Generators.
Updating the State
In 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 country visited filter in the toolbar, app will either fetch/keep all user’s countries or only visited ones. Other important thing – when we add/delete/update the country we now call fake API and we want to 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 success message when some async operation is done. This is now easy with Thunk and API. In the action we simply call async operation on API and when it resolves we dispatch another action to update the UI – show the message.
It’s important to gracefully handle error messages. For example, when we call API, sometimes we can get errors. We can use error handler in the Promise to dispatch failure action, then reducers can receive that to maintain error messages, etc.
OK, so let’s take a look at the big picture. In React-Redux app we have many different small pieces that work together. It sometimes feels that we have unnecessary too 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 about UI rendering. It’s perfectly fine to have some UI logic here like checking if input field is not empty before calling some method, etc.
- Container components provide data and behaviour for presentational component and they invoke actions and selectors from reducers.
- Actions – purpose of action dispatching is to express desire to do something (update something, delete something, show message, etc). Using Thunks we can dispatch actions in (a)sync way. Actions can call API and, typically, dispatch simple action objects to update the state when 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 interface to the backend(s)
There are so many things more to learn, like React Router, testing, Saga, etc, but it’s now already solid knowledge and it’s time to enter into real world application and face new React-Redux challenges! 😀