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]);