Intro to React Services with Redux and useReducer Hooks
This lesson focusses on building the data-handling services and core Redux management system for the Dinosaur Search App
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
This lesson preview is part of the Beginner's Guide to Real World React course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Beginner's Guide to Real World React, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

[00:00 - 00:07] Lesson 3, building the services and core structure. We're going to introduce something a little different in this final project, the concept of services.
[00:08 - 00:25] If you've been developing for a while the idea of a service layer won't be anything surprising or new to you, but for the rest of us introducing some data handling services will give us some separation between different layers of our app. Ideally the front end UI should just concern itself with asking for data, receiving it and then displaying it to the user, allowing them to interact with it.
[00:26 - 00:35] It shouldn't know or care where this data comes from or how. That's the idea behind creating our various name.service.js files that we're going to build out in this lesson.
[00:36 - 00:48] However, for now let's start with implementing the Redux system using the use reducer hook and the context mechanism as this whole process should be fresh in your mind from the previous module. We'll begin by opening the initial state.js file in the Redux folder.
[00:49 - 00:59] Add the following initial state object in its entirety. You might remember that this offers us a good at a glance starting point for the sort of structure we want our app's state to take.
[01:00 - 01:10] We'll plug this file into our reducers to affect change upon it as each reducer function is called. At the moment however you can see that we have two slices of state, auth and d inos.
[01:11 - 01:24] Each has a loading flag set on it, which we can use to toggle some sort of loading UI in the components and you can see that the dinos slice has a favorite array where we'll keep track of our favorite dinosaur ID values. Let's move on.
[01:25 - 01:44] As you may expect the auth reducer.js file will handle state updates that relate to the auth slides that we've just seen. Specifically we're interested in a few state changes, triggering a loading status change upon signing a user in, updating the user object when we've successfully finished signing in and performing a state reset when the user signs out.
[01:45 - 01:52] Start by creating a set of actions. The actions variable is just a plain JavaScript object that houses some hard- coded action strings.
[01:53 - 02:05] Next, it's time for the physical reducer code. You can see that we have three different switch cases to handle the three scenarios we've just outlined.
[02:06 - 02:18] Each one returns a new copy of the state object only changing the path that it needs to. The only time we're concerned with using the action argument past the reducer is when the user is successfully signed in and we get a user object back.
[02:19 - 02:24] You might remember this from the previous lesson where we explored the API. The file in its entirety now looks like this.
[02:25 - 02:39] The Dynar reducer file is going to look very familiar to the auth reducer file in its approach. This is something I highlighted in the previous module on Redux where things might look a little alien and complex to begin with but once you've built a Redux system, extra additions to it start to look familiar.
[02:40 - 02:51] Let's define this reducer's actions. This time we have four actions, two for fetching dinosaurs and two to handle the favoriting and unfavoriting of a particular dinosaur.
[02:52 - 03:01] Let's add in the reducer body. The first two switch cases essentially just alternate a loading property from true to false and vice versa.
[03:02 - 03:19] I don't think it hurts to have this loading state change happen in two separate reducer cases for our learning purposes but you absolutely could create a toggle loading status action and just flip the loading body into its opposite state in one shot. Further down where we have the favorite handling part is where there's a little more logic but nothing too complicated.
[03:20 - 03:42] In the first one the user favorited a dinosaur, we return a copy of state with the action.paralloid value which will be an ID string, tacked onto the end of the favoritor array. Conversely when a user unfavorited a dinosaur we need to perform a little bigger of a code dance to filter the current favoritor instead, removing the ID value that matches the action.paralloid value and then set the favoritor property instead to this new array.
[03:43 - 03:53] The completed reducer file should now look like this. Open up the reducers.js file and let's pull everything together to wire up the various parts of our Redux system.
[03:54 - 04:01] Here's the code that's going to power things. This might look like a lot to drop in all in one go unexplained but we're not going into well in the details here.
[04:02 - 04:15] The keen id among you will notice that this is almost identical to the reducers .js file from the last lesson in the previous module on Redux. The only difference this time is that we have two reducers to import, namely auth and diners.
[04:16 - 04:36] We import those reducers and pass them to the combine reducers function which will smush them together and handle different updates to different slices of step for us whilst we just worry about calling a single dispatch function to do the job. If you'd like to revisit this file and how it works please head back over to the previous module on Redux for a full breakdown and step by step walkthrough of what each part does.
[04:37 - 04:46] Save this file and it's time for some services. As we introduced at the beginning of the lesson, building a service layer gives us a great degree of separation between the different parts of the app.
[04:47 - 04:55] By building out some service handlers we can remove the responsibility of talking to the API from the UI components. They don't need to concern themselves with talking to the Redux start either.
[04:56 - 05:13] With service handlers in place we just need to ask a particular service for data, retrieve it and then process it accordingly. What's also nice about this approach is that later down the line we could change the service handler to use a JSON file instead of an API or talk to a database directly and the components calling the service would never need to know about it.
[05:14 - 05:28] Each service will manage a particular aspect of data interaction such as authentication and dealing with the API. Each one will talk to the API, supply and request information as appropriate and call out to the Redux start to dispatch any updates to our app's global state system .
[05:29 - 05:34] Let's start with the api.service.js file. This will be a service to service for the services.
[05:35 - 05:39] That's why I say that three times fast. Essentially the API service will be the direct link to the API.
[05:40 - 05:55] It'll handle any and all API calls formatting incoming data and returning whatever response is returned from the API to the caller. Open it up and let's fill it out starting with the best scaffold.
[05:56 - 06:03] We're pulling in Axios to help with our physical API calls. Next we have a base URL variable which is a simple string /api.
[06:04 - 06:14] At the moment all of our API calls start with this string path but we don't want to have to litter each function we create with it and if it changes this means more work. We can stash it in a variable here for reference later.
[06:15 - 06:36] Next the get URL function is a simple one-line arrow function that uses JavaScript's string templating syntax to return as a resulting API endpoint that begins with API and appends whatever URL was passed to it as a parameter. Finally we have a bare bones JavaScript class API service that contains two methods get and post which will handle get and post calls to the API respectively.
[06:37 - 06:42] Now we need to add in the Axios calls. Let's flesh out the two class methods.
[06:43 - 06:54] In each method we call an Axios function get for get and post for post. We pass some additional data to the post function but each case returns the same asynchronous promise to the caller.
[06:55 - 07:10] The completed API service file should now look like this. With this first service complete I must admit that it doesn't look very exciting at the moment not does it do anything particularly exciting at this point however it gives us a great starting point to extend the interaction with the API.
[07:11 - 07:30] Even if it's doing little more than literally calling the API and returning the response to the caller we could add login in here intercept the request our response inject authentication bearer tokens or a myriad of other things if we saw choose. The important takeaway here is that we've created a helper service whose responsibility is to deal directly with the API.
[07:31 - 07:54] The auth service will provide us with two super useful custom hooks when we're finished. Use auth which will give us access to the loading state of the authentication state and two functions to login and log out and with authentication required which will be a wrapper function that will check the authenticated status of our user and either return them to the component they want if they're authenticated or redirect them to the / logging path if they are not.
[07:55 - 08:03] Let's start with the imports. We've got the use context hook directly from react and then the redirect component from the react router.
[08:04 - 08:23] Next we grab our auth related actions from the auth reducer file as well as the star context and create action helper so that we can both access any relevant state items and dispatch updates as necessary. Finally we're bringing in the API service that we created moments ago as we'll need to send some requests out to the API when signing our users in and out.
[08:24 - 08:34] The use auth hook is going to be our very first custom hook. It might sound a little daunting but essentially a hook is a section of code that provides some access to deeper parts of the system.
[08:35 - 08:47] Default hook such as react use stay are really high level functions of sorts that give us access to react building component state management system for turning two variables when we call it. Let's start with the scaffold function.
[08:48 - 09:03] With use auth we really want to provide components access to check if a user is authenticated, if there are any operations in a loading state and a couple of functions to log a user in and out. Let's create the variables first and then move on to the functions.
[09:04 - 09:15] Just like we did in the previous module we're calling the use context hook here with the imported star context object. This will give us access to our app state and dispatch functions to make updates to set state.
[09:16 - 09:30] Next, let's create a function to allow users to login. Starting at the top we've created a login function that is asynchronous and accept to use an M and password as parameters.
[09:31 - 09:44] We define a response object and set it to null as we'll work this out as we go. Next we want to change our state loading boolean to true so we call the dispatch function from our context passing in the relevant action using our helpful create action function.
[09:45 - 09:59] With that done we can now call our API service calling its post function and ensuring that we use the await keyword to tell the JS engine to hold on for a moment and wait for the response from the service. We pass in the /log in URL and the use name and password arguments this function received.
[10:00 - 10:13] We're tacking on the catch function to the promise that we get back just in case something goes wrong and we can ensure we handle the response. If this happens we log it to the console and update our currently null response object assigning a property error to it with the value of true.
[10:14 - 10:20] This will then be returned to the login response variable. Next up we do a quick check of the login response variable to see if it has an error property.
[10:21 - 10:29] If it does we just return it right away. If not and everything has succeeded then we call the dispatch function one more time to update the login status of the user instead.
[10:30 - 10:45] Passing along the login response dot data object from the API call which will be a user object consisting of a token and authenticated status values. Finally we return the same login response dot data to the caller just in case they want to do anything useful with this information.
[10:46 - 10:54] That's the largest function done in the use half hook. The next logout is far simpler so let's define that name.
[10:55 - 11:12] In an actual app with a proper authentication system there might be a need to call an API endpoint to flush any caches, clear tokens or sessions at the server level and so on. However for us we can simply clear out the user property instead by calling the dispatch function passing at the sign out user action and our work is done.
[11:13 - 11:22] The last thing to do is to return an object that provides these various methods to anyone wanting to consume and use them. Let's define that now.
[11:23 - 11:32] We've passed the login and logout directly and added two other properties, is loading and is authenticated. The is loading property is just the direct value of the loading property from the off slice of state.
[11:33 - 11:45] With is authenticated we're doing a double exclamation shortcut assignment operator to cast the value of state dot off dot user to a boolean. If it's a faulty value such as null or undefined then it'll return false.
[11:46 - 12:08] The with authentication required function is a higher order component that accepts a function which will be a component that we'd like to have rendered and some options and returns a function which will be another component as a result. Its main purpose is to work out whether a user is authenticated returning the intended component and props if they are or redirecting the user to the login screen if they're not.
[12:09 - 12:17] It's useful because there will be certain routes that we don't want our visitors to view without being authenticated first. For example we don't want unauthenticated users to view the favourites route.
[12:18 - 12:36] However whilst the navigation component that we're going to build later on will only render links to restricted routes if a user is authenticated it won't stop anyone from just putting slash favourites into the address bar so we need a better means to handle sneaky or accidental routing. Let's start with the outline.
[12:37 - 12:54] After all when we flesh out components we'll see this in action but for now trust that we'll call this component function as with authentication required passing it a component that we want to ultimately render. What gets returned immediately is another function component named the same but capitalized as with authentication required.
[12:55 - 13:06] Let's add some variables into this returned function. Having just defined the use off hook we're using it here to grab the is authenticated variable it exports.
[13:07 - 13:14] We'll then destructure the incoming options argument and extract the location value from it. Next up the main logic.
[13:15 - 13:24] First we check whether the current user is authenticated. If not then we immediately return the redirect component that we imparted from the reactor route library sending the user to the login page.
[13:25 - 13:45] If everything is in order however then we can safely return whichever component was passed into the component argument making sure to also pass along any supplied props to it. With this high router component finished we can use it wherever we like to wrap around regular components that we want check in for user authentication before rendering something a regular user shouldn't see.
[13:46 - 14:00] It's worth highlighting here that this is just one aspect of a complete security system to protect your app and it's dead if unauthorized access. Ultimately the is authenticated check here isn't very solid on its own and since it's happening in the browser it's feasibly open to tampering.
[14:01 - 14:21] Plus we're not doing anything more in depth and check in for the existence of a user object instead. In a complete system you'd have many more interlocking security checks such as a jot talk and verification, API endpoint checks to make sure the request is authorized and the caller is authenticated and back end double checking of the same types of information to round out a holistic security system.
[14:22 - 14:30] The completed auth service should now look like this. The last file to edit in this lesson is dino.service.js.
[14:31 - 14:46] The primary purpose of this service is to provide us with some functions to add and remove favorite dinosaurs and load a list of dinosaurs or individual dino details. Open up the file and let's start with the import which will look very similar to the auth.service.js ones.
[14:47 - 14:54] There we go. The only real difference is that we're using the redox actions from the dino reducer instead of the auth reducer.
[14:55 - 15:10] Next up let's define the skeleton export and main logic in this file, the used dino's hook. What we've got here is a slightly different approach to creating a skeleton class function or other content piece of logic.
[15:11 - 15:28] We've created a named export used dino's which will be an arrow function and we 've immediately grabbed the state and dispatch values from the used context hook we imported. Next, we've outlined all the empty functions that we want to return from this custom hook, notice how some of them carry the async tag where we're going to be dealing with an API operation.
[15:29 - 15:38] And this is just a different way to approach building out your files. I like to scaffold components or hooks or services like this in my own code especially when I know what logic I want to build ahead of time.
[15:39 - 15:46] It can prevent some silly mistakes early on such as missing semi-colons or braces. Finally, we have a similar return object as we had in the auth service.
[15:47 - 16:01] We're providing a similar is loading property and then an array of favorite dinosaur ID values, favorite dino IDs, both coming directly from our app state. After these we provide the skeleton functions we created using the same name object property shorthand.
[16:02 - 16:12] Let's flesh out those empty functions starting with the add and remove from favorites. We'll cover both of these functions in one go because they're almost identical being two sides of the same coin.
[16:13 - 16:26] They're both calling the dispatch function to trigger a particular change instead to the list of favorite dinosaur ID values, the first adding one, the second removing one. Next, let's fill out the load dinos function.
[16:27 - 16:34] The first glance is looks very similar to the login function in the auth service. That's because the logic for calling the API is kind of similar across most of the functions we need to call.
[16:35 - 16:38] There's a common process they all share. 1.
[16:39 - 16:41] Work out which endpoint to call. 2.
[16:42 - 16:44] Prepare some data to send to the API. 3.
[16:45 - 16:47] Call the API handling some errors. 4.
[16:48 - 16:53] Check the response for errors. 5. Return either an error object or the intended response with requested data.
[16:54 - 16:57] And 6. Do some redox things along the way.
[16:58 - 17:04] In this respect the load dinos function is no different. We start by checking the search term value that's passed into the function.
[17:05 - 17:16] Notice how we set it to null by default as this is easier to check for if a value isn't supplied. People will only search for a dinosaur name, part of a name or nothing at all so search term here will either be empty or a string.
[17:17 - 17:26] We use the ternary if statement to check its value and set the appropriate endpoint to call from the API. As we saw when we looked at the API slash dinos will return us all the dinosaurs.
[17:27 - 17:41] Whereas slash dinos slash search term parameter will filter the available dinosaur data based on the term parameter that's passed in. Next up we call the redox dispatch function to kick off the loading process so our components can render the appropriate UI.
[17:42 - 17:47] From here we call the API via the API service we imported. Check for errors returning early if there are any.
[17:48 - 17:58] If everything went well then we issue a final dispatch call to flip the loading Boolean back to false and return the data from the response. In this case we will be in array of dinosaur information objects.
[17:59 - 18:09] Of course we don't always want to load a bunch of dinosaurs. Sometimes we'll only need to grab data about one specific dinosaur such as when we're viewing the details page we saw earlier in our user flow diagrams.
[18:10 - 18:18] This is where the load single dino function comes in. Let's fill in the final blanks for the logic in this function.
[18:19 - 18:33] You can see that we're looking at a very similar body of logic to the load d inos function. With a spatula state update to change the loading status then we call the API endpoint dinos and then with an id parameter passing along the id argument this function receives.
[18:34 - 18:59] Once we have a response back from the API we check for errors returning early if necessary update the loading status again via a final dispatch call and then return the data from the response object which will represent a single dinosaur information object. Top tip I briefly highlighted the similarities between our different service functions and how they each follow a familiar pattern of preparing data to send to the API calling redox and handling API responses.
[19:00 - 19:17] Now that we've finished the load single dino function you can see how this is very close to the load dinos function almost identical. Whilst we're not going to refactor this here hopefully you should start spinning those grey matter cogs and you can think about how you could abstract this overlapping portions of these very similar functions code into a single shared place.
[19:18 - 19:30] This could be a good exercise in extending and refactoring the code here if you want to experiment further once you've finished the course. After all our hard work the complete dino service file should now look like this.
[19:31 - 19:44] We've covered an awful lot of ground in this lesson putting both our apps redox system in place and building some helpful service layer helpers. I look forward to seeing you in the next lesson where we'll be finalizing our code in the component files and running the app to search for interesting dinosaur facts.