This video is available to students only

Building the components

In the final coding lesson of this module we'll flesh out the UI components that will power the Dinosaur Search App

Fleshing out the components#

So here we are: the final coding lesson in the course and the one that will make all the magic happen in our Dinosaur Search App: the components.

In this lesson we’re going to work through the components we defined right at the start of the module and craft our UI, which will be powered by the hard work we completed in the last lesson.

Let’s start with the Layout.jsx component.

Layout.jsx#

The Layout component is simple and presentational in nature. We used it in the App component to wrap a set of child components in some common styles and structure including things like a header and navigation. Now’s the time to put those common elements in place and we’re going to define the entire file here in one go since it’s quite small:

We pull in the Navigation component (which we’ll build out next) and then define and export the Layout component. We’re destructuring the incoming props argument passed to the component, pulling out the children value. The children property is one of a few default properties on each props object passed to a given component. A lot of components have children, but you don’t always make use of this children property unless you want to simply pass them along and render them as intended (but unaffected), like the Layout component is doing here.

We wrap the Navigation component in a <header/> element and then add a semantic <section/> element and child container with some scant Bulma classes on them to just give some padding and other spacing.

Because this component will be applied to any route that’s rendered by App we can ensure that each separate ‘page’ in our app will share the same foundational structure, header and spacing base styles.

The Navigation component is almost entirely presentational in nature in that it has minimal logic in place, and even then, what it does have is a series of toggles for displaying one thing or another depending on the boolean value of some variable.

Imports section#

Let’s start with the imports:

We’ll need the Link component from React Router so that we can navigate our users around the app when they click on a menu item, so we pull that in. We’re also going to be checking if a user is authenticated or not using our custom Hook, useAuth so we’ll bring that in too.

Now for the skeleton component body and default export:

After calling the useAuth() Hook here, we can pull out just the isAuthenticated check and the logout function to sign a user out of the app. With the return statement we have an HTML <nav /> element with some Bulma classes in place that will wrap our menu items.

Let’s define those next.

Component body#

In the top section where we have the navbar-brand class, we’re adding a Link component around the little dinosaur logo we added to the project in the setup lesson. The link will take our users to the home page from wherever they are in the app at the time. The navbar-burger element will display a hamburger-style navigation menu on a mobile device.

I recommended naming the file cartoon-dino.png in the setup lesson and making sure it was saved to the /client/public/ folder. However, if you have the file named something different, just take care to use the correct filename in this lesson otherwise you’ll see a broken image when we run the app.

In the element with the id main-nav we have two distinct sections, navbar-start and navbar-end. These are dictated by the Bulma framework with the former displaying navigation items to the left of the bar, the latter displaying them to the right.

In the left-hand side we have another two Link components to take our users to the search page to find a dinosaur by name, or the browsing page to just scroll through the dinosaurs on offer — this one doubling up as a search results page too.

In the navbar-end side of things, we have a couple of checks for authenticated users going on. With the first one, we’re using the shortcut expression evaluation to check if the user is authenticated and if they are then we render another Link component that will take them to the favorites page. If they’re not then this link won’t display at all.

Finally we have two statements that offer the exact opposite behavior to each other. The first checks for an authenticated user and if it finds one, shows the button that allows that user to sign out. If they click this, we use a simple inline arrow function to call the logout function we pulled from the useAuth Hook.

The second checks to see if the user isn’t logged in and if that’s the case, it displays a Link component that allows the user to navigate to the login page to authenticate against the API.

React, like JavaScript, is very flexible and will allow you to define your logic and component rendering in lots of ways to suit different coding approaches and styles. For example, you could do this last section in a single line as a ternary if statement, like isAuthenticated ? 'render one thing' : 'render another thing'. This is fine for more concise or terse code, but when dealing with the conditional display of other components with their own attributes and properties as we are doing here, I prefer to have separate and distinct lines that use the shortcut expression evaluation syntax as a sort of toggle. To my eye it looks clearer and easier to read.

The complete file#

The completed file should now look like this:

LoginForm.jsx#

The login form is going to be a straightforward component form that will accept a username and password and authenticate the user against the API, either redirecting them to the home page upon success, or displaying an error if authentication fails.

Let’s bring in some imports:

We’ll be using the useState Hook so we grab that from React, and we’ll need to redirect the user if they sign in successfully, so we’ll need the Redirect component from React Router too. Finally, we’ll be using various functions from the useAuth Hook so let’s pull that in too.

Before we flesh out the main component logic, let’s define the skeleton body and default return:

Variables and logic functions#

With the skeleton in place, let’s put some flesh on those code bones:

We want to keep track of any errors that occur and that’s a perfect job for useState so we employ that in the first line. Next, we’re pulling a few items from the useAuth() Hook:

  • isLoading will tell us if we’re doing any loading operations (i.e. things involving the API) at any given moment.

  • isAuthenticated tells us if the current user is authenticated or not.

  • login is the abstracted service function that will do all the talking at the API level.

We only have a single handler function here, handleFormSubmit, which does what it says in the name: handles the submission event of our soon-to-be-added HTML form.

Inside this async function we stop a full-page refresh with evt.preventDefault(), then reset the errors in state using the setErrors(false) function. Next, we’re awaiting the response from the login function provided by the useAuth Hook, passing in the expected values of username and password from the synthetic event object’s target property.

Notice how we’re passing in the form values in the format evt.target[0].value? It’s another way to grab form field values from the submitted synthetic event object. You don’t always have to hook up every form field into an object via useState to track their values in component state. Since we’re not doing anything particularly clever or detailed here (e.g. input error checking or conditional displaying of other fields, and so on), so we can afford to just grab the form field values directly from the form submission event.

Once we have a response from the API the code moves on and we set the value of errors in state to true or false depending whether the response has an error value (which it will if anything went wrong signing the user in).

Component body#

Now we’ll move on to the main JSX part:

Remember things always look more bloated around form fields and HTML forms because there’s just more markup to make things look and feel how we want. Starting at the top, we have our familiar little mascot being displayed in a figure element, followed by a title of ‘Sign in’.

After that comes our form with the handleFormSubmit event handler function attached to its onSubmit event. We have username and password form fields with only the lightest of conditional logic in place around the className attribute. In both fields, we add the input CSS class, then check the errors value in state, adding an additional is-danger CSS class if it has a value. This will just give a nice little UI clue to the user that something’s wrong if there are errors during the sign-in process.

Note, when we come to run the app and play around with it, remember that to simulate a successful sign in, you can enter any value in the username and password fields to authenticate. If you want to see the errors in action, just leave one or both of the fields empty.

After the form fields we have a default submit button with some Bulma button styling. We’re adding a conditional CSS class again, but this time we’re checking the isLoading boolean value from the useAuth Hook. If we’re loading then we add an is-loading CSS class to the button which will give us a little spinner icon and temporarily disable the button for us.

Finally, after the form, we have a shortcut evaluation again that looks for a truthy value of errors in state and displays an error-styled notification message to explain to the user that there were problems signing in and to try again.

The complete file#

The completed file should now look like this:

DinoSearch.jsx#

In many ways the DinoSearch component will look very similar to the LoginForm because at their cores, they’re both essentially input forms that handle submission events and then take action based on form field input.

Let’s start with the imports, component scaffolding and export:

Imports and component skeleton#

Nothing flash here, we pull in React and then a new one, the useHistory Hook from React Router. This will enable us to interact with browser history properties and functions via the React Router system.

Now for the skeleton component:

We’ve got a standard function component here that we define and export straight after. We’ve called the useHistory Hook right at the top so we can use it later. Next, we’ve outlined a handleLuckyClick and handleSearchSubmit functions. The first will handle the click event of an ‘I’m feeling lucky’ style button in the form we’ll be building shortly. The second will, unsurprisingly, take care of any form submissions that we make.

Logic functions#

With our barebones event handling functions in place, let’s add some logic to them, starting with the handleLuckyClick function.

When a user clicks the ‘I’m feeling lucky’ button we want to bypass the form field search input and just direct the user to the /browse page but supply a random alphabetical character to the page as a URL parameter. This is what the handleLuckyClick function will achieve.

First, we define a string of the alphabetical characters and then grab one randomly, stashing the value in the randChar variable. We’re using a common pattern of combining Math.floor and Math.random multiplied by the length of a particular string or array. Once we have this random alpha character, we can use history.push() to add the path /browse/[our-random-character] into the browser’s history and then navigate our user over to the new page.

You could also use the React Router Redirect component here, but we’re using the useHistory approach as it enables users to navigate backwards to this search page and try another search more easily and it maintains a browser history trail which some users may find useful too.

With handleSearchSubmit we have a very similar function. We stop the form from refreshing the page, then we grab the user’s search term from the form’s first field target property, before performing the same history.push() we did in the previous function, adding the dynamic URL path to the browser history and sending our user to the new page.

Component body#

All that’s left is to flesh out the component’s JSX, so let’s take care of that now:

We have some Bulma-styled section and Flexbox columns to help us keep everything centralized on the screen, and you’ll notice our little dino mascot popping up again in the figure element at the top.

In the form element we’re attaching the handleSearchSubmit handler function to the form’s onSubmit event. After this, we have a search box (which is a text input field) and then two buttons: the ‘I’m feeling lucky’ button, which is hooked up to the handleLuckyClick handler function; and the ‘Let’s search!’ button which isn’t attached to anything, but will trigger the default form submission event when clicked.

The complete file#

The completed file should now look like this:

DinoCard.jsx#

Let’s move on to creating the DinoCard component. This component looks longer and more complex, which belies its actually fairly straightforward nature. It’s going to be a largely presentational component that will display information about a particular dinosaur as part of a parent list component, such as the DinoBrowse that we’ll define just after this one.

Imports and component skeleton#

Open up the /components/DinoCard.jsx file and let’s start with the imports section and the component skeleton:

Again we’ll be making use of our old friend the Link component from React Router, so we import that, as well as our shiny new useDinos service Hook. In the DinoCard component definition we’re destructuring the incoming props object to dinoInfo, which will match the properties of a dinosaur information object returned from the API.

Variables and functions#

With our skeleton in place, let’s fill out the variables and single function:

There’s lots of destructuring going on here, but it all starts with setting a string constant (of the base URL where we’ll find our dinosaur images) into imgBaseUrl. This is the static path that our Express-based API exposes to the outside world. In reality it happens to be a folder full of dinosaur images.

Next, we pull out the array of favorite dinosaur id values, favouriteDinoIds, from the useDinos() Hook, as well as both the addFavouriteDino and removeFavouriteDino functions that will add or remove a particular id value from our list of favorites in state.

After this we have some more destructuring, this time of the incoming prop value dinoInfo into its constituent parts. You don’t have to do this, but I like to as it feels nicer to me to refer to variables by their name directly instead of using the longer-form props.dinoInfo.time_frame, as an example.

Finally, we create a variable isFavourite which is just a boolean that determines if this particular dinosaur is one of our favorites of not. It does this by checking if the array favouriteDinoIds includes the current dinosaur’s _id value.

With the single function, handleOnFavouriteClick we’re going to first check if this dinosaur is a favorite already. If it is then we call the remove function from the useDinos Hook, passing along the _id value. If not then we should call the add function instead. We carry out this toggling logic here because, in the UI, we just want a single button that will just trigger one function call regardless of whether the id needs to be added to or removed from state.

Like anything, however, there are multiple ways to approach this. In the upcoming JSX we could implement this same logic as follows:

  • We could render two buttons (one at a time) that conditionally display depending on the favorite status of this dinosaur, each handling the add or remove.

  • We could have a single button (as we will do shortly), but move this isFavourite checking logic into an anonymous inline arrow function, directly in the onClick event of the button. Something like this, onClick={() => isFavourite ? removeFavouriteDino(_id) : addFavouriteDino(_id)}.

Personally, I feel the first approach is too much JSX and adds unnecessary clutter to the rendering elements. And with the second, I like to keep event handling function calls simple and small, choosing to abstract any looping logic or conditional checks into a separate function where possible. This also means we don’t have to rummage through the JSX to keep track of what happens on a click or change event. We can go to the function that handles it and work out the logic going on from there.

Component body#

Let’s add the JSX and then step through some of the finer points: