typescript - el context-api de ReactJS no se renderiza después de que obtengo datos
Este es un sitio de next.js, dado que tanto mi componente Navbar como mi página de carrito deberían tener acceso al contenido de mi carrito, creé un contexto para ellos. Si intento renderizar la página, recibo:
Error de Ejecución No Controlado :
TypeError: No se pueden leer las propiedades de undefined (leyendo 'key') :
obs: El arreglo cartContent existe y tiene longitud 1, puedo obtenerlo retrasando cuando se renderizan los datos usando setTimeout, pero no puedo hacer que se renderice justo después de que se obtiene.
Necesito hacer que se renderice después de que se devuelvan los datos de Firebase, pero siempre me encuentro con el error mencionado.
Este es mi archivo _app.tsx
function MyApp({ Component, pageProps }) {
// set user for context
const userContext = startContext();
return (
<UserContext.Provider value = { userContext }>
<Navbar />
<Component {...pageProps} />
<Toaster />
</UserContext.Provider>
);
}
export default MyApp
Este archivo tiene la función startContext que devuelve el contexto para que pueda ser utilizado.
export const startContext = () => {
const [user] = useAuthState(auth);
const [cart, setCart] = useState(null);
const [cartContent, setCartContent] = useState(null);
useEffect(() => {
if (!user) {
setCart(null);
setCartContent(null);
}
else {
getCart(user, setCart, setCartContent);
}
}, [user]);
return { user, cart, setCart, cartContent, setCartContent };
}
Este archivo contiene la función getCart.
export const getCart = async (user, setCart, setCartContent) => {
if (user) {
try {
let new_cart = await (await getDoc(doc(firestore, 'carts', user.uid))).data();
if (new_cart) {
let new_cartContent = []
await Object.keys(new_cart).map(async (key) => {
new_cartContent.push({...(await getDoc(doc(firestore, 'products-cart', key))).data(), key: key});
});
console.log(new_cartContent);
setCartContent(new_cartContent);
console.log(new_cartContent);
setCart(new_cart);
}
else {
setCart(null);
setCartContent(null);
}
} catch (err) {
console.error(err);
}
}
}
Esta es la página web cart.tsx. Cuando la cargo, obtengo el error mencionado.
export default () => {
const { user, cart, cartContent } = useContext(UserContext);
return (
<AuthCheck>
<div className="grid grid-cols-1 gap-4">
{cartContent && cartContent[0].key}
</div>
</AuthCheck>
)
}
He intentado renderizar el contenido del carrito[0].key de muchas maneras diferentes, pero no pude hacerlo. Siempre obtengo un error como si fuera indefinido. Hacer un truco con setTimeout funciona, pero realmente quería resolver esto de una manera decente para que sea al menos a prueba de errores en el sentido de no depender del tiempo de respuesta de Firebase o la latencia de Internet.
Editar: Dado que funciona con setTimeout, se siente como una condición de carrera en la que si se utiliza setCartContent, esto provoca el rerender, pero setCartContent no puede terminar antes de que se renderice el contenido, por lo que considerará el estado cartContent como indefinido y no se activará nuevamente más tarde.
Respuesta #1
Intenta cambiar
{cartContent && cartContent[0].key}
a
{cartContent?.length > 0 && cartContent[0].key}
Editar:: El problema real está en la función getCart en la línea
let new_cart = await (await getDoc(doc(firestore, 'carts', user.uid))).data();
Esto se establece ya sea en un arreglo vacío o en un objeto vacío. Así que intenta cambiar tu condición if (new_cart) a
if (Object.keys(new_cart).length > 0) {
Ahora no obtendrás el error undefined
Respuesta #2
Dado que parecía haber una condición de carrera, supuse que setCartContent se estaba ejecutando antes de que se obtuviera su contenido. Así que cambié en la función getCart el bucle map con una función asíncrona por un bucle for
await Object.keys(new_cart).map(async (key) => {
new_cartContent.push({...(await getDoc(doc(firestore, 'products-cart', key))).data(), key: key});
});
a
for (const key of Object.keys(new_cart)) {
new_cartContent.push({...(await getDoc(doc(firestore, 'products-cart', key))).data(), key: key});
}
No puedo hacer una función map con await dentro sin hacerla asincrónica, así que el bucle for hizo que funcionara. Espero que alguien encuentre algunas alternativas para resolver esto, yo solo pude idear un bucle for para que el código sea sincrónico.
