Building the services
This lesson focusses on building the data-handling services and core Redux management system for the Dinosaur Search App
Building the services and core structure#
We’re going to introduce something a little different in this final project, the concept of services. If you’ve been developing for a little 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 frontend UI should just concern itself with asking for data, receiving it and then displaying it to the user, allowing them to interact with it. 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 in this lesson.
However, for now let’s start with implementing a Redux system using the
useReducer 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
initialState.js file in the
/redux folder. Add the following
initialState 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. We’ll plug this file into our reducers to effect change upon it as each reducer function is called.
At the moment, however, you can see that we have two slices of state:
dinos. 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
favourites array where we’ll keep track of our favorite dinosaur id values.
At this point, you may be starting to suspect some British involvement in the development of this course! Of course, you would be right (I am a UK developer after all). That's why the
Favouritescomponent and functions dealing with the favoriting and unfavoriting of dinosaurs contain the British spelling of the word "favourite". Feel free to change these however you wish, just make sure to update all the file and function references from to share the same names or you'll run into errors when you run the project.
Save the file and let’s move on.
As you may expect, the
authReducer.js file will handle state updates that relate to the
auth slice that we’ve just seen. Specifically we’re interested in a few state changes:
Trigging a loading status change upon signing a user in.
userobject when we’ve successfully finished signing in.
Performing a state reset when the user signs out.
Start by creating a set of actions:
You can see we have three different
switch cases to handle the three scenarios we outlined earlier. Each one returns a new copy of the
state object, only changing those parts that it needs to.
The only time we’re concerned with using the
action argument passed to the reducer is when the user has successfully signed in and we get a
user object back — you might remember this from the previous lesson where we explored the API.
The complete file#
The file in its entirety now looks like this:
dinoReducer.js file is going to look very familiar to the
authReducer 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.
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.
Let’s add in the reducer body:
The first two
switch cases essentially just alternate a
loading property from
false and vice versa. 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 boolean to its opposite state in one shot.
Further down where we have the favorite-handling parts there is a little more logic, but nothing too complicated. In the first, when the user favorites a dinosaur, we return a copy of
state with the
action.payload value (which will be an id string) tacked onto the end of the
Conversely, when a user unfavorites a dinosaur we need to perform a slightly bigger code dance to filter the current favorites in
state , removing the id value that matches the
action.payload value, and then set the
favourites property in
state to this new array.
The complete file#
The completed reducer file should 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.
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 to dwell on the details here. The keen-eyed 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 is that this time we have two reducers to import, namely
We import those reducers and pass them to the
combineReducers function which will smush them together and handle different updates to different slices of
state for us, whilst we just worry about calling a single
dispatch function to do the job.
If you would like to revisit this file and how it works, please head over to the previous module on Redux for a full breakdown and step-by-step walkthrough of what each part does.
Save this file. Now it’s time for some services.
As we explained at the beginning of the lesson, building a service layer gives us a greater degree of separation between the different parts of the app. 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 store either.
With service handlers in place they just need to ask a particular service for data, receive 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 this service would never need to know about it.
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 store to dispatch any updates to our app’s global state system.
Let’s start with the
api.service.js file. This will be a service to service other services (try saying that three times fast!). Essentially the API service will be the direct link to our API. It’ll handle any and all API calls, formatting incoming data and returning any response from the API to the caller.
Open it up and let’s fill it out, starting with a bare scaffold:
We’re pulling in
axios to help with our physical API calls. Next we have a
baseUrl variable which is a simple string,
/api. 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 that means more work. We can stash it in a variable here for reference later.
/api and appends whatever URL was passed to it as a parameter.
ApiService that contains two methods,
post() which will handle
POST calls to the API respectively.
Adding the axios calls#
Let’s flesh out the two class methods:
In each method we call an axios function,
post(). We pass some additional data to the
post() function, but each case returns the asynchronous promise to the caller.
The complete file#
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, nor does it do anything particularly exciting yet. However it gives us a great starting point to extend the interaction with the API. Even if it’s doing little more than calling the API and returning the response to the caller, we could add logging in here, intercept the request or response, inject authentication bearer tokens, or a myriad of other things if we so choose.