Level up your React components

Photo by Kevin Ku on Unsplash

Level up your React components

Refactoring and simplifying you React components

While I may be a noob in tech, I love that it allows me to write soooo much bad code. Seriously. Sometimes I just avoid my old repos because it's just that bad, even now. But, what I love, is that when I do get brave enough to open those repos, it gives me a chance to see what can be done to refactor that code, and put some new skills to use. I was hoping to go through React in a methodical way (beginning to end), but after my last post, I realized that I want to help myself concentrate on the concepts that I'm currently learning, and I'll get back to the older React stuff at a later date...but for now, let's discuss routes!

Hold up...

Before we jump straight into routes (and using Data APIs), we need to look at the createBrowserRouter function, and how that differs from the BrowserRouter that I was previously using. createBrowserRouter is a function that is provided by React to instantiate your router that allows you to take advantage of the data APIs. Previously, I was using BrowserRouter in my project, but that doesn't support the data APIs, and would load my components first, and then do a fetch of the data that was needed to render the elements within those components. This is not an ideal situation when you have entire sections of your webpage that rely on that data to create state for certain elements. Here is a simplified snippet of code using BrowserRouter:

import React from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
//Our imported external components
import Home from './Home'
import About from './About'

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path='/' element={<Home />} />
        <Route path='/about' element={<About />} />
      </Routes>
    </BrowserRouter>
  )
}

export default App;

In this snippet, we are defining our routes to our components from BrowserRouter -> Routes -> Route. This, however, cannot take advantage of the Data APIs, so our components will be rendered first, and then the data will be fetched and rendered only after the component loads, causing some components to lag on load times and providing a poor user experience.

What createBrowserRouter brings to the table, is the ability to use the DOM History API - the user's browser session history - through the global history object. In doing so, this provides access to methods and properties that allow you to move back and forth through the user's history (web pages) and allows for manipulation of the history stack. We are also able to access the Data APIs through loaders so that any data needed for that specific route element will be retrieved in parallel so that the data is available when the component loads, and not after it has loaded.

How do we use loaders?

It's always best to check out the documentation, but I know that can sometimes become a little overwhelming. When you have a component that requires data via 'fetch', we will need to use the useLoaderData hook within that particular component. This allows us to use the data that is returned from our route loader. So let's look at an example:

import React from 'react'
import { RouterProvider, createBrowserRouter, createRoutesFromElements, Route } from 'react-router-dom'
//These next three are imported component files
import Home from './Home'
import About from './About'
import Data, {loader as dataLoader} from './Data'

const router = createBrowserRouter(createRoutesFromElements(
        <Route>
            <Route path='/' element={<Home />} />
            <Route path='/about' element={<About />} />
            <Route path='/data' element={<Data />} loader={dataLoader}/>
        </Route>
))

function App() {
  return (
    <RouterProvider router={router}/>

  );
}

export default App

So, quite a bit has changed from our previous code. We have created a variable ('router') outside of our App component, and initialized it with the createBrowserRouter function and createRoutesFromElements helper function. The createRoutesFromElements function creates router objects from your JSX routes (ex. - <Route path="/" element={<Home />} /> ), so we don't have to manually create each of those objects. All of the data router objects that we created in our 'router' variable, are then passed to the RouterProvider component to be rendered. It essentially replaces BrowserRouter. We simply pass in the prop 'router', and assign it to our 'router' object variable created above. So far, so good. Let's move on to the loader for our Data component.

Loaders...

Loaders are functions that allow us to retrieve data (eg - a 'fetch') before the component is rendered. The benefit is that we don't have to load the component first, and then use a fetch request to get the data which then can be rendered to the page. So there are a couple things that we'll need in order to utilize our loaders. First, in the component where we want to use our data retrieved in the 'fetch' (in this case, our Data component), we will need to import the useLoaderData hook from 'react-router-dom'. useLoaderData is a hook introduced by React Router for fetching data on the server before rendering on the client side. We will also create our loader function in this component (but outside of our Data component), and call our fetch request.

import React from 'react'
import {useLoaderData} from 'react-router-dom'

/* This async function could also be stored in a separate .js file
    and the imported to this file to be called in the loader function */

export async function loader() {

    const res = await fetch('/api/data')
    if(!res.ok){
        throw {
            message: 'Failed to fetch data',
            statusText: res.statusText,
            status: res.status
        }
    }
        const data = await res.json()
        return data
}

export default Data(){
    return (
        const data = useLoaderData()

    /* Code for rendering data will go here. We can then access the 
       information that we need through our 'data
       variable to render what information we want in our component */
)

}

In this snippet, create our loader function (as an async function), and using if/else logic in case an error is thrown. If there is no error, the data is returned. We then create a 'data' variable in our component to be able to utilize the data that we are receiving from that fetch request. Back in our App component, we will need to import the loader function from our Data component, and also pass a prop to our Data component route within the App component, as shown below:

 import React from 'react'
import { RouterProvider, createBrowserRouter, createRoutesFromElements, Route } from 'react-router-dom'
import Home from './Home'
import About from './About'
import Data, {loader as dataLoader} from './Data'

const router = createBrowserRouter(createRoutesFromElements(
        <Route>
            <Route path='/' element={<Home />} />
            <Route path='/about' element={<About />} />
            <Route path='/data' element={<Data />} loader={dataLoader}/>
        </Route>
))

function App() {
  return (
    <RouterProvider router={router}/>

  );
}

export default App

Because we may end up using loader in multiple components, we can change the name when we import it (as seen above with when we do a named import loader as dataLoader). We then use dataLoader for our 'loader' prop in our Data component route. So, when the Data component is navigated to by the user, the loader function is called (our fetch request), the data from our fetch request is sent through our loader prop so that when the component is rendered, the data is already there! It's like magic, but real!

in conclusion...

As I do for pretty much every concept that I learn (because they usually don't stick in my brain the first time), use this in a small project, and then once you get it working, delete the whole thing and start over. Keep doing this until you feel comfortable using your new skills in larger or more complex projects. createBrowserRouter gives you more flexibility and an overall better user experience while streamlining your React applications. I would love to get feedback on anything you would do differently, or what you're currently learning!