Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why isn't my ReactJs App not updating correctly

Iam writing a cookbook app.
The recipes of the cookbook are stored in yaml files and these are being stored in a static way. When I load up the site, it will automatically reach out to an index.json file in which all recipes are indexed and load them one after one and add them to an array. This array is then given to the setRecipe method where it should update the dom accordingly.
This doesn’t happen.
I already tried to console.log a little and when doing this I get logged the expected data but as soon as I refresh the page this isn’t case anymore. The request for the yaml files are being done. Why does that happen?

Full Sourcecode

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;

Simple Yaml example:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

---
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!

>Solution :

Your issue comes with populating the store array in your second .then(): when using Array.prototype.forEach, remember that return doesn’t really do anything. You are not awaiting for the blob to be parsed before resolving the promise. In fact, this section of the daisy chained promises resolves immediately:

.then(recipeIndex => {
    let store = [];
    recipeIndex.forEach(element => {
        fetch(...);
    });

    // `store` is returned immediately without waiting for forEach!
    return store;
})

Instead, I would suggest using Array.prototype.map to return an array of promises based off recipeIndex. Then return Promise.all(), which ensures that all promises are resolved:

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

What about using for loop? (You can, but not recommended)

An alternative is to use a for loop, which you can then use async/await. However I would typically not suggest this for two reasons:

  1. for loop + await means that each requests are dispatched after each other and not in parallel, which increases latency. The Array.prototype.map solution above dispatches fetch requests at the same time.
  2. some linters out there discourage nesting of async callbacks inside .then()

However if you want to give it a try, a for loop is totally doable:

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));
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading