Home > Enterprise >  Render a Component of React after fetch
Render a Component of React after fetch

Time:09-22

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 need useReducer at all. You might just grab a hold of a list of riders with useState;

Try to:

  • Fetch your riders api in a useEffect call and when the promise resolves call setRiders and pass it the resolved riders' list;
  • Render some loading component while your riders list is empty (you can use a simple if statement checking on riders 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.

  • Related