- Overview
- Transcript
5.7 Add Asynchronous Actions
For our last lesson, we’ll create a simple server, and see how we can use asynchronous actions to exchange data with the server.
Related Links
1.Introduction2 lessons, 04:39
1.1Introduction01:31
1.2Application Demo03:08
2.Get Started With Redux6 lessons, 41:43
2.1Set Up the Project09:34
2.2Reducers and Actions10:10
2.3Combining Reducers08:42
2.4Challenge: Add a Case to a Reducer04:44
2.5Challenge: Split a Reducer05:19
2.6Challenge: Build a Component03:14
3.Create React Components8 lessons, 50:18
3.1Build a Pure Component04:53
3.2Start the Sidebar04:31
3.3Write Action Creators08:37
3.4Use Action Creators06:42
3.5Challenge: Temperature Converter05:50
3.6Challenge: Todo List09:21
3.7Challenge: Action Creators07:26
3.8Challenge: Refs Research02:58
4.Application Structure9 lessons, 56:09
4.1Refactor Our Application for Growth08:43
4.2Using the `react-redux` Package13:12
4.3Add a Router07:24
4.4Create Nested Routes07:44
4.5Add `localStorage` Support03:38
4.6Challenge: Presentational and Container Components07:26
4.7Challenge: Basic Routing02:53
4.8Challenge: Route Not Found02:51
4.9Challenge: Route Parameters02:18
5.Implement the App9 lessons, 1:31:34
5.1Create the Toolbar06:16
5.2Create the New Card Modal15:16
5.3Display a Deck of Cards05:27
5.4Create the Edit Card Modal10:20
5.5Filter Cards06:24
5.6Create a Study Interface19:29
5.7Add Asynchronous Actions13:08
5.8Challenge: General Conversion Component07:11
5.9Challenge: Users List Component08:03
6.Conclusion1 lesson, 01:32
6.1Conclusion01:32
5.7 Add Asynchronous Actions
As we saw in the previous lesson, we've built all the main features of our application, but our data is currently just being stored in the local storage of our browser. What would be more ideal is if we could store it in a database and fetch it through our server. Now right now we've just been using the live server package, but in this lesson we're going to write a very small server that we can use to host our application on. So let's head over to the terminal here, and we're going to install express which as I'm sure you know is a very easy to use node web server package. We're also going to need the body-parser middleware so that we can parse json bodies that we receive back from our client. With those installed. Let's go back to our editor and I will create a new file in the root of our directory called server.js. Let's go ahead and import express by requiring express and we will also import the bodyPaser In a real application, of course, we would have a connection to a database where we would store our user information as well as their decks and their cards, however, because we're making a very basic server here, we're just going to have a simple data object that we Store all of our data in. So now let's create a new Express application. So the first thing we'll do is we'll use the one piece of middleware Express does have, express.static, and this will allow us to statically host some files in a folder And this is going to be dirname + /public. This will allow us to host our JavaScript as well as our index.html file and our CSS with this server. Next let's go ahead and use bodyParser.json so that we can parse any Json bodies coming back from the browser. Of course, this is how we're gonna save our data. So it's important that we are able to read that. Now let's make a super simple API. We'll get /api/data and when we get a request for data what we can just do is say resres.json and we'll send back whatever the data in our data variable is, and that's how we get our data. Of course it's possible that they want to set data so they'll do that by posting back to the very same root, api/data. In here what we wanna do is set data equal to request our body. Request our bodies can be passed to JSON thanks to the body parser middleware we have and so we're just gonna set that data. Of course in a real app this is where you would do any kind of validation. Now we do need to send a response back. Thankfully, when we set a variable without having a var or a let keyword at the beginning, this evaluates to whatever the value of the variable is. Basically this is an expression and not a statement. So we could actually just wrap this in a call to res.json and that way we just return To the browser, whatever they sent to us. It doesn't really matter what we send back because we're not going to be expecting anything in return. Finally, we'll just use an asterisk as our wild card here, basically a catch all route. If there are any other routes, specifically, of course, these are routes that we need to manage on the front end, we can use response.sendFile. And response.sendFile takes an absolute path to a file name. So we'll use double underscore dirname To get the current directory and then we can say + /public/index.html. And finally we will listen on port three 3333 and our server is none. So now let's head over to the tab where we are hosting our live server application and instead let's go ahead and do node server. And run our server. Now if we come back to our browser, we can go to a local host port 3333 and you can see that we have our application. Of course, since this is a different host, we don't have our local storage but will be able to save some stuff to the server soon. For example, if I was to create a new deck right now it would actually be saved. However, we're still saving in local storage. But now that we have a server, we can actually adjust our application so that it will use data from a server instead of in local storage. Now what we're gonna be using here is an asynchronous action. The idea here is that previously all of our actions were fully synchronous as it says right here in the documentation. Every time an action was dispatched, the state was updated immediately, but with asynchronous actions it's a little bit different, when we dispatch an action we need to wait for something to happen on the server, so we can't actually update our state right away. So how do we do this in redux? Well, if you think about our actions, there are actually three important moments in an asynchronous action, and they're listed right here in the documentation the first, of course, is an action that will let our reducers know that our request has started. Then, we can fire another action to let our reducers know that the request has finished successfully. Of course, if the request is not finished successfully, then there should be an action that lets the reducers know that their request has failed. So let's go ahead and see how we can actually build this. Now the way we have our application set up right now does not support asynchronous actions. Instead, we need to install a piece of middleware for redux. So let's do npm install redux-thunk. Now if you've never heard of thunks before Basically their promises. I can't really explain all the details of a thunk right now but Cal Simpson has written an excellent blog post called Thoughts on Thunks and he explains in great detail exactly what a thunk is and why it's important. Basically though, as you can see from the spoiler here, it's pretty much a promise, but not quite. So the first thing to do, now that we have this thunkMiddleware, is to actually use it. We're back here in our starting file, app.js. And I'm gonna go ahead and import thunk Middleware from redux-thunk. Now while we're up here in our imports, let's get rid of our localStore, because we no longer need to store things locally. So we can get rid of that there. We can also get rid of it in our creation of our store here. And finally, let's get rid of it down here in our run function. So now for applying the middleware. Well, as a second parameter to store here we can pass a call to applymiddleware and we'll call applymiddleware passing in the thunkmiddleware. All right so now all of our current actions going to the store can act as they already do. However now we can also send actions that will return a function. So let's go over to our actions file and let's create a new function here called fetchData. So all of our previous functions here have just immediately returned an object but instead fetchData is going to return A function. Now thanks to our thunk middleware this function actually receives a parameter and that parameter is dispatch. This allows us to dispatch other actions to our store from within our fetch data call. Now the reason we have dispatch here is so that we can perform our asynchronous action here. And then dispatch another event to let the store know when that asynchronous action has completed. So what we're gonna do here is call fetch and fetch is a function that most modern browsers have that allows us to make an Ajax request to the server. So I can just go ahead and fetch('/api/data') to get our data from the server. Now we get a response object here and let's return response.json which is another promise. And this will just convert our response body to actual json. And then once we receive that json, we can actually use dispatch and so basically this is our success call, right? When we call this fetch data function, we're starting the process of our asynchronous event. When we receive this json, then, we can go ahead and dispatch another event. And let's dispatch the receiveData event and we'll just pass that json in there. Now if we wanted to, at this point, we could also Have our catch events, right? And we could catch an error here and maybe we dispatch an event failed request or something, right and we pass that error in. This is the third of the three important points of an asynchronous action that we talked about earlier and we're not gonna worry about this in this case because it's a very simple application. And we know we'll get our data from the server but in a complete application don't forget to prepare for failure. The one thing I didn't mention here is that we could also dispatch an event up here before our asynchronous call begins. Right because we have access to dispatch. So we could go ahead and dispatch a startedCall event or something like that to let our store know that we have started to look for data. Maybe it's going to display a spinner somewhere or something. But we're not going to do that in this application. Now we have this received data function call here but we don't have an action function for that. So let's go ahead and create receive data and as we know this is going to receive some data. So let's just dispatch an action called receive data and the data is the data that we receive. All right so when we receive this data, what are we gonna do with it. What we need to manage that data from within our reducers So let's jump into our reducers here and there are actually two different reducers they need to manage this data. As we might guess this data is is going to be the cards and the decks. So we know our cards reducer here at the very top let's add a new case and this case is going to be receive data, and if we receive data then what is our state going to be. Our state is going to be action. .data.cards. We assume the data that we get back here from the server will be an object with the cards and a decks property and this is just going to return action.data.cards. Or if nothing is there then we'll just return one of the current state is. Now we wanna do almost the exact same thing down here in our decks reducer. When we receive data, we'll return as our state action.data.decks. And this way we can load our cards and our decks into our application. So now we actually need to call this fetch data Action. So let's go over here to our app.js file and down here at the bottom and let's create a new function. And we're going to call this function init and we're going to do a couple of things in here. First let's move our run and store subscribe run calls in here. Then We're gonna create another store.subscribe call and we're gonna call save. Well, write that function just a second. Finally at the very end, let's use store.dispatch and we'll dispatch the fetch data event. Now we need to actually pull that in so back at the top we actually aren't getting any actions right now. So let's just Import fetchData from actions. So now we'll actually of course calling init to begin our application. What about this save function? Well let's write a function here where we can actually do our save. Save will be very simple. We'll just get the current state of our application by doing a store.getState, and then we can use fetch again. This time, to post to api/data. And when we're posting, we Pass an object of parameters here. The method is going to be post. We will need to pass some headers here so that it knows we're dealing with json and those headers are going to be Accept application slash json and also Content dash Type application slash json And finally, the most important, the body of our request is going to be JSON.stringify and let's create a new object here. And we'll have the decks equal state.decks and we'll have the cards Equals state.cards. Now this is actually gonna be called a little bit more often than we want. Basically what we're doing here in init is saying that every time the store changes, we want to go ahead and save to the server. Really we only care about whenever the decks or the cards changes. So we could check that in here. Maybe we save a previous version of decks and cards in there, JSON.stringify format, and we can compare those strings and if they've changed Then we'll go ahead and do the fetch. However., we're just gonna keep this super simple every time the state changes we're going to save it to the server. So that should actually be all we need, let's go back to the browser and as you can see we have our empty application. Let's go ahead And set new deck equal to one and I'll go ahead and create a new card, very simple like that. We can go ahead and create another card here. All right, everything seems to be working. Let's see if we can filter, we can filter if I study the deck. I can study just fine when we get to the end, we have no cards to study. But try and study again, no cards to study. Let's pop this open and if we look at local storage.getItem state. You can see that from a previous time running our app, the decks and the cards are empty in local storage. In fact We could go ahead and set this equal to null just to be sure that we've cleared that out and now if I refresh this page you can see we still have our data because we're getting that data from the server and that means of course that our asynchronous events are happening. We can go to the Network tab here and you can see that we've Gotten our data right here. Here's our response. We've already posted it back because we made a change to the state when we got the data but if I go ahead and actually do a filter here you can see that we post the data right away and, of course, you don't want to have your post requests going to your server every time that your user Types a letter. That's what we're doing here. But again remember you want to be a little more selective about when you send that data back to the server. Apart from that however we have built a complete react and redux application.