We now have an app that gets its initial state by fetching data from the API, and renders the UI according to the data that it receives. Pretty sweet! But what if we want to update this data?
Let's say we post a new message to our room. When we do that, we want an AJAX call to be sent to the server (so that we store the new message in the database), but we also want this new data to be instantly visible on the page without us having to refresh the page. To accomplish this, we should use some kind of state management tool for our app. Fortunately, there's a great library that fits out use case pretty well – Redux.
What makes Redux so special is that it makes updating this single resource easy and predictable using actions and reducers. Here's our vision for how Redux should work in our app:
We have an object representing the state of our app – the store. It currently contains one room and one message.
After we compose a message, we click the "send"-button, which triggers a Redux action.
The Redux action sends all the necessary info (the message and the room that it was sent to) to a certain reducer.
The reducer takes the object representing the current state of our app, clones it, and makes certain changes (in this case, it adds a message). When it's done, it returns the new state.
Our UI detects that our Redux store has been updated, and re-renders.
Creating the Redux store
To start using Redux, we'll install
react-redux with NPM (again, in the
We whitelist the packages...
...and we import the stuff that we need (
Provider) near the top of our
Alright, are you ready for some Redux? We're now going to be a bit bold and comment out the
componentDidMount() hook, while completely removing the
constructor() hook from our component. This way, we no longer have any data!
Since we no longer have any component state, we cannot pass that down as props to the
ChatContainer components. We should therefore remove those lines too:
Goodbye sweet data! :( Don't worry, it will soon come back.
Next, we're going to create our Redux store, which is the new place where our data will live. To create a store, we first need a function that returns the state of our app. In order to get started quickly, we'll simply return the
DATA constant (from
fake-data.js) as the result of this function.
This function, which we now store in a
rooms constant, is called a reducer, and we create the store by passing it as a parameter to the
Finally, in order for our
ChatContainer components to be able to read from that store, our
App component needs to be wrapped in a
Provider component! We'll do this at the very bottom of the file:
Are you still following? Good! Now we need to take care of the first flow of this data – from the store to one of our components.
We'll start with the
MenuContainer component. As you know, this component expects to find a
rooms prop so that it can render the list of rooms. Only now, we're not passing down that prop anymore, because we want to read the data from the Redux store instead, right? The trick is therefore to map the state of our store to a prop called
rooms. We do this using the
connect-function given by the
mapStateToProps-function will read the global Redux state (from the argument
state) and return an object that simulates the behaviour of passing down props. In this case, the prop is
rooms and its value is whatever the value of
And with that, you've successfully created your Redux store and read its data from a component!
Our first action
Even though we've set up Redux, you might be thinking right now that this feels like a step back. We used to have database data in our UI, but now we just have that stupid fake data again! I agree, let's try to bring back our real data by creating our first Redux action!
Our Redux action will be called
"SET_ROOMS" (Redux actions are usually all-caps and use underscores), and its responsibility will be to initialise the "rooms" data as soon as we start our app. To get the data from our API, we need to use the Fetch request that we commented out earlier. This time however, we're going to make that request in the
MenuContainer component instead of the
componentDidMount-hook from your
app.js-file and place it in
When we get our data this time, instead of using
this.setState to set some local component state, we want to set the state in our Redux store. This is done by dispatching an action. This might sound advanced, but the concept is actually pretty simple – we invoke a special prop on the component as function, and that function will "bubble up" the new data to our reducer.
Since we called the Redux action
"SET_ROOMS", we'll call this special prop function
setRooms, and here's how we invoke it after our Fetch request is done:
setRooms prop obviously doesn't exist yet, so if we try to run this it will just say
this.props.setRooms is not a function. How do we then create this prop and map it to a Redux action?
Remember how we used
mapStateToProps to turn data from the Redux store into props? Well, it turns out that we can easily turn dispatch actions into props too, using
First, we need to create our action. A Redux action should simply return an object with the action's
type, and some eventual data, which will be used by the reducer later. In this case, the type is
"SET_ROOMS", and the data is the array of rooms that was returned from the API. We can put this Redux action in the same file, right above the component class:
Now we simply connect this action to the
setRooms prop by creating a
mapDispatchToProps object and passing it to the
connect-function at the end of the file. Again, this is very similar to what we just did with
Now, when we use `this.props.setRooms` in our component, the `setRooms` function declared at the top of the file will be called!
setRooms-function will, in turn, trigger the reducer function:
In case you've forgotten, our reducer is the
rooms-function at the top of our
app.js-file. Let's stop returning fake data in that function and instead we'll simply log the arguments of the function when it's called, just to see what happens:
The reason why
state stays undefined is because we're not returning anything from our reducer function now.
What's interesting is that you can still see the different actions that are triggered! The first one is of type
"@@redux/INIT", which, as you probably guessed, is initialised by Redux itself and sets the default state. The second action however,
"SET_ROOMS", is the one that we just created!
The only thing left to do now is to return a new state based on the previous state and the action (both of which we receive as arguments)! For this, we're going to use a
state =  in the arguments section, we make sure that the default value of the state is an empty list (which seems reasonable since our data is supposed to be a list of rooms).
Then, based on the action type, we return a new state. If the action type is
SET_ROOMS, we completely disregard the previous state and we return the new list of rooms that we receive. All other actions are currently handled by
default, which simply returns the previous state without making any modifications whatsoever.
Refresh the page again and we should be back to using database data for our list of rooms!
You've now gone through the full loop of Redux in only two files –
menu-container.js! We first created our store. Then we extracted the store's data and set it as a prop for our component. Finally, we dispatched an action from the component and modified the store based on the action type and data. Here's an updated picture of the Redux flow, based on our little project:
Don't worry if you're new to Redux and you're still confused – I was as well for a long time. Usually, you have to go through a couple of examples before you get to a stage where it finally "clicks"!
Our list of rooms is now rendered perfectly, but the message list is still empty since that component is not receiving any props. Let's fix that!
Selecting a room
Before we continue, it might be a good idea to extract our current action and reducer into their own files to make our project more maintainable.
We'll start with our reducer function. Let's create a new file called
reducers.js and move the
app.js to that new file:
Now we can just import that function from
app.js, and everything will work as expected:
Next, we'll create a separate
actions.js file as well. We currently only have one action,
setRooms, so let's move it from
menu-container.js to our new file:
Now we can just import
After this is is done, everything should still work as before. Great!
We're now going to create a new
"SELECT_ROOM" action. The idea is that there should always be one room among our list of rooms that is selected and has an
isActive property that is
true. Then, in our
ChatContainer component, we'll read the data from the store, determine which room is currently active, and get the messages from that room. Sounds good? Let's get to work!
The first thing we want to do is to set the first room in the list as active, right after we get the data from our fetch request. We'll do this in the
MenuContainer, and call a
selectRoom action with the room's ID:
selectRoom action doesn't exist yet, so let's create it!
Now we can import this function in our
menu-container.js file, and connect it to the prop action, just like we did with
So far so good. Now we need to write the reducer for this action. This is where it gets a little complicated, but just stay focused!
In our reducer function, we receive an action with the
roomId that we want to select. What we want to return is the exact same list of rooms as we start out with, except that we want one of our rooms (the only one we have for now), to have the property
isActive set to
true whereas the other ones should have it as
To do this, we need to loop through every room that's in the list, check if the room's ID matches the
roomId from our action, and, if it does, we set that room as active.
Here's how we could write this case (don't copy this into your code yet!)
This is already pretty good, but we can make it even better! Whenever one loops through multiple objects to determine a new state, it's usually a good idea to extract the part determining the state of a single object into its own function.
Therefore, we'll create a new
room-function (in addition to the existing
rooms), which will respond to the same actions and perform the same logic as above, but for only one given room at the time. The final code of our reducer looks like this (now you can copy it):
To see if this worked as we expected, we'll log the
room object in our
The first logging occurs as soon as your fetch request finishes. The
"SET_ROOMS" action is called, and your
MenuMessage component is rendered with that data.
The second logging occurs after our
"SELECT_ROOM" action is called. That's when we take all the room data and edit it to set one of the rooms as active. Since the component's data has changed, it trigger a re-render and logs the new data that is has received, which now contains
So it seems like our data gets updated correctly! Let's now delete that
console.log line and instead add an
active class to the rendered room so that we can clearly distinguish it from the rest of the rooms in the future (in our CSS-file the
active class darkens the background):
Displaying the messages
The last part of the puzzle is to always show the messages for whatever room is selected! For this, we head over to
As we know, the
ChatContainer component expects to receive a list of messages through the
messages prop. Just like we did with
MenuContainer, we need to give it this prop by extracting the relevant data from the store and pass it down using
First, we import
connect at the top of the file:
Then we write our
mapStateToProps function and pass it to
connect. This function is in charge of only retrieving the messages from the active room.
Redux might seem overly-complicated at first, but there are many benefits to having a single state tree that provides your components with only the data that they need. It's pretty nice knowing that you can easily follow the order of your actions and know exactly what they did to change the state of your application.
Now that we have this setup, the next chapter till focus on creating new rooms and new messages. This time, we'll go back to writing Elixir code!