React-Router-Dom Routes

We use the React-Router library to enable client-side routing, allowing the browser to manage page navigation without requesting new HTML documents from the server.

Unlike server-side routing, which involves full page reloads, React Router updates components and URLs on the client-side based on route configurations.

//Not included in React we install its components
npm i react-router-dom

//We change the name of Browser
import {
  BrowserRouter as Router,
  Link,
  Route,
  useParams,
  Routes,
  Outlet,
  useRouteError
} from "react-router-dom";

We configure the <Route/> URL path and element component in Router>Routes, any component containing React-Router components has to be inside of <Router/>.

The component outside the <Router> will remain during all route URL updates.

//The "/" component will be the initial render of the page
import Primo from './components/Primo';
import Secondo from './components/Secondo';
import Home from './components/Home';

return (
  <div>
    <Title />

    <Router>
      <Navi />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path=":primo" element={<Primo />} />
        <Route path=':primo/fisso1' element={<Secondo />} />
        <Route path=':primo/:secondo' element={<Secondo />} />
      </Routes>
    </Router>
  </div>
);
Fixed and Variable routes

Using <Outlet> on nested and /* paths in React Router

Let's check the properties of nested <Routes> and the properties of the /* catch-All parameter.

<Routes>
  <Route path="/" element={<Primo />}>
    <Route path=":variable/*" element={<Secondo />} />
  </Route>
  
  <Route path=":variable/primo/*" element={<Primo />} />
  <Route path=":variable/:altro" element={<Terzo />} />
</Routes>

On reload Route components replace any previous element inside the <Router>. A parent Route component can render its child Route using <Outlet>, but any new <Route> that's not included in the nesting won't render any previous component.

//The child component will share the <Outlet/> position
<div>
  <Link to="edit"> Variable 1< </Link>
  <Link to="edited"> Variable 2< </Link>
  <Link to="etedi"> Variable 3< </Link>
</div>
<Outlet />
Variable <Route/> path with <Outlet/> and absolute paths

The useParams() hook and nested <Routes on object arrays.

We access the current Route URL path with the useParams() hook, which returns a key-value object with the route path name and its url parameter respectively.

//We can directly destruct for the route values
let variable = useParams()  //{variable: 'Primo', *: ''} 
let { variable } = useParams();           //Primo
let articoli = useParams()  //{variable: 'Primo', *: 'useref', articolo: 'useref'}
let { variable, articolo } = useParams(); //Primo useref

<Routes>
  <Route path=":variable/*" element={<Topic />} />
  <Route path=":variable/:articolo" element={<Final />} />
</Routes>

Instead of using the <Outlet> component we can nest routes by adding <Routes> on a element component.

//Both need the /* and don't repeat <Router>
//app.js
<Router>
  <Routes>
    <Route path="/*" element={<House />} />
  </Routes>
</Router>

//House.js
<Routes>
  <Route path=":variable/*" element={<Topic />} />
</Routes>
[Component] array render and content filter on useParams() path parameter

We render each object's name and set their id as :variable route path.

//Home.js
//We loop each array object
import { getSites } from "../Content";

<div className="d-block">
  <ul>
    {topico.map(({ id, name }) => (
      <li key={id}>
        <Link to={id}> {name} moree?</Link>
      </li>
    ))}
  </ul>
</div>

We can add an extra component as a fixed background and components for each avaiable route.

//Topic.js
//The component array and extra fixed component
//aren't needed if there is no difference between the components
const { variable } = useParams();

const modules = {
  Primo,
  Secondo,
  Terzo,
};

const Module = modules[variable];

<div className="d-block">
  <h1 className="text-center"> {variable} </h1>

  <Module fonte={variable} />
  //<Primo>	//also the props is optional
</div>

Each singular component shares the same filter structure, we render each array id as a new <Route> and include the next component as child component.

//Primo.js
//Both useParams() and props parameters work the same
import { getResor } from "../Content";

let risorsa = getResor(prop.fonte);
const { variable } = useParams();

<div>
  <ul>
    {risorsa.resources.map((id) => (
      <li key={id.id}>
        <Link to={id.id}>{id.name}</Link>
      </li>
    ))}
  </ul>
  <Routes>
    <Route path=":articolo" element={<Final />} />
  </Routes>
</div>

The useParams() values from the :variable <Route> filter and render the content.

import { getDesc } from "../Content";
const { variable, articolo } = useParams();
let { name, description } = getDesc({ variable, articolo });

<div>
  <h4> {name}, {description} </h4>
</div>
Router variable Routes with variable components

The useLocation() hook and properties

The <Router> can contain any DOM tag (while <Routes> can only contain <Route/>).

//Link tags and components work only if rendered inside Router
<Router>
  ...  //Any DOM element here will be fixed in any route
  <Routes>
    <Route path="/primo" element={ <Primo/> } >
      <Route path="primo1" element={ <Secondo/> } />
    </Route>
    <Route path="/primo/secondo/:edit/*" element={ <Terzo/> } />
  </Routes>
</Router>

Instead of the deprecated useHistory(), we import the useLocation() hook to access the current location object. It returns the URL pathname, a unique route key, an indiritation state, and both the search(?=) and the hash(#) in the URL.

The useLocation() hook updates only if a router-dom component is triggered. The browser navigation buttons update the URL using the history API and the <Router/> component tree, but it's not a router event.

import {
  useLocation 
} from "react-router-dom";

const location = useLocation();
console.log( location.pathname ) 	//  /primo

The search property returns the search queryString from the URL.

//useLocation().pathname won't return the search query
<Link to="secondo/fine?place=anderville&sort=asc">
  Imperative Route 
</Link>

let posto = useLocation()
console.log( posto.search )  //search: "?place=anderville&sort=asc"

The URLSearchParams is a built-in javascript interface for the query strings in the URL, It allows us to access and edit the returned key/value pairs.

const queryParams = new URLSearchParams(posto.search);
const someParam = queryParams.get('place');

const [via, setVia] = useState( someParam || 0 )	//andreville

Unlike useParams(), the useLocation() pathname includes both the static and dynamic routes.

//From, <Route path="/minni/pop/:fnaf/*" element={ <Terzo/> } />

const { pathname } = useLocation();  // /minni/pop/variegato
const variable = useParams();        //{fnaf: 'variegato', *: ''}

The useNavigation() sibling <Route> and localStorage

The <Link> handles imperative redirecting, while the useNavigate() is for programatical redirecting.

The useNavigate() hook returns a function that redirects the user in response to a variety of events, not just clicking a link, its 2 arguments are the route path and the options object.

The replace property resets the URL to the current path and disables the previous route, while state is passed and accessed with useLocation().

//We can reset the scroll position with <ScrollRestoration> on dataBrowser
//replace is false by default
let viaggio = useNavigate()

function giro(){
  viaggio("primo3", {state: {primo: "here"}, replace: true} )
}

<button className="btn btn-primary" onClick={()=> giro()}>
  Programatically redirect
</button>

//on primo3
console.log( useLocation().state )	//{primo: 'here'}

A useNavigate() relative path will use history API to update the URL without updating the useLocation(), similarly to the browser navigation buttons.

//Both will work only if the route has been already rendered

let via = useNavigate()
onClick={()=>( via(1) )}
onClick={()=>( via(-1) )}

We can also use the relative path as a shortcut between the sibling <Route>.

//from the primo/primo1 sibling route, without re-declaring the route
function test(){
  via("../primo2");
}

//Remember that <Outlet/> goes on the parent Route to make the children visible
<Routes>  
  <Route path="/primo" element={ <Primo/> } >
    <Route path="primo1" element={ <Secondo/> } />
    <Route path="primo2" element={ <Quarto/> } />
    <Route path='primo3' element={ <Terzo/> } />
  </Route>
</Routes>

The localStorage is a browser-provided web storage object, accessible by the client and the server, persistent across browser sessions and <Router> routes.

//After being set It can be accessed by any route
let forma={ first: 12, second: "badge" }
localStorage.setItem('stato', JSON.stringify(forma));

const storedFormState = localStorage.getItem('stato');
const parsedFormState = storedFormState ? JSON.parse(storedFormState) : null;

//We useState() to modify it more easily
const [formState, setFormState] = useState(parsedFormState || {});

useEffect(() => {
  localStorage.setItem('stato', JSON.stringify(formState));
}, [formState]);

//How we use input to edit the useState() bound to the localStorage()
<input id="cash" type="text" className="form-control" value={formState.first} 
  onChange={(e)=> setFormState((stat)=> ({...stat, first: e.target.value}) )}/>
  
localStorage.removeItem("stato");

1

Last updated

Was this helpful?