How to create a universal ReactJS application with Redux
Erstwhile in the adventures series
In the previous post we got to know Flux.
Full code of the application is accessible here.
We moved all the state modifications to stores, to have better control over the changes.
I’ve also mentioned that there is a mechanism for synchronising store updates. The truth is, though, in a complex application handling store dependencies that way can become messy.
In this post we will update our app to use another pattern, which evolved from Flux – Redux.
As I mentioned, handling store dependencies when you have many of them can be tricky. That’s why Flux architecture evolved introducing reducers.
A reducer is a pure function that takes a state and an action and returns a new state depending on the given action payload.
It’s good practice to return a new instance of a state every time, instead of modifying the old one. Such immutability increases performance during establishing the need to rerender. You can read a really good detailed explanation here.
The main flow looks very similar to the Flux one:
- every state change needs to be done by dispatching the action
- the store gets the payload and uses reducers to determine the new state
- the view (“smart component”) gets the new state and updates its local state accordingly
I recommend you to read more about reducers and Redux in general.
A thing worth emphasising is that there is only one store. You can, though, register as many reducers as you like while creating a store.
Let’s reduxify our app
You can now remove events and flux from the package.json.
And let’s add redux dependencies to our package.json by running: npm install –save redux react-redux redux-router redux-thunk.
We won’t need our dispatcher implementation anymore, as there is one already in the redux library. Let’s remove it then:
We can also remove SubmissionStore (and any other stores if you added them).
We are going to create one general store.
There will be one store class, but two instances – one for the client side and one for the server side:
There are couple of things going on here.
Firstly, we define middlewares we want to use in the store. We are composing them using compose method from the redux library.
I’ll say more about why we’d need any middleware later.
Secondly, we use the combineReducers method from the redux library to pass all reducers we need in our application to the store.
The question now is: what are reducers?
Reducers are responsible for the state change.
They get the action dispatched from the component and calculate the new state if needed.
The whole application state is then passed to the component which dispatched the action and the component can choose what part of the state it’s interested in. More about this later.
Now take a look at our reducers:
When this reducer gets the RECEIVE_SUBMISSIONS_LIST action, it will take all the submissions that came in the payload (action.submissions) and map them to a hash with submission ids as keys and related submissions as values.
As I already mentioned, it’s good practice not to modify the state, but always return a new state object.
If you look at RECEIVE_SUBMISSION or RATING_PERFORMED, you can see that the new state is calculated using another reducer, SubmissionReducer:
Here we just return the submission from the action payload.
The action types file looks the same as before, but we have more actions.
This is because previously actions got directly to the store where a request to the API was made and where the state was updated:
But now the store just gets the state from the reducers. And reducers get state by calculating it from the action. So we also need actions that will return data loaded from the API.
That’s why now we have separate actions to request data and separate actions to receive data.
That is why need the middleware that I mentioned before. There is a library, implemented as middleware, called redux-thunk, which will allow us to dispatch this kind of actions.
We apply this middleware while creating the store:
You can also see here that we have a second middleware, needed for redux-router.
Thanks to redux-thunk we can now create the _fetchSubmission action:
As I mentioned before, we make an actual request to the API here, and in the success callback we dispatch a standard action with RECEIVE_SUBMISSION type, passing the loaded submission object to the payload. Now everything (state change) is in the reducer’s hands.
In the example we also dispatch an action with type REQUEST_SUBMISSION before the actual request is made. It’s not needed for loading the submission, but it might be handy if you want to react somehow to starting a request – like adding a loader etc.
In a real application, it would be also useful to add error callbacks the same way as we added successful ones.
Here is the full SubmissionActionsCreator example:
I’ve said that the dispatched action gets to the reducer, and the reducer calculates the state, which is used to update the store.
I’ve also said that the state is returned to the component which dispatched the action. Now we can see what it looks like:
Notice two important things here.
Firstly, we don’t use this.state anymore, we use this.props instead.
It’s possible because of these lines:
Thanks to these lines, the select method will be executed when the component gets the new calculated state.
In this select method you can choose which state parts your component needs.
As the component in the example is a component for the submission detailed view, in the select method we choose the submission with the id specified in params.
That’s why we can use this.props.submission in render method.
Secondly, notice how the action is dispatched – this.props.dispatch(performRating(this.props.submission, value)).
Thanks to the connect method we also have this.dispatch available.
Creating the store
The last thing we are still missing is actually creating the store object. We defined a method for creating a store, but we didn’t use it anywhere yet.
Let’s do this client side first. Edit your application.js to look like this:
Now you can see why we needed to define a method for creating the store.
It’s because a big part of the configuration (like reducers, middlewares) are the same client and server side, but some parts differ.
Notice that createHistory for client side is imported from history/lib/createBrowserHistory and for server side from history/lib/createMemoryHistory. It’s simply because on server side you don’t have browser.
Similar thing with reduxReactRouter – for client it’s imported from redux-router and for server from redux-router/server.
Full rendering on the server side
In the first post of this series I mentioned that our app will be universal, which means that it will render on the server side too, so we can benefit from better SEO.
But when you check your source code, you can see that although our component tree is rendered correctly, we still can’t see actual data being rendered on the server side.
They are still only visible on the client side. That’s because we use asynchronous requests to fetch the data, so the server renders the page before the request to load data is finished.
Now, when we have redux-router, it’s easy to fix. In routerState we have access to the components’ classes matched for this route.
Assuming that in each component that needs data fetched we’ll have a class method to fetch needed data, we can iterate through a given array and use this method.
Still the request will be asynchronous, so we need a mechanism for waiting for all the requests to finish, so we can finally render the page with all needed data.
Here is where Promise.all comes in handy. It does exactly what we need. You can pass an array with promises and you can invoke then, the same as on a single promise.
Now when we have a mechanism to retrieve the needed data before rendering a page, all we need to do is pass fetched data to the client side.
That’s why we needed window.INITIAL_STATE in our view. Server will save the initial page in the window.INITIAL_STATE while rendering the page. Then the client side will configure the store using this state.
Let’s update server.js then:
Add these lines above our main application div:
And add fetchData static method to the SubmissionPage component:
Full code accessible here.
Post image was taken from really nice Redux example with modern best JS practises.