Recientemente escribí un componente de tabla usando ganchos, y cada vez que se carga la página hay una llamada API al backend, por lo tanto, mientras tanto, se mostrará un Spinner de carga hasta que haya una respuesta de la API. Estoy usando redux como administración de estado, por lo que cuando hay una respuesta de API, se envía una acción y se actualiza el estado. Entonces, el problema aquí es que, por lo general, en el componente de clase podemos comparar prevProps y nextProps usando
componentDidUpdate(prevProps){ if(this.props.someState !== prevProps.someState){ // do something } }pero no estoy seguro de cómo lograr lo mismo usando Hooks. También hice referencia a esta pregunta de stackoverflow ¿ Cómo comparar oldValues y newValues en React Hooks useEffect?
pero esta solución no parece funcionar en mi caso. Intenté crear un gancho personalizado usePrevious y crear una referencia para comparar el valor actual , esto no resolvió mi problema.
Aquí está mi parte del código.
let loading = true; let tableData = useSelector((state) => { if ( state.common.tableDetails.data && state.common.tableDetails.status === true ) { loading = false; return state.common.tableDetails.data; } if ( state.common.tableDetails.data && state.common.tableDetails.status === false ) { loading = true; } return []; }); // table component <Fragment> { loading === true ? <Spinner /> : <TableComponent tableData={tableData }/> } </Fragment>Entonces, cada vez que se carga el componente, si hay datos presentes en el estado redux, esos datos se muestran y no se realiza ninguna comparación para prevProps y nextProps debido a que Loading spinner no aparecerá y después de una respuesta del estado api recién llamado será la actualización y los nuevos datos se mostrarán en la tabla.
ACTUALIZACIÓN 1: Aquí está el código para el envío y la acción y el reductor
useEffect(() => { dispatch(fetchDetails(params.someID)); }, [dispatch, params.someID]);Archivo de acción
export const fetchDetails = (data) => (dispatch) => { axios .post( `${SomeURL}/fetchAll`, data ) .then((res) => { if (res.data.status) { dispatch({ type: FETCH_DETAILS, payload: res.data, }); } }) .catch((err) => console.log(err)); };Archivo reductor
const initialState = { tableDetails: {}, }; export default function (state = initialState, action) { switch (action.type) { case FETCH_DETAILS: return { ...state, tableDetails: action.payload, }; default: return state; } }Puede implementar una validación simple para lograr lo que desea. Asumiré que su tableData tiene una id , luego puede validar así:
useEffect(() => { // don't fetch the same table if(tableData?.id !== params.someID { dispatch(fetchDetails(params.someID)); } }, [dispatch, params.someID, tableData?.id]);Para comparar los datos de la tabla anterior con los nuevos, puede hacerlo en el creador de acciones:
loading a la tienda redux.getState , que es el segundo argumento recibido por el creador de la acción (suponiendo que esté usando redux-thunk)tableData con los datos en la tienda (prev tableData ).loading . export const fetchDetails = (data) => (dispatch, getState) => { axios .post( `${SomeURL}/fetchAll`, data ) .then((res) => { if (res.data.status) { const prevTableData = getState().tableData; // compare prev and new table data if(isDiff(prevTableData, res.data) { // Do something... } dispatch({ type: FETCH_DETAILS, payload: res.data, }); } }) .catch((err) => console.log(err)); };La forma correcta es usar el paquete de selección de redux https://github.com/reduxjs/reselect . El equipo de Redux lo recomienda.
reseleccionar está diseñado específicamente para este tipo de escenarios. También utiliza la memorización bajo el capó. Puede crear sus selectores personalizados y usarlos en escenarios como este.
Puede hacer un archivo separado para todas las funciones de reselección como esta
//select file (eg: selectors.js) import { createSelector } from "reselect"; const tableDetails = state => state.common.tableDetails; function checkDataFunction(details){ if ( details.data && details.status === true ) { return { loading: false, data: details.data } } else if ( details.data && details.status === false ) { return { loading: true, data: [] } } return { loading: true, data: [] }; } //redux selector //enables you to make your own custom selector export const checkData = createSelector( tableDetails, checkDataFunction );Ahora, en su archivo de componente principal donde desea usar esto, simplemente impórtelo y utilícelo como cualquier otro selector
//main component file (eg: App.js) import {checkData} from './selectors'; export default function App(){ const tableData = useSelector(checkData); useEffect(() => { //continue with your logic here },[tableData.loading]) //... }