Estoy tratando de averiguar cómo debo estructurar mi código con ganchos React de manera que:
loading se establece en true cuando useEffect() se ejecuta para obtener datos para mostraruseEffect() configuro la loading como falseloading se pasa como accesorio para cambiar varios componentes.A continuación se muestra un modelo simplificado de mi código:
const App = () => { const [state, stateDispatch] = useReducer(stateReducer,{loading: false}) useEffect(() => { stateDispatch({type:'loadingResults'}); loadResults(); stateDispatch({type:'finishedLoadingResults'}); }); return ( <ExampleComponent loading={state.loading} /> ); } const stateReducer = (prevState,action) => { switch(action.type) { case 'loadingResults': return {...prevState, loading:true}; case 'finishedLoadingResults': return {...prevState, loading: false}; default: throw new Error(); } }El componente "secundario" de ejemplo se muestra a continuación:
const SampleComponent = (props) => { const [buttonText, setButtonText] = useState('Load More'); useEffect(() => { if (props.loading) { setButtonText('Loading...') } else { setButtonText('Load More'); } },[props.loading]); return ( <div onClick={(event) => props.getBooks(event,false)} className="moreResults">{buttonText}</div> ) } Con esta configuración actual, nunca veo que el componente secundario se vuelva a renderizar, aunque parece que if (props.loading) se evalúa correctamente. Por ejemplo, si coloco un console.log dentro de if check y else check, veo que ambos se imprimen cuando se cambia el estado de loading .
Alternativamente, si defino state.loading tiene una dependencia para la función useEffect() en el componente de la App , obtengo infinitas representaciones ya que estoy alternando el estado de loading dentro useEffect() .
¿Alguna idea de lo que me estoy perdiendo aquí?
No necesita useEffect o useState en su componente secundario. La propiedad está cambiando en el padre, por lo que es suficiente para volver a renderizar su componente.
const SampleComponent = (props) => ( <div onClick={(event) => props.getBooks(event,false)} className="moreResults">{props.loading ? 'Loading...' : 'Load More'} </div> )Un componente decide volver a renderizar solo cuando cambia alguna de sus propiedades o estado.
Javascript ejecuta todo de forma síncrona debido a su naturaleza de subproceso único. Por lo tanto, la función de diferenciación de reacción para identificar si el componente debe volver a renderizarse o no ocurrirá en orden síncrono, que es después de que se complete la ejecución del gancho useEffect en este caso.
useEffect(() => { stateDispatch({type:'loadingResults'}); // -> state update happened // -> but re-render won't happen, since useEffect metho execution is not complete yet loadResults(); stateDispatch({type:'finishedLoadingResults'}); });Pero para entonces habrá terminado de cargar y restablecer el estado. Como no hay cambios, el componente no se volverá a renderizar.
Además, no puede establecer el estado fuera del gancho porque cualquier disparador de setState activará una nueva representación que establecerá nuevamente el estado y esto creará un bucle infinito.
Solución: Llame a setState para cargar dentro de una condición if fuera del bloque useEffect. Algo como esto.
const App = () => { const [state, stateDispatch] = useReducer(stateReducer,{loading: false}) useEffect(() => { if(!state.loading) { loadResults(); stateDispatch({type:'finishedLoadingResults'}); } },[state.loading]); if(notResults) { stateDispatch({type:'loadingResults'}); } return ( <ExampleComponent loading={state.loading} /> ); }En primer lugar, useEffect en el componente de su aplicación no tiene una lista de dependencias, por lo que se ejecuta en cada procesamiento. Dado que establece el estado, esto provoca una nueva representación, después de lo cual se vuelve a llamar a su useEffect, y así sucesivamente. Así que obtiene un bucle infinito.
Debe agregar una lista de dependencias a useEffect.
En segundo lugar, si su función loadData es una función asíncrona (lo que debería ser si está obteniendo datos), devolverá una promesa, por lo que puede enviar "finishedLoadingResults" cuando se resuelva la promesa.
Esto da lo siguiente para su useEffect:
useEffect(() => { stateDispatch({ type: "loadingResults" }); loadResults() .then(() => stateDispatch({ type: "finishedLoadingResults" })); }, []);Puede ver el código de trabajo en este Sandbox .