Estoy escribiendo una aplicación de libro de cocina. Las recetas del libro de cocina se almacenan en archivos yaml y estos se almacenan de forma estática. Cuando cargo el sitio, accederá automáticamente a un archivo index.json en el que se indexan todas las recetas y las cargará una tras otra y las agregará a una matriz. Luego, esta matriz se entrega al método setRecipe, donde debería actualizar el dom en consecuencia. esto no sucede Ya traté de console.log un poco y al hacer esto, registré los datos esperados, pero tan pronto como actualicé la página, este ya no es el caso. La solicitud de los archivos yaml se está realizando. ¿Por qué sucede eso?
Código fuente completo
import React, { useState, useEffect } from 'react'; import jsyaml from 'js-yaml' import { ListGroup, ListGroupItem } from 'react-bootstrap'; const basePath = '/k0chbuch' const recipeStore = basePath + '/recipe-store' const index = recipeStore + '/index.json' const RecipeList = () => { const [recipes, setRecipes] = useState([]); useEffect(() => { fetchAllRecipes(); }, []); const fetchAllRecipes = () => { fetch(index) .then(response => { if (!response.ok) { throw new Error("HTTP error " + response.status); } return response.json() }) .then(recipeIndex => { let store = [] recipeIndex.forEach(element => { fetch(recipeStore + '/' + element + '.yaml') .then(res => res.blob()) .then(blob => blob.text()) .then(text => jsyaml.load(text)) .then(recipeObject => { store.push(recipeObject) }) }) return store }) .then((all) => setRecipes(all)); } return ( <div> <h1>Rezepte:</h1> <ListGroup> {recipes.map((r)=>(<ListGroupItem>{r.Titel}</ListGroupItem>))} </ListGroup> </div> ); }; export default RecipeList;
Ejemplo simple de Yaml:
--- Titel: Hackbraten Autor: d3v3lop3r Komplexitaet: 1 Portionen: 4 Zutaten: - 1000g Hack - 150g Zwiebeln - Gewürze nach Wahl Zubereitung: | Zuerst wird das Hack gewürzt, dann die Zwiebeln braten und dem Hack zugeben. Kräftig kneten, dann bei 200°C eine Stunde backen. Kommentar: Schmeckt am besten mit Kartoffeln!
Su problema viene con llenar la matriz de la store
en su segundo .then()
: cuando use Array.prototype.forEach
, recuerde que return
realmente no hace nada. No está esperando que se analice el blob antes de resolver la promesa. De hecho, esta sección de las promesas encadenadas se resuelve inmediatamente :
.then(recipeIndex => { let store = []; recipeIndex.forEach(element => { fetch(...); }); // `store` is returned immediately without waiting for forEach! return store; })
En su lugar, sugeriría usar Array.prototype.map
para devolver una serie de promesas basadas en recipeIndex
. Luego devuelva Promise.all()
, lo que garantiza que todas las promesas se resuelvan:
fetch(index) .then(response => { if (!response.ok) { throw new Error("HTTP error " + response.status); } return response.json(); }) .then(recipeIndex => { const promises = recipeIndex.map((element) => { return fetch(recipeStore + '/' + element + '.yaml') .then(res => res.blob()) .then(blob => blob.text()) .then(text => jsyaml.load(text)); }); return Promise.all(promises); }) .then((all) => setRecipes(all));
for
bucle? (Puede, pero no recomendado) Una alternativa es usar un bucle for
, que luego puede usar async/await. Sin embargo, normalmente no sugeriría esto por dos razones:
for
loop + await
significa que cada solicitud se envía una después de la otra y no en paralelo , lo que aumenta la latencia. La solución Array.prototype.map
anterior envía solicitudes de búsqueda al mismo tiempo..then()
Sin embargo, si quieres intentarlo, un bucle for
es totalmente factible:
fetch(index) .then(response => { if (!response.ok) { throw new Error("HTTP error " + response.status); } return response.json(); }) .then(async (recipeIndex) => { const recipes = []; for (const element of recipeIndex) { const recipe = await fetch(recipeStore + '/' + element + '.yaml') .then(res => res.blob()) .then(blob => blob.text()) .then(text => jsyaml.load(text)); recipes.push(recipe); } }) .then((all) => setRecipes(all));
useEffect
se ejecuta en función de la matriz de dependencias que se pasa como segundo argumento del useEffect
.
Prueba esto,
const [recipes, setRecipes] = useState([]); useEffect(() => { if(!recipes && recipes.length == 0){ fetchAllRecipes(); } }, [recipes])
Esto hará que su gancho useEffect
llame tan pronto como cambie el estado de las recipes
, pero solo hará que la función fetchRecipes()
llame si el estado de las recipes
es null
o la longitud de la matriz es 0