I have this code, which is that of the Grid component, which generates the list of riders available from the information recorded in the LocalStorage, the problem is that it renders before the information arrives, and even after it loads well, it does not re-render. Do you think I could solve it with the useContext? or what would be the best solution?
This is the component:
import { useEffect, useReducer, useState } from "react"
import { ridersReducer } from "../hooks/ridersReducer";
import { GridRow } from "./GridRow";
//This component generates the grid of the app
export const Grid = ({ pressed, setPressed }) => {
//eslint-disable-next-line
const [ state, dispatch ] = useReducer(ridersReducer, []);
const [ riders, setRiders ] = useState([]);
useEffect( () => {
dispatch({
type: 'read'
});
//With dispatch in update we update the riders
// We retrieve the state of the riders from the localStorage
setRiders(JSON.parse(localStorage.getItem('riders')));
}, [pressed]);
// Return the component
return (
<table className="table table-hover">
<thead>
<tr>
<th scope="col">Horas</th>
<th scope="col">Free Riders</th>
<th scope="col">Estado</th>
<th scope="col">Reservar</th>
</tr>
</thead>
<tbody>
{
// We check that the riders are not null and call
// GridRow to generate each row of the table
riders !== null &&(
Object.keys(riders).map( rider => (
<GridRow
id={ riders[rider].id }
key={ riders[rider].id }
hora={ riders[rider].Hora }
riders={ riders[rider].Riders }
pressed={ pressed }
setPressed={ setPressed }
dispatch={ dispatch }
/>
))
)
}
</tbody>
</table>
)
}
And this is the call:
import { getRiders } from "../helpers/getRiders";
import { updateRiders } from "../helpers/updateRiders";
export const ridersReducer = ( state, action ) => {
// We define the different actions that our reduce will do
switch (action.type){
case 'read':
localStorage.clear();
getRiders()
.then( data => {
// Save the riders on the state
if( JSON.parse(localStorage.getItem('riders') === null )){
state = data.record;
} else {
state = [...JSON.parse(localStorage.getItem('riders')), data.record];
}
})
.then( () => {
// We save the state of the riders in the localStorage to be able to
// retrieve them from the Grid component and be able to display it
localStorage.setItem('riders', JSON.stringify(state));
});
return state;
case 'update':
updateRiders()
break;
default:
return state;
};
};
CodePudding user response:
should'nt custom hooks start with "use" like "useReducer" instead of "ridersReducer", thats how react knows its a hook
CodePudding user response:
If you want to wait until data comes then render. just add if
condition after hooks
...
useEffect( () => {
dispatch({
type: 'read'
});
//With dispatch in update we update the riders
// We retrieve the state of the riders from the localStorage
setRiders(JSON.parse(localStorage.getItem('riders')));
}, [pressed]);
if(!riders ) return null //this will stop render null until data comes
// Return the component
return
....
CodePudding user response:
I think you're making this way more complicated than it needs to be.
You have both state from the reducer, and another state called riders
. You don't need both. Use your component to fetch the data, and then dispatch the result to your reducer. Your reducer should only be updating state with that data. And when state updates the component will re-render with the new data.
If you then wanted to add the data to localStorage, this too has been simplified. You can use the useLocalStorage hook
but, honestly I don't think that necessary, but your use-case is unclear.
As a simplified example:
useEffect(() => {
async function getRiders() {
const res = await fetch(url);
const data = await res.json();
dispatch({ type: 'read', payload: data });
// useLocalStorage here perhaps, but it seems unnecessary
});
getRiders();
}, [pressed]);
And in your reducer:
function ridersReducer( state, action ) {
const { type, payload } = action;
switch (action.type) {
case 'read': return payload;
default: return state;
};
You would then iterate over state to build your JSX:
state.map(rider => (
<GridRow
id={rider.id}
key={rider.id}
hora={rider.Hora}
riders={rider.Riders}
pressed={pressed}
setPressed={setPressed}
dispatch={dispatch}
/>
))
CodePudding user response:
There are couple of problems here:
- first, is that the reducer your are passing to the
useReducer
React hook should be a pure function, so it shouldn't do any promise or localStorage work, just return the new shape of the state; - second, you should send your API requests inside of a
useEffect
hook (API calls are side effects, hence the name of the hook); - third, you actually have no use of the updated state returned from
useReducer
, and according to your code you don't seem to needuseReducer
at all. You might just grab a hold of a list of riders withuseState
;
Try to:
- Fetch your riders api in a
useEffect
call and when the promise resolves callsetRiders
and pass it the resolved riders' list; - Render some loading component while your
riders
list is empty (you can use a simpleif
statement checking onriders
length); - Render your list of riders by mapping through them.
If you still want to use useReducer you obviously can, but the reduce is not the right place to centralize all your side effects.