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 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.
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.
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.
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.pngin 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-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.
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
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.
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:
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
isLoadingwill tell us if we’re doing any loading operations (i.e. things involving the API) at any given moment.
isAuthenticatedtells us if the current user is authenticated or not.
loginis 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
password from the synthetic event object’s
Notice how we’re passing in the form values in the format
evt.target.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
useStateto 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
false depending whether the response has an
error value (which it will if anything went wrong signing the user in).
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
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: