Skip to content

React Router v6

En esta sección aprenderemos a trabajar con rutas en nuestra aplicación con React.

⭐ Videos Premium ⭐

Esta sección es parte del curso en Udemy. Si quieres acceder a ella, puedes comprar el curso en Udemy: React + Firebase by bluuweb.

Router v6

  • react router v6
  • React Router es una biblioteca de enrutamiento del lado del servidor y del cliente con todas las funciones para React.
  • React Router se ejecuta en cualquier lugar donde se ejecute React; en la web, en el servidor con node.js y en React Native.
npm create vite@latest router-tutorial
cd router-tutorial
npm i
npm i react-router-dom@6
npm run dev

Enrutador

Si bien su aplicación solo usará un solo enrutador, hay varios enrutadores disponibles según el entorno en el que se esté ejecutando su aplicación.

picking a router

En v6.4, se introdujeron nuevos enrutadores que admiten las nuevas API de datos:

  • createBrowserRouter
  • createMemoryRouter
  • createHashRouter

Los siguientes enrutadores no admiten las API de datos:

  • <BrowserRouter>
  • <MemoryRouter>
  • <HashRouter>
  • <NativeRouter>
  • <StaticRouter>

BrowserRouter

Lo primero es lo primero, queremos conectar su aplicación a la URL del navegador: importarla BrowserRouter y renderizarla alrededor de toda su aplicación.

BrowserRouter Es el componente que se encarga de conectar su aplicación a la URL del navegador.

main.jsx

jsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";

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

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

Navbar.jsx

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

const Navbar = () => {
  return (
    <nav className="navbar navbar-dark bg-dark">
      <div className="container">
        <Link
          className="btn btn-outline-primary"
          to="/"
        >
          Inicio
        </Link>
        <Link
          className="btn btn-outline-primary"
          to="/blog"
        >
          Blog
        </Link>
        <Link
          className="btn btn-outline-primary"
          to="/contacto"
        >
          Contacto
        </Link>
      </div>
    </nav>
  );
};

export default Navbar;

App.jsx

jsx
import Navbar from "./components/Navbar";

const App = () => {
  return (
    <div>
      <Navbar />
      <div className="container">
        <h1>App</h1>
      </div>
    </div>
  );
};

export default App;

Agregar Rutas

  • src/routes/Blog.jsx
  • src/routes/Contacto.jsx
  • src/routes/Inicio.jsx

index.jsx

jsx
import Navbar from "./components/Navbar";
import Inicio from "./pages/Inicio";
import Blog from "./pages/Blog";
import Contacto from "./pages/Contacto";

import { Routes, Route } from "react-router-dom";

const App = () => {
  return (
    <>
      <Navbar />
      <Routes>
        <Route
          path="/"
          element={<Inicio />}
        />
        <Route
          path="/blog"
          element={<Blog />}
        />
        <Route
          path="/contacto"
          element={<Contacto />}
        />
      </Routes>
    </>
  );
};

export default App;

Rutas anidadas (Layout)

Las rutas anidadas se pueden usar para crear un diseño de página (Layout) que se repite en varias páginas.

Paso 1:

jsx
<Routes>
  <Route
    path="/"
    element={<LayoutPage />}
  >
    <Route
      index
      element={<Inicio />}
    />
    <Route
      path="/blog"
      element={<Blog />}
    />
    <Route
      path="/contacto"
      element={<Contacto />}
    />
  </Route>
</Routes>

Paso 2: Layout.jsx

jsx
import { Outlet } from "react-router-dom";

const LayoutPage = () => {
  return (
    <div className="container">
      <Outlet />
    </div>
  );
};

export default LayoutPage;

Ruta indice

jsx
<Routes>
    <Route path="/" element={<LayoutPage />}>
        <Route index element={<Inicio />} />
        <Route path="/blog" element={<Blog />} />
        <Route path="/contacto" element={<Contacto />} />
    </Route>
</Routes>
  • Observe que tiene el index prop en lugar de path.
  • Eso es porque la ruta del índice comparte la ruta del padre.
  • Las rutas de índice coinciden cuando una ruta principal coincide, pero ninguna de las otras secundarias coincide.
  • Las rutas de índice son la ruta secundaria predeterminada para una ruta principal.
  • Las rutas de índice se representan cuando el usuario aún no ha hecho clic en uno de los elementos de una lista de navegación.
jsx
import { NavLink } from "react-router-dom";

const Navbar = () => {
  return (
    <nav className="navbar navbar-dark bg-dark">
      <div className="container">
        <NavLink
          className="btn btn-outline-primary"
          to="/"
        >
          Inicio
        </NavLink>
        <NavLink
          className="btn btn-outline-primary"
          to="/blog"
        >
          Blog
        </NavLink>
        <NavLink
          className="btn btn-outline-primary"
          to="/contacto"
        >
          Contacto
        </NavLink>
      </div>
    </nav>
  );
};

export default Navbar;

404

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

const NoEncontrada = () => {
  return (
    <div>
      <h1>404</h1>
      <Link
        to="/"
        className="btn btn-outline-dark"
      >
        Inicio
      </Link>
    </div>
  );
};

export default NoEncontrada;
jsx
<Routes>
    <Route path="/" element={<LayoutPage />}>
        <Route index element={<Inicio />} />
        <Route path="/blog" element={<Blog />} />
        <Route path="/contacto" element={<Contacto />} />
        <Route path="*" element={<NotFound />} />
    </Route>
</Routes>

Parámetros

hooks/useFetch.js

js
import { useCallback, useEffect, useState } from "react";

export const useFetch = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const getData = useCallback(async () => {
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error("Error fetching data");
      }
      const data = await response.json();
      setData(data);
    } catch (error) {
      setError(error.message);
    } finally {
      setLoading(false);
    }
  }, [url]);

  useEffect(() => {
    getData();
  }, [getData]);

  return { data, loading, error };
};

Blog.jsx

jsx
import { Link } from "react-router-dom";
import { useFetch } from "../hooks/useFetch";

const Blog = () => {
  const { data, error, loading } = useFetch(
    "https://jsonplaceholder.typicode.com/posts"
  );

  if (loading) {
    return <h1>Loading...</h1>;
  }

  if (error) {
    return <h1>{error}</h1>;
  }

  return (
    <div>
      <h1>Blog</h1>
      {data.map((item) => (
        <h4 key={item.id}>
          <Link to={`/blog/${item.id}`}>
            {item.id} - {item.title}
          </Link>
        </h4>
      ))}
    </div>
  );
};

export default Blog;
jsx
<Route path="/" element={<LayoutPage />}>
    <Route index element={<Inicio />} />
    <Route path="/blog" element={<Blog />} />
    <Route path="/blog/:id" element={<Post />} />
    <Route path="/contacto" element={<Contacto />} />
    <Route path="*" element={<NotFound />} />
</Route>

Post.jsx

jsx
import { useParams } from "react-router";
import { useFetch } from "../hooks/useFetch";

const Post = () => {
  let params = useParams();

  const { data, error, loading } = useFetch(
    "https://jsonplaceholder.typicode.com/posts/" + params.id
  );

  if (loading) {
    return <h1>Loading...</h1>;
  }

  if (error) {
    return <h1>{error}</h1>;
  }

  return (
    <div>
      <h1>
        {data.id} - {data.title}
      </h1>
      <p>{data.body}</p>
    </div>
  );
};

export default Post;

Parámetros de búsqueda

  • React Router hace que sea fácil de leer y manipular los parámetros de búsqueda con useSearchParams
  • El useSearchParams se utiliza para leer y modificar la cadena de consulta en la URL de la ubicación actual.
  • Funciona de manera muy similar, React.useState() pero almacena y establece el estado en los parámetros de búsqueda de URL en lugar de en la memoria.

Blog.jsx

jsx
let [searchParams, setSearchParams] = useSearchParams();

useEffect(() => {
  // http://localhost:3000/blog?nombre=juan
  console.log(searchParams.get("nombre"));
  // setSearchParams({ nombre: "juan" });
}, [searchParams]);

Blog.jsx

jsx
import { Link, useSearchParams } from "react-router-dom";
import { useFetch } from "../hooks/useFetch";

const Blog = () => {
  let [searchParams, setSearchParams] = useSearchParams();

  const { data, error, loading } = useFetch(
    "https://jsonplaceholder.typicode.com/posts"
  );

  if (loading) {
    return <h1>Loading...</h1>;
  }

  if (error !== "") {
    return <h1>{error}</h1>;
  }

  const handleChange = (e) => {
    let filter = e.target.value;
    if (filter) {
      setSearchParams({ filter });
    } else {
      setSearchParams({});
    }
  };

  return (
    <div>
      <h1>Blog</h1>
      <input
        className="form-control mb-2"
        type="text"
        value={searchParams.get("filter") || ""}
        onChange={handleChange}
      />
      {data
        .filter((item) => {
          let filter = searchParams.get("filter");
          if (!filter) return true;
          let name = item.title.toLowerCase();
          return name.startsWith(filter.toLowerCase());
        })
        .map((item) => (
          <h4 key={item.id}>
            <Link to={`/blog/${item.id}`}>
              {item.id} - {item.title}
            </Link>
          </h4>
        ))}
    </div>
  );
};

export default Blog;

Netlify + queryParams

  • fuente
  • Crear archivo _redirects en public con:
/* /index.html 200
npm run build

Subir a netlify 🎉 https://www.netlify.com/