How to Build a React Admin Dashboard
We're going to build a demo Admin Console app step by step during this lesson, adding in React Router as we go.
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:25] Lesson 4, Building the Admin Console App. Let's start to build out our routing demo admin dashboard. We're going to use Create React App again to quickly provide us with a scaffolded starting point. Onto the project setup. Open up a terminal window and navigate to the parent folder that you want to create the new project in. Next we'll type the Create React App command as follows. Yarn, Create, React Dash App, Routing Dash demo.
[00:26 - 00:52] Let the command line finish installing the dependencies, waiting until you see the success message and Yarn commands to start and build the app. As we've done before, let's spin up the default app just to make sure that we 're starting from a working base. So follow the advice in your terminal output and enter the following commands. CD, Routing, Dash demo and Yarn start.
[00:53 - 01:24] Once the project's built in launch, you should be able to fire up, hit CTP, localhost 3000, in a browser and look for the familiar dark background and spin in React logo. So we need to make a few changes to get everything cleaned up and ready for our new routing app. First, open the index.js file located in /routing-demo/source and remove the following line. Import index.css. It should be on around line 3 of the index.js file. We'll just remove a link to the default styles from the project that we won't need.
[01:25 - 02:01] After that, locate the /sar/index.css and /sar/app.css files and delete them. Finally, open the main app.js file located at /sar/app.js, highlight everything in this file and delete it, saving the empty file ready to be populated with our new routing demo code. Our project still contains a few default files, components and assets that are loaded in by default, but we're not going to worry about them for now, as they're not doing any harm, just sitting there and they 're not currently being loaded anywhere. So now we need to add some project dependencies.
[02:02 - 02:20] We'll create and edit the files we need to get our project running, but first we need to add a couple of dependencies to the project. So we need to bring React Router on board first. The first dependency to add is the React Router DOM npm package. There is a React Native version of React Router, but the web version is what we need and that's React Router DOM.
[02:21 - 03:26] Back in the terminal window, make sure that you're in the root project location and enter the following command. Yarn add React-rooter-dom. With that done, we can now import the main package export browser-rooter in order to associate components such as link. Now we need to add Bulma. For this project, let's change things up a little bit with Bulma at this time, instead of referencing the Bulma framework in the index.html file via a link element, let's add Bulma via its npm package. Still in the terminal window, we add Bulma like this. Yarn add Bulma. In conjunction with our Bulma via node approach, let 's also add some project styles via SAS this time. SAS is still a popular choice for adding styles to projects, but it requires an additional library, node SAS, to be added to the create React app project. Again, still in the terminal, let's add the node SAS package using the command Yarn add -dev node -sas. We add the -dev flag this time around because we don't want this dependency bundled into the final output of the project, if it were to be built and deployed.
[03:27 - 05:15] With all of our dependencies added, let's create all the files we need for the project and work through them one by one. Note that just about all of the components under the slash components folder will be presentational, containing hard-coded JSX, a partner once explained in further detail, mainly concerned with navigation aspects. So with our app.js file, which we already have, the familiar project starting point where all the magic happens. It will take care of setting up the main React router, routing function, as well as handling the diverse URL paths we'll be using. We'll create an assets folder and that will house style.scss and that was the SAS version of our standard style.css file where we'll set some default styles and import the Bulma library . We'll create a components folder and in there we'll have account management.jsx, create user.jsx, dashbar.jsx and invoicing.jsx and nav.jsx. Now the nav component will be used and displayed as part of the header for our dashboard. It will work with our roots file, explained in a moment, to render out the top-level navigation items and their sub-nav igation items as a drop-down menu. We've got a products.jsx, a sidebar.jsx and with this component we'll dynamically render any sub-navigation items relevant to the particular area where currently viewing. It will use a few routing hooks that React router provides. We have a signout.jsx, support.jsx and users.jsx. Finally in the project route we have root.js. We're going to keep our apps, routes and matching components in this file so that we can dynamically list them in our app.js file. With the files created let's work through them to flesh them out.
[05:16 - 06:46] To open up the assets style.css and copy in the following styles. The import statement at the top of this file brings in the Bulma CSS framework into the project. Next we set a couple of sc_variballs and a scamp couple of styles to mildly color the sidebar and menu that we'll be fleshing out later. Now it's time to build out the present ational components. The vast majority of our components are purely presentational and have zero working parts. Since they have hard-coded JSX markup peppered with a few Bulma styles we're going to move a little quicker to fill them out so we can spend more time focused on the pivotal components that handle the routing logic and navigation elements. So open up the components, account management JSX component and scaffold the basic default export as follows. We'll be making use of this exact same template for the remaining presentational components so it might be worth copying and pasting it somewhere as you build these out before we fill in the specific JSX. Speaking of which, here's the JSX that we need to complete the account management component. We've got a title and dummy intro paragraph followed by a couple of form fields including a select element. We've given each of these form fields here a value so that it looks like we've loaded some account data in to be able to edit but again it's just smoke and mirrors for our purposes. Finally at the bottom we have two dummy buttons that aren't hooked up to anything. For the create user component, start out with the default export template we just mentioned then fill in the rest of the JSX as follows.
[06:47 - 07:47] After the title hit one, we have some very similar fields as in account management component but this time without the value attributes just placeholders as far as this time. Onto the dashboard components. Open the dashboard.jsx file in the components folder and fill out the JSX as follows. The dashboard is arguably the most complex looking as far as JSX structure goes. Generally dashboards contain a lot of well structured information in bite sized chunks. To simulate that we've used Bulmer's tile system which helps us create interesting grid light layout structures using a series of div elements with the tile CSS class applied to them. It is the nesting of these tiled elements that makes the JSX structure look more complex than it really is but you should be able to follow what's going on. The end result is quite striking especially with the different background colours we've applied. However as before everything in here is dummy text, placeholder images and hard coded information. Next open up the invoicing.jsx file and complete the JS X for the component.
[07:48 - 09:49] We have a page title and a Bulmer style table which contains a few action buttons to simulate possible invoicing actions that our users may wish to take such as re-sending and invoicing it as paired. Again we've leaned on the Bulmer framework to make our buttons small and coloured which adds great visual weighting to otherwise barring regular buttons. For the products component the JSX looks like this. We have a page title followed by a regular HTML table that contains a mocked list of possible products. It's all standard HTML well JSX but it looks identical, barring the addition of the Bulmer table CSS class that makes the magic happen. Onto you sign out.jsx. This is firing away the simplest component we'll be making. All we're adding to the template default export is a heading level one to inform the user that they've been logged out of the app. Now to open the support.jsx file and complete the JSX like this. The support component will look quite similar to create user and account management components. We have the ubiquitous page title, a number of farm fields to enable potential users to send us a message requesting support. For the users component in the users.jsx file flesh things out with the following JSX. After a typical page heading level one we have a HTML table with simulated user details. To make this table look the business all we have to add is the single CSS class table and Bulmer does the heavy styles lifting for us. Now we come to the slightly more advanced components and supporting files that require a little more thought and focus. They're concerned with our app navigation. We'll start with the root.js file located in the main code folder /sauce. You can of course use ReactRooter as we've seen in the previous lessons . You define link components with string path attributes and matching root components that handle those paths when the URL changes. But we're learning about real world react here and want to challenge ourselves. With our roots.js file we want a central place to manage root and navigation.
[09:50 - 11:03] It will hold an array of root to the function as navigation items. Each root object will have a name for a menu item, a URL property for the path we want this item to have and a component we want to render when the path matches. In a real application you might strip this back a little to a JSON file without the components or you might have several of these files to represent different areas of your app. Either way we're granted two benefits by structuring our rooting information like this. Number one, any rooting changes we need to make such as to the path or matching a component can be made in a single place the roots.js file. And number two, the implementation of ReactRooter's root component can now be much more dynamic and programmatic removing the need for large lists of very similar components. Open up the roots.js file and let's start filling it out. We'll start with all of the presentational components we've just finished creating. This is a regular JavaScript file so we can import our components as we've always done with the familiar format import component name from file path. Next we'll define a roots variable which will be an array of rooting objects. Each rooting object is fairly straightforward, nothing but standard JSX objects here. We have a set of object properties that will function as follows.
[11:04 - 12:36] For name we'll be using this property value directly in the nav menu so that users know how to get to each section. With URL this value will be used behind the scenes of each menu item to direct the user to a new URL. When link components are rendered to the user in the front end as anchor tags the HRF property will be filled out with this property value here. What's more it will be used by the underlined rooting system to swap out components and match the correct rooting paths. With component when a URL path matches the associated URL value this is the component that will be rendered. With exact a simple true false value that determines if this root path should be matched exactly just what we saw in the previous lessons. If the property isn't present that's the same as being set to false for our purposes. And finally sub nav. If we want any particular root to have sub roots or child pages this property will capture an array of root objects that represent them. We'll see how this mechanism works when we come to build out the components that consume the root.js file shortly. So with all of that in place the complete root.js file looks like this. On to nav.jsx. The nav component will be used and displayed as part of the header for our dashboard. It is our app's main navigation element. It will work with our root.js file to render our top level navigation items and their sub navigation items as a drop down menu. Let's get started with some imports. Of course we need our familiar friend react in the mix but we're also pulling in our first react router component the link.
[12:38 - 13:21] We're surrounding it with curly braces because link is a named export from the react. rooter-dom library. Next we're going to define a helper function that will take care of the rendering of any sub navigation that a particular root may have. Subnab is a genuine bon afide react component but it is only employed here as a helper component to reduce code complexity in the main nav component that we'll build in a moment. That's why we're not going to export it and it's not defined in a separate file. It's going to accept a root array and a parent root path as destructured props. And we'll loop through the root array and render our link component for the react.rooter library adding a class name of nav bar dash item which Bulma provides to help style individual navigation items in a nav bar element.
[13:22 - 13:42] We use the parent root path value to provide a complete URL path for the link component to attribute. This includes the parent value and the URL of the current root in the loop. Thinking back to the roots.js file we didn't define complete URL paths for children of a particular root. For example with the users root it has three children one of which is create.
[13:43 - 14:21] We want the complete URL path to read slash users slash create when we come to render navigation links which is why we got the extra step in the sub nav component here to build this full rounded out URL. We could have just fleshed out the entire URL in the roots.js file of course but it creates extra work for us if we need to change say the slash users URL as we then have to update all the child roots too. Now we're ready to build out the default export our nav component. We'll start with the skeleton body and Bulma style JSX. Nothing fancy just yet. This is all standard JSX with a particular structure and styles applied that Bulma dictates for its nav bar element.
[14:22 - 14:42] This will give us a tidy clean well styled head and navigation bar at the top of our admin console app. Now we'll need to process and render some navigation items. So we take the roots array that the component has been passed as part of its props and loop through it using the dot map function. For each root we check to see if there's a sub nav property.
[14:43 - 17:28] If not things are quite simple. We return a link component passing the root URL value to the link components to attribute and using the name value to display a meaningful level to the user. If we do have sub nav property then we still create the top level link component but is wrapped in a div element that functions as a drop down container. Again relying on the Bulma CSS framework to style all of these things for us. For the drop down part of the menu item we render the sub nav helper component we created earlier, passing in the sub nav array. We absolutely could have just scrapped the sub nav component and place its code right here instead. However as soon as you start nesting multiple map statements loops are other complex logic things get really messy really fast. It's definitely a preference call but for me by separating out this additional logic into a helper component we keep the main nav component much cleaner and simpler to read. The complete nav component looks like this. Onto sidebar dot jsx. The sidebar component's main function is to render out a title for the particular section of our app that we're visiting as well as any sub navigation links that may exist. This is a nicety for our users as it's more than likely they'll want to perform multiple actions within a given section of the app and this way they don't have to search around in the top level navigation to access them. Open up the sidebar dot jsx file from our components folder and let's start with the imports. Naturally we've got React and we're bringing in the root state of my roots dot js file. We'll be using this to match the current URL to the top level URLs from the roots array allowing us to grab the relevant sub navigation list for a specific root. You'll also notice that we're bringing in the nav link component from React Router. It will take care of any URL path matching for us applying an active CSS class for us which we'll see in a moment. But what is this use location item we're also importing? React Router has started moving to a hooks based model in a similar way to react itself. It offers several rooting hooks that give us clean easy access to various parts of the rooting system such as location and parameter information. For the sidebar we need to know more about the current location specifically the name of the path the visitor is using. We can access location information in React Router using the use location hook. Now for the main body of our component let 's define the sidebar component skeleton and default export for this file. First things first we need to create two variables. With location we're calling the use location hook . How simple is that? React Router's handy location hook will return us an location object that we can use in the next variable area root. The area root variable will hold the matching top level root object that is returned from the root stop find function call.
[17:29 - 19:00] With this we're looping through each of the root objects in the root.js file and performing a comparison between each item's URL property and the path name value from our location variable. e.g. information returned from the use location hook. We're splitting the path name value by forward slash which gives us an array. We then grab the second member of this array, the value of position 1, which will give us our top level URL path. It's key to note that the path name property the use location hook returns to us includes the domain as part of the path. For example, for the slash users slash create path we are providing a path name that looks like this. Localhost 3000 slash users slash create. That's why we must grab the second value in the string array to ensure that we have our true top level root URL to match against. All that's left is to drop in the JSX. We have an aside element, a paragraph and an unordered list each styled up with Bulma classes. In the paragraph tag we're using another React shot circuit display trick, evaluating the area root value and if it's truthy, evaluating the right and part of the expression which will output the name value which will be the name of the area we're visiting. For the contents of the unordered list we check to see if the currently found area root value isn't null and has any sub nav items. If it does, we loop through them and render a nav link component constructing an appropriate URL string to supply to its two attribute and providing the is active CSS class to its active class name attribute. It's such a small but powerful and helpful change to be able to use the nav link component over the link which we've used elsewhere.
[19:01 - 19:47] There's nothing wrong with the link component being used here but to apply some sort of active highlight styling to the currently visited URL we'd have had to do some inline logic using string matching to work this out. Fortunately the nav link component automatically does this for us, we just have to supply a CSS class name that will be applied to any matching rendered elements. Once all the code is in, the finished component should look like this. At this point in our demo build we've got presentational components to display for given roots, we've got some navigation to display in our apps header and we've got a nice sidebar section to hold any sub navigation. But all of this is for not if we don't build anything to handle the roots we visit. We can add all the link on nav link components we want in our app will render no problem, however if we go clicking around on those links we'll be greeted with errors galore.
[19:48 - 20:44] That's where our app component comes in. In the app.js file we'll be capturing and handling any and all roots that our user attempts to visit, including catering for default roots and roots that don't exist. Open up the app.js file in the root of our code folder and let's begin with those all important imports. Ignoring the imports from the reactor rooted DOM package will address those shortly. We've got react the root state of from our root.js file, our styles.scss which will be automatically compiled from us from SAS into CSS and our layout and navigation components that aren't handled within any of our roots objects. None of that should be out of place unfamiliar but it's the imports we're bringing in from the reactor router package that I have not here. These are the key components that we'll use to build out the root in structure that will handle our users navigation around the app. Breaking them down, browser router, this is the main routing provider component and needs to wrap all of the other routing components.
[20:45 - 23:52] We've brought it in and renamed it as router which is so common it's almost a convention to do so. Root, this is a component that we'll use to capture each navigable root that our user can visit. It'll also be used for four or four roots and default pages that we want to show when the app starts. Essentially wherever you want a root handled you'll need a root component and switch. We've not talked about this component yet but the switch component is another wrapper that you will wrap around a collection of root components. What it does is matches the first child root that it matches the current location. Without it you have to be more careful in composing your root handling because multiple child root components may be matched and output. The React router switch documentation has some great examples that go into much more detail about this. Suffice to say we're using it here to prevent unwanted results and gain more exact root matches. With our imp arts at the ready let's define the skeleton component body and default export. Nothing happening yet but we've added our router component in the return statement ready to be populated with our root handling work. Onto our variables. The single variable we're going to define here is all roots. We're using the array.reduce method here to essentially flatten our currently nested root data from the root.js file we imported earlier. There are other ways to achieve this but I've chosen to use reduce. It's efficient and we can easily see what's going on during each iteration. The reduce method scares some people but it's not that bad. In fact here it's really handy. We loop through each item in the root data and array of objects adding it to an accumulated array. When we hit a root object that has a subnav array we loop through that adding it to the same flat accumulated array as its parent but making sure to override the URL property to the full path which includes the parent URL string. At this point I wouldn't blame you for asking why do we need to flatten our root data. My answer would be that we don't strictly need to but as we'll see in a moment it'll make our dynamic root component rendering much cleaner and easier to read. We're almost ready to launch our app and feast our eyes on our marvellous rooting handy work. However before we start popping the champagne we need to add the last bit of JSX into the mix. Inside of our router component we have a regular HTML header element which we've placed our nav component within. It's supplied with the root data from the root.js file and takes care of itself. Next we have a common layout trope of a container with some columns, the first smaller one containing our sidebar component. The next larger column is where all of the rooting magic happens. We'll start with React Rooter's switch component. It doesn't need any additional attributes just like Rooter it can be employed just so as a wrapper. It handles what it needs to on its own . What follows is a mixture of different ways you can use the root components to decl aratively build out whatever rooting and navigation handling structure you wish. We have a simple root component up top that handles the slash sign out path we've supplied it with the sign out component as a child. We've done a similar thing further down with the root that handles a single forward slash. This one renders the dashboard components. Notice the exact attribute here. Without this we would likely catch some undesirable roots or unknown roots that would then display a dashboard when ideally we want some different behavior such as a 4 or 4 page.
[23:53 - 24:43] Speaking of which under here we have a lonely root component that has no attributes. How this works is that if no other roots have been matched this root will be triggered and its child rendered out to the user. For example if we visited slash users slash spoons which we know definitely doesn't exist that root would be caught here and the simple h1 element displayed. We'll see this in action during our demo in a moment. Hop in back up a little bit. Let's take a look at the interesting block in the middle of the JSX. This is another way you can handle rooting using dynamic or programmatic rendering. With our flattened all roots array we can call upon the trusty map function to iterate through each root object. For each item we output a root component assigning various root object properties to corresponding component attributes. We've added in the key attribute which is a react staple when rendering components as part of a loop. It helps react identifying keep track of those components.
[24:44 - 26:35] Notice how we're using the render attribute on the root component instead of the component attribute. Using an inline function passed to the render attribute prevents unnecessary re-renders of any components we passed to it. We could have also passed in the matching components as children but this way is neater, self-contained and highlights the different options that you have available via react router. Also take note of how we're looping over the root items in a single loop. This is the result of flattening the root array data earlier in the component. Without taking this step we would have had to loop through each top level root object then check for sub-navigation items loop through those and so on each time rendering a root component. You could do it that way of course and it would work just the same but the JSX becomes very cluttered and hard as a read. What 's nice about this programmatic routing approach is that it can handle virtually any number of roots that we throw at it without having to change any of our code. If we want to add new roots we can amend the roots.js file and that's it the navigation will be updated and the matching root will be handled appropriately. This approach won't work for every project and every scenario but you can see how flexible and declarative both react and react root are. They also allow you to compose your components however you wish to suit your coding style and project needs. With everything done and dusted in the app component it should look like this. So running the demo enough code time to run our app. Fire up the terminal and run the yarn style command and you'll see the finished app in the browser. Take a browse through the various sections using the navigation menu in the header. Notice how the URL updates as you do so along with the page contents displayed on screen. If you head over to any of the user pages notice how our sidebar is populated with other sibling links for this section and if we move through these pages notice the nav link component in action. Automatically updating the currently active page highlighting it in the sidebar.
[26:36 - 26:41] In the next lesson we've got an augment our base app here with some simulated user data and adding some dynamic parameterized rooting.