Clonar React Router en React es el reto final para entender cómo funciona la navegación SPA por dentro. Vas a construir tus propios componentes HashRouter, Route y Link, además de custom hooks como useLocation y useNavigate, replicando la lógica de react-router-dom desde cero.
Este ejercicio cierra el ciclo de aprendizaje sobre enrutamiento en JavaScript: pasaste de un router vainilla basado en location.hash a una versión componetizada en React, lista para reusar en cualquier proyecto.
Qué significa clonar React Router en React
La idea es replicar el comportamiento de react-router-dom sin instalarlo. En el reto anterior ya construiste un router en JavaScript puro que escuchaba location.hash y renderizaba contenido distinto según la ruta. Ahora toca llevar esa misma lógica al mundo de los componentes.
¿Qué hace un HashRouter? Lee el valor de location.hash en la URL y decide qué componente renderizar. Cuando el hash cambia, el router actualiza la vista sin recargar la página.
No necesitas que el código quede perfecto en el primer intento. La consigna es clara: primero haz que funcione, después lo refactorizas.
Cómo construir HashRouter, Route y Link paso a paso
El reto principal se compone de tres piezas que trabajan juntas. Cada una tiene una responsabilidad concreta dentro del sistema de navegación.
Cómo crear tu propio componente HashRouter
El HashRouter es el contenedor que envuelve toda la app y mantiene el estado de la ruta actual. Su trabajo es escuchar los cambios en location.hash y notificar a los componentes hijos para que renderen lo que corresponde.
Puedes apoyarte en el Context API de React para compartir la ruta activa con cualquier componente anidado. Así, Route y Link podrán leer y modificar esa ruta sin pasar props manualmente.
Cómo funciona el componente Route con la propiedad path
El componente Route recibe una propiedad path y decide si renderiza o no su contenido. Si el path hace match con el hash actual, muestra el componente; si no, devuelve null.
¿Cómo decide Route qué renderizar? Compara su prop path con el hash actual de la URL. Si coinciden, renderiza el componente hijo; si no, no devuelve nada.
El nombre de la prop lo eliges tú. Puede ser path, route o lo que prefieras, siempre que mantengas consistencia.
Por qué necesitas un componente Link en lugar de una etiqueta a
Una etiqueta a normal dispara una nueva petición HTTP y rompe la experiencia SPA. El componente Link evita eso: usa JavaScript para cambiar la URL y, en consecuencia, la ruta renderizada, sin recargar el HTML.
Puedes usar la etiqueta que prefieras por dentro, lo importante es que el cambio de ruta ocurra en cliente. Esa es la diferencia clave entre navegación tradicional y navegación SPA.
Cómo replicar los custom hooks useLocation y useNavigate
Si quieres llevar el reto un paso más allá, clona también los custom hooks de react-router-dom. Estos hooks permiten interactuar con el router desde cualquier componente funcional sin pasar props.
useLocation: devuelve información sobre la ruta actual, como el hash o el pathname.
useNavigate: retorna una función que cambia la URL programáticamente desde JavaScript.
Otros hooks que quieras explorar, como los relacionados con parámetros o búsqueda.
La lógica detrás es la misma del HashRouter: leer y escribir sobre location.hash, y notificar los cambios al resto de la app mediante contexto o estado.
Qué aprenderás al terminar este reto de React
Más allá del código, este ejercicio te deja claridad sobre cómo funcionan las librerías que usas todos los días. Entender el motor interno de React Router cambia tu forma de leer documentación y de depurar bugs.
Manejo de location.hash como fuente de verdad para la ruta.
Composición de componentes con Context API para compartir estado global.
Diferencia entre navegación tradicional con etiqueta a y navegación SPA con un Link personalizado.
Creación de custom hooks que encapsulan lógica reutilizable.
Cuando termines, comparte tu versión en los comentarios. La idea es ver qué soluciones aparecen en la comunidad, comparar enfoques y mejorar iteración tras iteración hasta tener una versión sólida de React Router hecha por ti.
Lo logré! Creé mi propio Router, con link y la posibilidad de usar HashRouter.
Al momento de comentar, no hay ninguna solución en los comentarios. Los invito a que prueben el reto! Y si ya lo hicieron, a que lo compartan.
Fue un muy buen reto. Casi me rindo muchas veces, pero finalmente lo terminé. Dejo mi solución.
import{ useContext }from'react'import{PathContext}from'./Context'exportfunctionRoute({ path, element }){const{ currentPath, routes }=useContext(PathContext)//Push all routes except '*' (not found) to routes[]const routeInRoutes = routes.includes(path)if(!routeInRoutes && path !='*') routes.push(path)//returning in matchif(currentPath == path)return element
// Not found caseconst currentPathInRoutes = routes.includes(currentPath)if(path =='*'&&!currentPathInRoutes)return element
}
Link.jsx
import{ useContext }from'react'import{PathContext}from'./Context'const styles ={textDecoration:'underline',color:'blue',cursor:'pointer',}exportfunctionLink({ to, children }){const{ navigate }=useContext(PathContext)return(<a style={styles} onClick={()=>navigate(to)}>{children}</a>)}
import{Link}from'../router/Link'import{Route}from'../router/Route'import{Router}from'../router/Router'exportfunctionApp(){return(<Router><Route element={<HomePage/>} path="/"/><Route element={<BlogPage/>} path="/blog"/><Route element={<NotFound/>} path="*"/></Router>)}functionHomePage(){return(<><h2>You are in the home</h2><Link to="/blog">Go to blog</Link><p></p><Link to="/random-link">Go to non-existent page</Link></>)}functionBlogPage(){return(<><h2>You are in the blog</h2><Link to="/">Back to home</Link></>)}functionNotFound(){return(<><h2>Page not found</h2><Link to="/">Go to home</Link></>)}
AppWithHash.jsx
import{Link}from'../router/Link'import{Route}from'../router/Route'import{HashRouter}from'../router/HashRouter'exportfunctionAppWithHash(){return(<HashRouter><Route element={<HomePage/>} path="/"/><Route element={<BlogPage/>} path="/blog"/><Route element={<NotFound/>} path="*"/></HashRouter>)}functionHomePage(){return(<><h2>You are in the home</h2><Link to="/blog">Go to blog</Link><p></p><Link to="/random-link">Go to non-existent page</Link></>)}functionBlogPage(){return(<><h2>You are in the blog</h2><Link to="/">Back to home</Link></>)}functionNotFound(){return(<><h2>Page not found</h2><Link to="/">Go to home</Link></>)}
Todo se ve tan fácil y obvio una vez terminado jajaja... para nada se sintió así. Como siempre, tuve que volver a revisar todo mi conocimiento, consultar muchas fuentes y probar mil cosas.
Geniaaaaaaaal
¿Lo publicaste en algún repositorio?
despes de todos estos retos de este buen curso he decidido aceptar un reto: hacer el curso de react router DOM5
Seria bueno, así vemos y practicamos las diferencias que nos encontraremos en el mundo laboral.
Solución
Les comparto mi solución al reto donde logramos clonar parte de lo que es React Router DOM 6.
GitHub:
Deploy:
Lo primero que vemos al ingresar a la aplicación es la página Home desde el cual hacemos algunos testeos utilizando un HashRouter.
Por ejemplo, podemos navegar hacia la página About por medio de un componente custom Link de la siguiente manera:
Navegación simple.
Navegación con estado.
Navegación con search, donde además podemos cambiar el valor del search por medio de un botón que utiliza un custom useSearchParams para lograrlo.
Navegación con search y estado, donde el estado persiste aún si cambiamos el valor del search.
Podemos navegar también a través de uno botones que utilizan un custom useNavigate y redireccionan de la misma manera que el custom Link.
Finalmente, podemos navegar hacia rutas dinámicas y obtener estos parámetros por medio de un custom useParams.
Para el test, hemos probado lo siguiente:
Navegación simple hacia la página de Profile del usuario #73 con un botón que utiliza un custom useNavigate.
Navegación con search y estado hacia la página de Profile del usuario #73 con un botón que utiliza un custom useNavigate.
Esas fueron las pruebas suficientes como para saber que el clon de React Router DOM 6 funciona correctamente. Además de redireccionarnos a una página de Not Found en caso de no coincidir ninguna ruta.
Este componente principal usa HashRouter para manejar la navegación basada en hash. Dentro de HashRouter, las rutas son definidas usando Routes y Route, donde cada ruta especifica un camino path y el componente a renderizar element. Esto incluye rutas estándar, rutas dinámicas con parámetros como /user/:id, y una ruta comodín * para manejar páginas no encontradas.
HashRouter.js
import{ createContext, useEffect }from"react";import{ useLocation }from"../hooks/useLocation";constRouterContext=createContext();constHashRouter=({ children })=>{constlocation=useLocation();useEffect(()=>{if(!window.location.hash){window.history.replaceState(null,"","#/");window.dispatchEvent(newHashChangeEvent("hashchange"));}},[]);return(<RouterContext.Provider value={{location}}>{children}</RouterContext.Provider>);};export{RouterContext,HashRouter};
Este componente proporciona el contexto de enrutamiento RouterContext a toda la aplicación. Utiliza el custom hook useLocation para rastrear la ubicación actual en la URL hash. Si la URL no tiene un hash al cargar la página, se agrega automáticamente #/ para mantener la estructura del enrutamiento.
Link.js
importReactfrom"react";functionLink({ to, children, state =null}){consthandleClick=(e)=>{ e.preventDefault();window.history.pushState(state,"",`#${to}`);window.dispatchEvent(newHashChangeEvent("hashchange"));};return(<span onClick={handleClick} style={{cursor:"pointer"}}>{children}</span>);}export{Link};
Este componente es una versión personalizada de un enlace <a>, que permite la navegación interna en la aplicación sin recargar la página. Usa window.history.pushState para actualizar la URL y despacha un evento hashchange para notificar a la aplicación sobre la nueva ubicación.
Route.js
constRoute=({ path, element })=>{return{ path, element };};export{Route};
Simplemente devuelve un objeto con la ruta y el elemento asociado, siendo más un contenedor de datos que un componente funcional.
Routes.js
importReact,{ useContext }from"react";import{RouterContext}from"./HashRouter";constmatchPath=(path, pathname)=>{const paramNames =[];const regexPath = path
.replace(/\/:([^/]+)/g,(full, key)=>{ paramNames.push(key);return"/([^/]+)";}).replace(/\*/g,"(.*)");const match = pathname.match(newRegExp(`^${regexPath}$`));if(!match)returnnull;const params = match.slice(1).reduce((acc, value, index)=>{ acc[paramNames[index]]= value;return acc;},{});return{ params };};constRoutes=({ children })=>{const{location}=useContext(RouterContext);const matchedRoute =React.Children.toArray(children).find((child)=>{const{ path }= child.props;if(path ==="*")returntrue;const match =matchPath(path,location.pathname);return match !==null;});const match = matchedRoute
?matchPath(matchedRoute.props.path,location.pathname):null;return(<RouterContext.Provider value={{location, matchedRoute }}>{matchedRoute
?React.cloneElement(matchedRoute.props.element,{...match?.params }):null}</RouterContext.Provider>);};export{Routes};
Este componente selecciona y renderiza el componente adecuado según la URL actual. Usa matchPath para verificar si la ubicación actual coincide con alguna ruta definida. Si se encuentra una coincidencia, se renderiza el componente correspondiente, pasando los parámetros extraídos de la URL.
Este hook personalizado rastrea la ubicación actual de la URL, detectando cambios en el hash y en la historia de navegación popstate. Devuelve un objeto con detalles sobre el hash, la ruta pathname, los parámetros de búsqueda search, y el estado state.
useNavigate.js
functionuseNavigate(){functionnavigate(to, state =null){window.history.pushState(state,"",`#${to}`);window.dispatchEvent(newHashChangeEvent("hashchange"));}return navigate;}export{ useNavigate };
Este hook personalizado permite navegar a diferentes rutas actualizando la URL hash sin recargar la página. La función navigate cambia la URL y despacha un evento hashchange para que otros componentes respondan al cambio de ruta.
Este hook extrae los parámetros dinámicos de la ruta actual, como :id en /user/:id. Utiliza la ruta actual pathname y la ruta coincidente matchedRoute para construir un objeto con los parámetros que se pasaron en la URL.
Este hook permite obtener y modificar los parámetros de búsqueda en la URL hash. Proporciona una función para acceder a los parámetros actuales getSearchParams y otra para actualizarlos setSearchParams, lo que desencadena un cambio de URL y un evento hashchange.
Este componente muestra información sobre la página de About, incluyendo el estado almacenado en la historia de navegación state y los parámetros de búsqueda searchParams. Incluye un botón para modificar el parámetro de búsqueda y actualizar la URL hash.
Home.js
importReactfrom"react";import{Link}from"../components/Link";import{ useLocation }from"../hooks/useLocation";import{ useNavigate }from"../hooks/useNavigate";functionHome(){constlocation=useLocation();console.log(location);const navigate =useNavigate();return(<div
style={{display:"flex",flexDirection:"column",width:"300px",gap:"8px",}}><h2>Home</h2><Link to="/about"><div style={{color:"red"}}>ToAboutwithLink component</div></Link><Link to="/about" state={{link:"link value"}}><div style={{color:"purple"}}>ToAboutwithLink component & state
</div></Link><Link to="/about?query=new"><div style={{color:"green"}}>ToAboutwithLink& search</div></Link><Link to="/about?query=new" state={{link:"link value"}}><div style={{color:"blue"}}>ToAboutwithLink, search & state</div></Link><button onClick={()=>navigate("/about")}>To about with useNavigate
</button><button onClick={()=>navigate("/about?query=new")}>To about with useNavigate & search
</button><button
onClick={()=>navigate("/about",{navigate:"navigate value"})}>To about with useNavigate & state
</button><button
onClick={()=>navigate("/about?query=new",{navigate:"navigate value"})}>To about with useNavigate, search & state
</button><button
style={{backgroundColor:"orange",color:"white"}} onClick={()=>navigate("/user/73")}>To user #73 profile
</button><button
style={{backgroundColor:"orange",color:"white"}} onClick={()=>navigate("/user/73?query=new",{user:"user data"})}>To user #73 profile with search & state
</button></div>);}export{Home};
Este componente muestra la página de Home ofrece múltiples opciones de navegación usando Link y useNavigate, cada uno con diferentes combinaciones de parámetros de búsqueda y estado. Esto demuestra cómo se pueden manejar diferentes tipos de navegación dentro de la aplicación.
Este componente muestra el perfil de un usuario específico, identificando al usuario a través del parámetro de ruta :id. También maneja el estado y los parámetros de búsqueda, permitiendo que el componente muestre información adicional según la URL actual.
Lo consegui... entre pasadas y venidas en resumen un masomenos.
Implemente:
1)HashRouter: Encargado de agregar el hash en los paths
3)Switch: Encargado de renderizar solo el componente cuando el path coincida
importReactfrom'react'letlocation=window.locationletformaterPath=(path)=> path = path[0]+'#'+ path
constSwitch=({children})=>{let locationHash =location.hashlet childrensFind = children.find(children=>{let path =formaterPath(children.props.path)if(path.includes('*')){return children
}if(locationHash.includes('/edit/')){ locationHash ='#/edit/:id'}if(path.includes(locationHash)){//console.log('Mostar',children.props.path)return children
}})if(childrensFind){return(<>{childrensFind.props.children}</>)}}exportdefaultSwitch
Hook useHistory o useNavigation. Encargado de recargar la pagina dependiendo del path que reciba, (Aqui hago una aclaracion porque me cambia el path pero no me renderiza el componente, no logre resolver este eror)
De esta forma ya puedes crear un ruteo basico con un hash y dependiendo de la ruta renderizar un componente o no, obtener parametros de una ruta y dependiendo de un evento o no navegar a tal path.
Todo esto gracias al objeto location de javascript vanilla
¿Alguien tiene el repo de reto en Javascript para verlo y replicarlo no vengo de la ruta de javascript porque solo estoy haciendo la ruta de React.js ya sabia javascript solo me estoy actualizando a React?
Reto: crea tu propio React Router
.
El reto consiste en clonar React Router. En el reto anterior ya vimos cómo construir un router para JavaScript vanilla, donde dependiendo del location.hash renderizábamos un código u otro.
.
Lo que debemos hacer ahora es lo mismo, pero transformado en componentes de React. Debes crear tu propio HashRouter y tu propio componente Route para renderizar un componente o no, dependiendo de si esa propiedad path (o como queramos llamarla) coincide con la URL o con el hash en el que nos encontremos.
.
Es obligatorio que creemos un componente Link que nos ayude a hacer la navegación simplemente con JavaScript. No será una etiqueta <a></a> normal, sino una etiqueta que haga que cambie la URL y que, a su vez, cambie la ruta que renderizamos.
.
Ese es el reto principal, pero si quieres ir más allá, también puedes clonar los hooks de React Router DOM, como useLocation y useNavigate, para que también desde JavaScript podamos ejecutar esas funciones, y dichas funciones cambien la URL y, por ende, el contenido de la aplicación.
Link del repo utilizando el router creado por mi implementando el proyecto del curso