A Guide to Routing in REACT with React Router

Routing can be a difficult aspect of building multipage React applications, especially as they become more complex. Fortunately, the React Router library provides a range of hooks and features that can help simplify this process and improve the overall development experience. Whether you need to handle complex routing scenarios or simply want a more streamlined approach to navigation, React Router has the tools you need to build efficient and effective applications.

In this article we are going to learn:

  • Installation and Set-up

  • Route declaration/configuration

  • Links in react-router

  • Nested Routes

  • Navigating Programatically in React

  • Dynamic Routing

  • Essential react-router hooks you should know

Installation and Set-up

installing react-router is pretty straightforward just like most npm packages all you have to do is run

npm install react-router-dom

this is enough to get you started after getting a successful installation message from npm

we will be working with this github repo app that showcases land on sale for a real estate company, you can clone and play around with it.

Routes configuration and definition.

To get started with defining our routes we import createBrowserRouter function and RouterProvider component from react-router-dom.

import {createBrowserRouter, RouterProvider} from 'react-router-dom'

The createBrowserRouter function accepts an array of objects containing paths and the elements to be rendered on that path. The RouterProvider is designed to receive and handle all of the defined routes in the application, ensuring that the appropriate elements are rendered when their corresponding paths are requested by the browser.

For instance, if we want to render the home page on the path '/' we could implement this in the following way

import React from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
//import the homepage componen
import HomePage from "./pages/HomePage";

const router = createBrowserRouter([{ path: "/", element: <HomePage /> }]);

// import './App.css';

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

export default App;

The createBrowserRouter can take multiple objects with all paths and components to render your application

You can also define your routes using JSX syntax in two other ways :

Using BrowserRouter

Using the browser router we can define paths for each of our JSX Components

here is how you can implement routing using the BrowserRouter

import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import HomePage from "./pages/HomePage";
import Listings from "./pages/Listings";



// import './App.css';

function App() {
  return (
    <>
     <BrowserRouter>
      <Routes>
       <Route path="/" element={</HomePage> }/>
       <Route path ="/" element={</Listings>} />
      </Routes>
     </BrowserRouter>
    </>

);
}

export default App;

This is not the most recommended way to handle routing, however, if you want to render pages individually you might make use of this method

Using CreateRoutesFromElements

The createRoutesFromElements also defines routes from JSX elements, below is the implementation

 import React from "react";
import {
  createBrowserRouter,
  RouterProvider,
  createRoutesFromElements,
  Route,
} from "react-router-dom";
import HomePage from "./pages/HomePage";
import Listings from "./pages/Listings";


const routeDefinitions = createRoutesFromElements(
  <Route>
    <Route path="/" element={<HomePage />} />
    <Route path="/listing" element={<Listings />} />
  </Route>
);


const router = createBrowserRouter(routeDefinitions);

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

export default App;

In the example above we create a routeDefinition object that we assign the routes defined using the createRoutesFromElements function from the JSX elements passed, We later pass the routeDefinitions to the createBrowserRouter to create an object that will be passed to the RouterProvider to handle navigation.

That is it for route definitions, you can check the react-router-dom docs for more details.

When navigating between pages in a web application, the "a href" HTML tag is commonly used. While this approach is generally functional, it has a significant drawback: each time a user clicks on a link to a new page, the entire application will reload. This resulted in the JavaScript being loaded again and HTTP requests being sent to the server, even if the new page only required a small amount of data to be fetched. This behavior could have a detrimental effect on the app's performance.

To address this issue, the Link component from the react-router-dom library was introduced. This component facilitates smooth transitions between pages by rendering the content on the client side, avoiding the need for a full page reload. Additionally, the Link component enables more efficient data fetching, as HTTP requests are only made if necessary. As a result, using the Link component can help to enhance the performance and user experience of web applications

import React from "react";
import {Link} from "react-router-dom";

const HomePage = () => {
  return (
    <>
//using a href traditional approach
    <div>
     <h2> WELCOME TO OUR lAND LISTING WEB APP TO VIEW LAND GOT TO <a       href="/all-listing"> LISTING </a> </h2>
      </div>

//using Link react-router approach
     <div>
     <h2> WELCOME TO OUR lAND LISTING WEB APP TO VIEW LAND GOT TO <Link to="/all-listings"> LISTINGS </Link>. </h2>
      </div>

      </>

};

export default HomePage;

It's generally recommended to use the Link component to handle navigation for performance and smooth transitions between pages in your React app.

Nested Routes

In React Router, nested routes are used to define routes that are children of other routes. This allows you to define a hierarchy of routes that map to the hierarchy of components in your application. Nested routes can enable us to have persistent components like a Navbar or any other components that might need to be present across multiple pages of your application.

Let's create a simple navigation component and a root component to wrap our routes

import React from "react";
import { Link } from "react-router-dom";

const Navigation = () => {
  return (
    <nav>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/listings">Listings</Link>
        </li>
      </ul>
    </nav>
  );
};

export default Navigation;

this is the simple navbar, we shall add styling later as we progress,

import React from "react";
import { Outlet } from "react-router-dom";
import Navigation from "../components/Navigation";

const Layout = () => {
  return (
    <>
      <Navigation />
      <Outlet />
    </>
  );
};

export default Layout;

This is a simple layout component that we can pass to our router object. Here we import an Outlet Component from react-router-dom that maps the place where child routes from the route definition will be rendered.

The Navigation Component will always be on top of every page in the application under all routes that will have this layout as its parent route, therefore, maintaining a consistent navigation menu.

We then implement the routing as follows in our App.js

import React from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import HomePage from "./pages/HomePage";
import Layout from "./pages/Layout";
import Listings from "./pages/Listings";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { index:true, element: <HomePage /> },
      { path: "/listings", element: <Listings /> },
    ],
  },
]);

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

export default App;

Route definitions can have a parent route and children routes, which means the children's route path will be appended to the parent route path, for example, if the parent root is "/home" for children's route path "/listings" will be "home/listings" on the browser. In our case for root path "/" the children's root path for the listing element will be "/listing". This is the nested routes concept, in a nutshell, we shall look at it more broadly as we progress.

Navigating Programmatically

Sometimes we may not always want to use Links and routes to navigate through the app, we might want to navigate automatically to another page after a successful HTTP request, for example when the user is logging in and is successful we might want to navigate the user to a dashboard. React Router comes with a useNavigate Hook which we can utilize to navigate the user to the page we want.

Here is an implementation:

import React, { useState } from "react";
import axios from "axios"
import {useNavigate} from "react-router-dom"

function LoginForm() {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const navigate = useNavigate()

  const handleSubmit = async (event) => {
    event.preventDefault();
    const loginCredentials = { username,password}
    const response = await axios({
     methods:"post",
     url:"https://www.somedummybackend.com/login",
     date:loginCredentials
     })
     if(response.status === 200){
       navigate("/homepage")
     }
     console.log(response)

    // Reset form
    setUsername("");
    setPassword("");
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input
          type="text"
          value={username}
          onChange={(event) => setUsername(event.target.value)}
        />
      </label>
      <br />
      <label>
        Password:
        <input
          type="password"
          value={password}
          onChange={(event) => setPassword(event.target.value)}
        />
      </label>
      <br />
      <input type="submit" value="Submit" />
    </form>
  );
}

export default LoginForm;

This is a simple code for a simple login function, The useNavigate() hook returns a navigate function that you can use to programmatically navigate to a different route, in the example above, after the user submits the login credentials we check if the response from the server has a status of 200, and if so we navigate to the homepage.

Dynamic routing

React router enables us to define dynamic route segments by adding a colon and an identifier of your choice after the colon in your route path, this might be important when you need to serve dynamic content based on an element property such as an id.This is demonstrated below


const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { index: true, element: <HomePage /> },
      { path: "/listings", element: <Listings /> },
      { path: "/listings/:listId", element: <ListingDetails /> },
    ],
  },
]);

From this exampleexmapleexmaple the path to the ListingDetails is now a dynamic path and the part after the colon makes it a dynamic path segment.

Let's see this in a more elaborate example, we shall create a listing of all available listings on our home page and we shall have a listing details page to view details of any listing the user will click.

import React from "react";
import { Link } from "react-router-dom";

const HomePage = () => {
  const listings = [
    {
      id: "1a",
      name: "Mwea ridges",
      description: "a  prime property for residential developments ",
      price: "ksh 5000",
    },
    {
      id: "2b",
      name: "Mlolongo ridges",
      description: "a  prime property for residential developments ",
      price: "ksh 10,000",
    },
    {
      id: "3c",
      name: "Syokimau ridges",
      description: "a  prime property for residential developments ",
      price: "ksh 15000",
    },
    {
      id: "4d",
      name: "Kitengela ridges",
      description: "a  prime property for residential developments ",
      price: "ksh 25000",
    },
    {
      id: "5e",
      name: "Ngong ridges",
      description: "a  prime property for residential developments ",
      price: "ksh 75000",
    },
  ];

  const properties = listings.map((lst, index) => (
    <div key={index}>
      <h2>{lst.name}</h2>
      <p>{lst.description}</p>
      <p>{lst.price}</p>
      <Link to={`/listings/${lst.id}`}> View Details</Link>
    </div>
  ));

  return (
    <>
      <h1>Welcome to Exotic Homes </h1>
      <p>discover the hottest properties in the market</p>
      {properties}
    </>
  );
};

export default HomePage;

This is the pretty simple homepage component that maps all properties from the dummy data and displays them. Our interest here is the Link to each product detail page on click of view page, We construct a dynamic route by adding the listing id to the path, this is already defined in our routes definition.

Let's look at the Listing Detail page

import React from "react";
import { useParams } from "react-router-dom";

const ListingDetails = () => {
  const params = useParams();

  return (
    <>
      <h2> Listing Details page</h2>
      <p> you are viewing the listing of id: {params.listId}</p>
    </>
  );
};

export default ListingDetails;

We introduce the userParams hook that gives us a params object that contains every dynamic path segment that we defined in our routes. we can access the id in the accessing it using the same name we defined in our root definition, that is the listId.

We can do more than just display the id, we can use the id to make an API call and get all the data associated with the listing, we will see this later on.

Essential React Router Hooks You Should Know About

There are quite a few hooks that come with react-router that come in handy to accomplish some critical tasks in your application such as fetching data from the backend, submitting data, error handling, and handling transition states.

Fetching data using the loader

There is an extra property that we can add to our route definitions called loader, we pass a function to the loader property that will call our API and pass the data to the component being rendered by the path.

below is an example:

import React from "react";
import { useLoaderData } from "react-router-dom";
import { Link } from "react-router-dom";

const Listings = () => {
  const data = useLoaderData();
  const listings = data.listings;

  const properties = listings.map((lst, index) => (
    <div key={index}>
      <h2>{lst.name}</h2>
      <p>{lst.description}</p>
      <p>{lst.price}</p>
      <Link to={`/listings/${lst.id}`}> View Details</Link>
    </div>
  ));

  return (
    <>
      <h2>Here are the properties on sale </h2>
      {properties}
    </>
  );
};

export default Listings;

export async function loader() {
  const response = await fetch("http://localhost:8080/listings");

  if (!response.ok) {
    // ...
  } else {
    return response;
  }
}

We create a loader function that will call the API and return the response, we then import this loader function into our route definitions file as shown below,

import React from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import HomePage from "./pages/HomePage";
import Layout from "./pages/Layout";
import Listings, { loader as listingsLoader } from "./pages/Listings";
import ListingDetails from "./pages/ListingDetails";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { index: true, element: <HomePage /> },
      { path: "/listings", element: <Listings />, loader: listingsLoader },
      { path: "/listings/:listId", element: <ListingDetails /> },
    ],
  },
]);

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

export default App;

We import the loader function as listingsLoader and pass it to the Listings component to pass the data to the component. We access the data by using the useLoaderData hook that returns data from the loader.

Error handling

We can also have an extra route definition property for "errorElement" that renders an Error Page if we encounter errors in our app.

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <HomePage /> },
      { path: "/listings", element: <Listings />, loader: listingsLoader },
      { path: "/listings/:listId", element: <ListingDetails /> },
    ],
  },
]);

We create an ErrorPage and pass it to the errorElement property and if experience any error in route definitions the error page will be rendered. We can do more with error handling than just displaying an error page, there is a useErrorRoute hook that comes with React Router that gives us an error object that we can extract data of the error that occurred.

Here is an example of how to handle errors

export async function loader() {
  const response = await fetch("http://localhost:8080/listingsghfj");
  if (response.status !== 200) {
    throw json(
      { message: "Could not fetch listing." },
      {
        status: 500,
      }
    );
  } else {
    return response;
  }
}

I have messed up the backend URL so that it will run to an error. We then make use of the json() utility function that enables us to create a response object with a custom message and the response status code.

import React from "react";
import { useRouteError } from "react-router-dom";
import Navigation from "../components/Navigation";

const ErrorPage = () => {
  const error = useRouteError();
  console.log(error.data.message);
  console.log(error.status);

  let message;
  let title;
  if (error.status === 404) {
    title = "NOT FOUND";
    message = error.data.message;
  }
  if (error.status === 500) {
    title = "Server Error ";
    message = error.data.message;
  }

  return (
    <>
      <Navigation />
      <h2>{title}</h2>
      <p>{message}</p>
    </>
  );
};

export default ErrorPage;

This is the error page that will be rendered in case of an error, we use the useRouteError hook to get hold of the error object thrown by our loader function, and we then use the data to tell the user what went wrong.

Dynamic routing using loaders

In a previous section, we demonstrated how we can handle dynamic routing by including a dynamic path segment and we also learned how we can extract the dynamic path segment using the useParams hook. It's ideal that after extracting the id using the use params we typically call the backend with the id appended on the endpoint and get data that we can manage as a state, and pass it directly to components or to other functions. Loader functions can take request and params object as parameters and we can get hold of the id using the params object and fetch the data using our loader function. Here is an example:

import React from "react";

const Listing = ({ listing }) => {
  return (
    <div>
      <h2>{listing.name}</h2>
      <p>{listing.description}</p>
      <p>{listing.price}</p>
      <button>Purchase Property</button>
    </div>
  );
};

export default Listing;
import React from "react";
import { useLoaderData, json } from "react-router-dom";
import Listing from "../components/Listing";

const ListingDetails = () => {
  const data = useLoaderData();

  return (
    <>
      <Listing listing={data.listing} />
    </>
  );
};

export default ListingDetails;

export async function loader({ request, params }) {
  const id = params.listId;
  const response = await fetch("http://localhost:8080/listings/" + id);
  console.log(response);

  if (response.status !== 200) {
    throw json(
      { message: "Could not fetch listing Details." },
      {
        status: 500,
      }
    );
  } else {
    return response;
  }
}
const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <HomePage /> },
      { path: "/listings", element: <Listings />, loader: listingsLoader },
      {
        path: "/listings/:listId",
        element: <ListingDetails />,
        loader: detailsLoader,
      },
    ],
  },
]);

The above code samples show how you could implement dynamic routing with loaders handling data fetching.

Accessing data from other routes

As our application grows you might get into a scenario where two pages might share the same data, now you might want to have a loader for each of the pages and there is nothing wrong with that approach, however, React Router comes with a useRouteLoaderData hook that you might want to utilize in such a situation. For example, we can have two pages EditListing and Listing details that both fetch data from the same API endpoint. Let's see how we can implement this

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <HomePage /> },
      {
        path: "listings",
        children: [
          {
            index: true,
            element: <Listings />,
            loader: listingsLoader,
          },
          {
            path: ":listId",
            id: "listing-details",
            loader: detailsLoader,
            children: [
              { index: true, element: <ListingDetails /> },
              { path: "edit", element: <EditListing /> },
            ],
          },
        ],
      },
    ],
  },
]);

I have changed the route definition from using absolute paths to relative paths, absolute paths are defined using a "/ "before the path name, while for relative paths we just write the path name and the path will be appended to the parent path in the route definition. We define our loader function at the parent route of the ":listId" path so as to have the data available in both children routes, we also add an id property to the route definition and then we pass the id when calling the useRouteLoaderData Hook to specify which route loader function we want to call to get the data, see the below examples:

import React from "react";
import { useRouteLoaderData } from "react-router-dom";
import ListingForm from "../components/ListingForm";

const EditListing = () => {
  const data = useRouteLoaderData("listing-details");
  console.log(data.listing);
  return (
    <>
      <ListingForm listing={data.listing} />
    </>
  );
};

export default EditListing;
import React from "react";
import { useRouteLoaderData, json } from "react-router-dom";
import Listing from "../components/Listing";

const ListingDetails = () => {
  const data = useRouteLoaderData("listing-details");
  console.log(data.listing);

  return (
    <>
      <Listing listing={data.listing} />
    </>
  );
};

export default ListingDetails;

export async function loader({ request, params }) {
  const id = params.listId;
  const response = await fetch("http://localhost:8080/listings/" + id);
  console.log(response);

  if (response.status !== 200) {
    throw json(
      { message: "Could not fetch events." },
      {
        status: 500,
      }
    );
  } else {
    return response;
  }
}

Using the id in the useRouteLoaderData hook we are able to get the data from the route with the id property we passed.

Data submission using actions in React Router

We can also handle data submission to our APIs using actions in React Router, just like we have a loader property in our root definition we can also have an action property where we pass a function to send data to a backend. Let's see how we can add a new listing to our project using actions.

import React from "react";
import { Form } from "react-router-dom";

const ListingForm = ({ listing }) => {
  return (
    <Form method="post">
      <p>
        <label htmlFor="title">Name</label>
        <input
          id="name"
          type="text"
          name="name"
          required
          defaultValue={listing ? listing.name : ""}
        />
      </p>
      <p>
        <label htmlFor="description">Description</label>
        <input
          id="description"
          type="text"
          name="description"
          required
          defaultValue={listing ? listing.description : ""}
        />
      </p>
      <p>
        <label htmlFor="price">Price</label>
        <input
          id="price"
          type="text"
          name="price"
          required
          defaultValue={listing ? listing.price : ""}
        />
      </p>

      <div>
        <button type="button">Cancel</button>
        <button>Save</button>
      </div>
    </Form>
  );
};

export default ListingForm;
import React from "react";
import ListingForm from "../components/ListingForm";
import { redirect, json } from "react-router-dom";

const AddListing = () => {
  return (
    <>
      <ListingForm />
    </>
  );
};

export default AddListing;

export async function action({ request, params }) {
  const data = await request.formData();

  const listingData = {
    name: data.get("name"),
    description: data.get("description"),
    price: data.get("price"),
  };

  const response = await fetch("http://localhost:8080/listings", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(listingData),
  });

  if (response.status !== 200) {
    throw json({ message: "Could not save listing." }, { status: 500 });
  }

  return redirect("/listings");
}

In our first file, we create a form component to handle the data input, however, you might notice we are importing a Form component from React Router, the Form component helps return the data from the form for every input field with a name attribute, we get hold of this through the request object in our action function as shown above, we then call data.get with the name attribute of our input field in the form to get the data which we can use to construct an object to pass to the request body of our API call as shown.

import AddListing, { action as newListing } from "./pages/AddListing";
const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <HomePage /> },
      {
        path: "listings",
        children: [
          {
            index: true,
            element: <Listings />,
            loader: listingsLoader,
          },
          {
            path: ":listId",
            id: "listing-details",
            loader: detailsLoader,
            children: [
              { index: true, element: <ListingDetails /> },
              { path: "edit", element: <EditListing /> },
            ],
          },
          { path: "new", element: <AddListing />, action: newListing },
        ],
      },
    ],
  },
]);

We add our action property to the path definition just as we did with the loader function, we just the action property instead.

Deleting using actions

import React from "react";
import { Link, useSubmit } from "react-router-dom";

const Listing = ({ listing }) => {
  const submit = useSubmit();

  function deleteHandler() {
    const confirm = window.confirm("are you sure you want to delete");
    if (confirm) {
      submit(null, { method: "DELETE" });
    }
  }
  return (
    <div>
      <h2>{listing.name}</h2>
      <p>{listing.description}</p>
      <p>{listing.price}</p>
      <Link to="edit">Edit </Link>
      <button onClick={deleteHandler}>Delete</button>
    </div>
  );
};

export default Listing;

The useSubmit hook is used to create a function that can be used to make HTTP requests to a server. It takes data and method parameters and an optional action parameter that specifies the URL path to send the request to. When the submit function is called, it sends the specified data to the server using the specified method (in this case, "DELETE").

import React from "react";
import { useRouteLoaderData, json, redirect } from "react-router-dom";
import Listing from "../components/Listing";

const ListingDetails = () => {
  const data = useRouteLoaderData("listing-details");
  console.log(data.listing);

  return (
    <>
      <Listing listing={data.listing} />
    </>
  );
};

export default ListingDetails;

export async function loader({ request, params }) {
  const id = params.listId;
  const response = await fetch("http://localhost:8080/listings/" + id);
  console.log(response);

  if (response.status !== 200) {
    throw json(
      { message: "Could not fetch events." },
      {
        status: 500,
      }
    );
  } else {
    return response;
  }
}

export async function action({ request, params }) {
  const id = params.listId;
  const method = request.method;
  const response = await fetch("http://localhost:8080/listings/" + id, {
    method: method,
  });
  console.log(response);

  if (response.status !== 200) {
    throw json(
      { message: "Could not delete events." },
      {
        status: 500,
      }
    );
  } else {
    return redirect("/listings");
  }
}

The submit function will call this action function, which will send the HTTP request to our backend and if the request is successful we shall redirect to the listings.

import ListingDetails, {
  loader as detailsLoader,
  action as deleteAction,
} from "./pages/ListingDetails";

we import the action the same way we import the loader functions

{
            path: ":listId",
            id: "listing-details",
            loader: detailsLoader,
            children: [
              {
                index: true,
                element: <ListingDetails />,
                action: deleteAction,
              },
              { path: "edit", element: <EditListing /> },
            ],
          },

We then pass the action function to the route definition.

The useFetcher Hook

Whenever we engage an action or a loader our apps transition to the particular route we are calling that function, this is not a problem but what if we need to call an action without transitioning to that page? We might find ourselves in scenarios where we ask for user input from a different page and we do not need to transition to that page, we might prompt the user to a feedback form about the site usability or ask for ratings of our app and we should handle this under the hood without transition, in such a scenario we can utilize the useFetcher hook to trigger an action without loading the element for that route. This hook gives us a fetcher object that avails a couple of methods to us.

import { useEffect } from 'react';
import { useFetcher } from 'react-router-dom';



function SiteUsabilityForm() {
  const fetcher = useFetcher();
  const { data, state } = fetcher;

  useEffect(() => {
    if (state === 'idle' && data && data.message) {
      window.alert(data.message);
    }
  }, [data, state]);

  return (
    <>
    <h2> Please Help us Better your experience by filling this Form           </h2>
    <fetcher.Form
      method="post"
      action="/feedback"
      className={classes.newsletter}
    >
       <label htmlFor="description">How is overall site performance in terms of speed </label>
        <input
          id="overall_performance"
          type="text"
          name="overall_performance"
          required

        />
 <label htmlFor="description">Do you like the application layout </label>
        <input
          id="layout"
          type="text"
          name="layout"
          required

        />
 <label htmlFor="description">Have Experienced any difficulties with accesing any feature</label>
        <input
          id="accessibility"
          type="text"
          name="accessibility"
          required

        />
      <button>Submit</button>
    </fetcher.Form>
    </>
  );
}

export default SiteUsabilityForm;

The fetcher object gives us access to the, this type of form will call the action for this route without triggering a transition that will call the element for this route. The fetcher object also gives us access to data and state objects. the data object contains the data that we return from the action function and the state contains four states, either idle if there is no operation going on, loading to show that it's fetching data from the server, and submitting to show that it's sending data to the server.

import SiteUsabilityForm from '../components/SiteUsabilityForm';


function Userfeedback() {
  return (
      <SiteUsabilityForm />
  );
}

export default Userfeedback;

export async function action({ request }) {
  const data = await request.formData();
  const feedbackData={
   overall_performance = data.get('overall_performance');
   layout = data.get('layout');
   accessibility= data.get('accessibility');
}

  const response = await fetch("http://localhost:8080/listings/feedback")
if (response.status === 200){
  return { message: 'Thank you for your feedback!' };
}else{
return { message: 'Could not process your response!' };
}
 {
        path: 'feedback',
        element: < Userfeedback/>,
        action: userfeedbackAction,
      },

The action in the feedback route will be called by useFetcher but the element will not be rendered, that is how you handle under-the-hood tasks using React Router.

The defer hook

Typically for React router to render a component, it first calls any loader function to fetch data that is required by the component before displaying the page, in case of delay to the server the user might stare at a blank screen for some seconds depending on the payload. This might not be the best experience we would want for our user, instead, we might want to show a spinner or a message to display to the user before data fetching is done, additionally, we might also want to render some components as we wait for the data. We can use the defer hook to defer a function and display a fallback component until when we get the data to resolve our element. Here is an example,

import React, { Suspense } from "react";
import {
  useRouteLoaderData,
  json,
  redirect,
  defer,
  Await,
} from "react-router-dom";
import Listing from "../components/Listing";

const ListingDetails = () => {
  const { listing } = useRouteLoaderData("listing-details");
  console.log("this is the", listing);

  return (
    <>
      <Suspense fallback={<p>Loading .....</p>}>
        <Await resolve={listing}>
          {(loadedListing) => <Listing listing={loadedListing} />}
        </Await>
      </Suspense>
    </>
  );
};

export default ListingDetails;

export async function getDetails(id) {
  const response = await fetch("http://localhost:8080/listings/" + id);
  console.log(response);

  if (response.status !== 200) {
    throw json(
      { message: "Could not fetch listing." },
      {
        status: 500,
      }
    );
  } else {
    const resData = await response.json();

    return resData.listing;
  }
}

export async function loader({ request, params }) {
  const id = params.listId;
  return defer({
    listing: getDetails(id),
  });
}

export async function action({ request, params }) {
  const id = params.listId;
  const method = request.method;
  const response = await fetch("http://localhost:8080/listings/" + id, {
    method: "DELETE",
  });
  console.log(response);

  if (response.status !== 200) {
    throw json(
      { message: "Could not delete listings." },
      {
        status: 500,
      }
    );
  } else {
    return redirect("/listings");
  }
}

We import defer and Await component from React Router, we use the await component to render defered component but it should be wrapped in a React Suspense component to render a fall back component before the Await component resolves.

With the defer function, we first define a separate function to fetch the necessary data. This function is then called within the loader function, and the returned data is wrapped in a defer function. By doing this, the data can be loaded asynchronously in the background while the route transition is taking place. This approach ensures that components are displayed as soon as possible, even while the data is still loading.

Conclusion

React Router is a robust and popular solution for handling routing in React applications. It provides a flexible and declarative syntax for defining routes, supports advanced features that come in handy to help accomplish important tasks and provide a great developer experience. You can aslo check the docs to learn more at React Router