Trouble rendering data after API call in React with custom hook and filters

39 views Asked by At

Description:

I have a React component with a custom hook useOfertas that makes an API call in the fnCargarOfertas function. After the API call, I'm able to fill the array with data, but the rendering doesn't happen until a filter is applied using the FiltrosOfertas component.

I want to render the data immediately after the API call, and then apply filters without having to press any additional buttons.

I dont want to make any additional API call.

export const PantallaOfertas = () => {

    const { qOfertas, misOfertas, fnBorrarOferta, fnCargarOfertas } = useOfertas();
    const [ofertasFiltradas, setOfertasFiltradas] = useState([]);

    let contenido = null;

    if (qOfertas.estado === "cargando") {
        contenido = <BoxCargando titulo="Cargando las ofertas" />
    } else if (qOfertas.estado === "descargando") {
        contenido = <BoxCargando titulo="Descargando el fichero" />
    } else if (qOfertas.error) {
        contenido = <BoxErrorApi msError={qOfertas.error} titulo="Error al cargar las ofertas" />
    }

    return (

        <>
            {/* <a ref={refDescargaEnlace} href="/" style={{ display: 'none' }}>as</a> */}
            <Box sx={{ mt: 4 }}>
                {contenido}
            </Box>
            {
                ofertasFiltradas
                    ?
                    <>
                        <FiltrosOfertas setOfertasFiltradas={setOfertasFiltradas} />
                        <Box sx={{ flexGrow: 1 }}>
                            <Grid container spacing={2}>

                                {
                                    ofertasFiltradas?.map(oferta => (

                                        <Grid
                                            sx={{ flexGrow: 1 }}
                                            xs={12}
                                            sm={12}
                                            md={6}
                                            lg={4}
                                            key={oferta.id}
                                        >

                                            <OfertaCard
                                                key={oferta.id}
                                                {...oferta}
                                            />

                                        </Grid>
                                    ))
                                }
                            </Grid>
                        </Box>
                    </>
                    :
                    <Box sx={{ m: 'auto', textAlign: 'center' }}>
                        <div><SentimentNeutralIcon sx={{ width: '60px', height: '60px', color: 'secondary.light' }} /></div>
                        <Typography sx={{ ml: 2, mt: 1 }} variant="h5" component="div">Sin resultados</Typography>
                    </Box>
            }
        </>
    )
}

I attempted to call fnCargarOfertas within the FiltrosOfertas component, followed by setting the results using useState. My expectation was to render the component with all the cards without the need for an additional button press.

