En este fascinante y desafiante curso de Platzi, se te invita a sumergirte completamente en el mundo de React desarrollando tus propias versiones de funcionalidades esenciales que ofrece React Router. Este no es un ejercicio cualquiera; es un reto que te llevará un paso más allá en tu camino para convertirte en un desarrollador profesional efectivo.
¿Cómo se clona un router en React?
La tarea inicial de este reto consiste en replicar lo que ya has aprendido sobre la creación de un router en JavaScript vainilla. Ahora, te toca llevar esa experiencia al entorno de React y crear un hash router. Este proceso implica transformar los elementos mágicos de un router manual en componentes de React con las siguientes indicaciones:
Crea un hash router: A partir de los conocimientos previos, construye un componente de 'hash router' en React que use location.hash para renderizar el componente correspondiente.
Desarrolla un componente Route: Implementa un componente Route que se encargue de verificar si el hash actual coincide con el path o propiedad que determines, y renderice los componentes correspondientes.
Implementa un componente Link: Crea un componente Link que permita realizar la navegación utilizando JavaScript, en lugar de una etiqueta a convencional, asegurando que la URL actualice adecuadamente sin tener que recargar la página.
¿Cómo replicar los hooks de React Router DOM?
Si hay quienes buscan llevar su aprendizaje al siguiente nivel, eres invitado a replicar los hooks personalizados como useLocation, useNavigate o cualquier otro que creas necesario. Estos permitían ejecutar funciones desde JavaScript, cambiando tanto la URL como el contenido de la aplicación. Esto, aunque suene complejo, es un excelente ejercicio para mejorar tus habilidades en JavaScript y React.
Consejos para llevar a cabo este reto
No te preocupes por la estética inicial del código: Tu prioridad debe ser que funcione, sin importar que el código no sea bonito al principio. Gradualmente podrás refactorizarlo para mejorar su estructura.
Aprovecha la comunidad React: No estás solo en este proyecto, puedes buscar apoyo y compartir experiencias con otros estudiantes. Aprende de sus implementaciones y busca inspiración en otras librerías y proyectos open source.
Itera y mejora constantemente: El desarrollo se trata de iterar. Prueba, falla, mejora y comparte tus aprendizajes. Cada nueva versión que desarrolles te acercará más a la maestría en React, especialmente en cuanto a la gestión de rutas.
Te animamos a que te lances a este reto con confianza y entusiasmo. Al completar esta actividad, no solo habrás profundizado en tus habilidades técnicas, sino que además te sentirás más satisfecho y seguro de tu capacidad para enfrentar y superar desafíos en programación. ¡Buena suerte, y sigue avanzando en tu ruta de aprendizaje con determinación!
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