Considere este escenario:
Un panel de administración que tiene una barra lateral y un contenido.
La barra lateral contiene el menú y cada elemento en el que el usuario hace clic, se mostrará un componente en el área de contenido.
El contenido es un Router que cambia el componente según la URL.
Digamos que tenemos estos componentes:
Page1.jsx
Page2.jsx
Page3.jsx
Cada uno de ellos realiza alguna operación cuando se carga. Cada uno lo hace dentro useEffect .
useEffect(() => { // some operation inside PageN.jsx that takes some time // Here we should show a progress }, []) Podemos definir fácilmente un estado en cada PageN.jsx y, en función de eso, devolver un progreso o el componente real:
const [progress, setProgress] = useState() return progress ? <div>Loading...</div> : <div>Actual component</div>Sin embargo, esto es ineficiente para una aplicación con 100 páginas y mata DRY y también introduce muchas inconsistencias. Cada página puede mostrar un componente de carga diferente, etc.
Una forma es elevar el estado de progress al Router del elemento principal o al evento de la propia App . Luego páselo por la jerarquía.
const PageN = ({ setContentProgress }) => { // now I can call parent's setContentProgressPero pronto entra en el famoso No se puede actualizar un componente desde dentro del cuerpo de la función de un componente diferente .
Entonces, ¿cuál es el enfoque seguro aquí? cual es la mejor practica?
Definitivamente, le sugiero que use la API de contexto, para simplemente crear un contexto de diseño en la parte superior de su barra lateral y área de contenido. Recientemente implementé algo similar para mi proyecto personal donde decidí que quería implementar una barra de progreso lineal como la que tiene YouTube (línea roja que avanza debajo del encabezado), por lo que la única opción para mí era crear un contexto de diseño que manejaría el progreso y luego en sus hijos, simplemente usaría acciones expuestas del contexto mismo, para actualizar el progreso.
Lo hice de esta manera:
interface IProps { children: React.ReactNode; } const initialState = { displayProgressBar: false, progressPercentage: 0 }; const PROGRES_BAR_TRANSITION = 300; // ms let timeout: undefined | NodeJS.Timeout; const Layout = ({ children }: IProps) => { const { pathname } = useLocation(); const [context, setContext] = useState(initialState); const navigate = useNavigate(); useEffect(() => { setContext(initialState); return () => { if (timeout) clearTimeout(timeout); }; }, [pathname]); const setLayoutProgressVisiblity = useCallback((isVisible: boolean) => { if (!isVisible) timeout = setTimeout(() => { setContext((p) => ({ ...p, displayProgressBar: isVisible })); }, PROGRES_BAR_TRANSITION); else setContext((p) => ({ ...p, displayProgressBar: isVisible })); }, []); const setLayoutProgressPercentage = useCallback( (percentage: number) => setContext((p) => ({ ...p, progressPercentage: percentage })), [] ); const contextActions = useMemo<ILayoutContext>(() => ({ setLayoutProgressPercentage, setLayoutProgressVisiblity }), [setLayoutProgressPercentage, setLayoutProgressVisiblity]); const goToHomepage = useCallback(() => { navigate("/", { replace: true }); }, [navigate]); return ( <LayoutContext.Provider value={contextActions}> <div className="app-container p-relative overflow-auto"> <div>{/** HERE YOU CAN PUT HEADER */}</div> {context.displayProgressBar && ( <HeaderProgressBar progress={context.progressPercentage} /> )} <div className=" py-4 flex-fill overflow-auto"> {<div className="container h-100">{children}</div>} </div> </div> </LayoutContext.Provider> ); }; export default Layout;Además de esto, creé un enlace personalizado para usarlo cuando quiera acceder a las acciones de diseño:
export const useLayoutContext = () => { const context = useContext(LayoutContext); if (!context) throw new Error( "No LayoutContext.Provider found when calling useLayoutContext." ); return context; };Y finalmente, es muy fácil actualizar el progreso dentro de cualquier elemento que esté dentro de LayoutContext. Tan simple como esto:
const { setLayoutProgressPercentage, setLayoutProgressVisiblity } = useLayoutContext();