export const FiltrosOfertas = ({ setOfertasFiltradas }) => {

    const [valueLocalizacion, setValueLocalizacion] = useState([]);
    const [valueCategoria, setValueCategoria] = useState([]);
    const [valueTag, setValueTag] = useState([]);
    const { qOfertas, misOfertas, fnBorrarOferta, fnCargarOfertas } = useOfertas();

    //Leo las localizaciones y categorías de los cards y elimino las repeticiones.
    const localizaciones = misOfertas.flatMap(oferta => oferta.localizaciones.map(loc => loc.titulo)).filter(Boolean).filter((localizacion, i, arr) => arr.indexOf(localizacion) === i);
    // const localizacionesVacias = ofertas.flatMap(oferta => oferta.localizaciones.map(loc => loc.titulo)).filter((localizacion, i, arr) => arr.indexOf(localizacion) === i);
    const tags = misOfertas.flatMap(oferta => oferta.tags.map(tag => tag.texto)).filter(Boolean).filter((tag, i, arr) => arr.indexOf(tag) === i);
    const categorias = misOfertas.map(oferta => oferta.categoria.titulo).filter(Boolean).filter((titulo, i, arr) => arr.indexOf(titulo) === i);

    //Capturo el evento del cambio en la Localización
    const handleChangeLocalizacion = useCallback((event, value) => {
        event.preventDefault();
        setValueLocalizacion(value.filter(Boolean))
    }, [valueLocalizacion]);

    //Capturo el evento del cambio en la Categoría
    const handleChangeCategoria = useCallback((event, value) => {
        event.preventDefault();
        setValueCategoria(value);
    }, [valueCategoria]);

    //Añado el nuevo tag al value
    const handleClickTag = (tag) => {
        if (valueTag.includes(tag)) return null;
        setValueTag([...valueTag, tag]);
    }

    //Filtro el array con los datos distintos al tag y lo SetTeo
    const handleDeleteTag = (tag) => {
        setValueTag(prevValueTag => prevValueTag.filter(t => t !== tag));
    }

    useEffect(() => {
        fnCargarOfertas();
        setOfertasFiltradas(misOfertas)
    }, []);

    useEffect(() => {

        let ofertasFiltradas = [...misOfertas];

        if (Array.isArray(valueCategoria) && valueCategoria.length > 0) {
            ofertasFiltradas = ofertasFiltradas.filter(oferta => valueCategoria.includes(oferta.categoria.titulo));
        }

        if (Array.isArray(valueLocalizacion) && valueLocalizacion.length > 0) {

            const localizacionesVacias = ofertasFiltradas.filter(oferta => oferta.localizaciones.length === 0)
            ofertasFiltradas = ofertasFiltradas.filter(oferta => oferta.localizaciones.some(loc => valueLocalizacion.includes(loc.titulo)));
            ofertasFiltradas = ofertasFiltradas.concat(localizacionesVacias);
        }

        if (Array.isArray(valueTag) && valueTag.length > 0) {

            const tagsVacios = ofertasFiltradas.filter(oferta => oferta.tags.length === 0)
            ofertasFiltradas = ofertasFiltradas.filter(oferta => oferta.tags.some(tag => valueTag.includes(tag.texto)));
            ofertasFiltradas = ofertasFiltradas.concat(tagsVacios)
        }

        setOfertasFiltradas(ofertasFiltradas);

        //He eliminado localizaciones y  localizacionesVacias para que no se renderice de más
    }, [valueLocalizacion, valueCategoria, valueTag]);

    return (

        <Box sx={{ flexGrow: 1 }}>
            <Grid container spacing={2} justifyContent="center" >
                <Grid xs={12} sm={12} md={6} lg={6} sx={{ padding: 1 }}>
                    <Autocomplete
                        multiple
                        id="localizaciones"
                        options={localizaciones}
                        className='localizaciones'
                        filterSelectedOptions
                        onChange={handleChangeLocalizacion}
                        renderInput={(params) => (
                            <TextField
                                {...params}
                                label="Localizaciones"
                                placeholder="Favoritas"
                            />
                        )}
                    />
                </Grid>

                <Grid xs={12} sm={12} md={6} lg={6} sx={{ padding: 1 }}>
                    <Autocomplete
                        multiple
                        id="categorias"
                        className='categorias'
                        options={categorias}
                        filterSelectedOptions
                        onChange={handleChangeCategoria}
                        renderInput={(params) => (
                            <TextField
                                {...params}
                                label="Categorías"
                                placeholder="Favoritas"
                            />
                        )}
                    />
                </Grid>

                <Grid container justifyContent="center" xs={12} sm={12} md={12} lg={12} >

                    {tags.map((tag, i) => (

                        <Chip
                            sx={{ mt: 1, ml: 1, mb: 2 }}
                            key={i}
                            label={tag}
                            onClick={() => handleClickTag(tag, i)}
                            onDelete={valueTag.includes(tag) ? () => handleDeleteTag(tag, i) : null}
                            // color={valueTag.includes(tag) ? "success" : "info"}
                            color="success"
                            variant={valueTag.includes(tag) ? "" : "outlined"}
                        // deleteIcon={valueTag.includes(tag) ? "" : <IconButton disabled> </IconButton>}
                        />

                    ))
                    }
                </Grid>
            </Grid>
        </Box>
    );
};

This is also my custom hook:

import { useCallback, useEffect, useState, useRef } from 'react';
import { API } from '../../../api/api';
import { useStore } from 'react-redux';

export const useOfertas = () => {

    const redux = useStore();

    const refDescargaEnlace = useRef();
    const [descargarEnlace, setDescargarEnlace] = useState(false);
    const [qOfertas, setOfertas] = useState({
        estado: 'inicial',
        error: null,
        ofertas: []
    });

    useEffect(() => {
        console.log(qOfertas)
    }, [qOfertas]);


    const fnCargarOfertas = useCallback(async () => {
        setOfertas({ estado: 'cargando', error: null, ofertas: [] })
        
        try {
            const respuestaApi = await API(redux).ofertas.get();
            console.log(respuestaApi)
            setOfertas({ estado: 'completado', error: null, ofertas: respuestaApi.content })
        } catch (error) {
            setOfertas({ estado: 'error', error, ofertas: [] })
        }
        
    }, [redux]);

    return {
        qOfertas,
        misOfertas: qOfertas?.ofertas,
        fnCargarOfertas,
        setDescargarEnlace,
        refDescargaEnlace,
    }

}

This workaround solved my problem. Any other advice would be more than welcome.

I have added this useEffect at FiltrosOfertas

   useEffect(() => {
       const cargarOfertas = async () => {
           try {
               const ofertasCargadas = await fnCargarOfertas();
               setOfertasFiltradas(ofertasCargadas);
           } catch (error) {
               console.error("Error al cargar las ofertas", error);
           }
       };

       cargarOfertas();
   }, [fnCargarOfertas]);
0

There are 0 answers