Company logo
  • Jobs
  • Bootcamp
  • About Us
  • For professionals
    • Home
    • Jobs
    • Courses
    • Questions
    • Teachers
    • Bootcamp
  • For business
    • Home
    • Our process
    • Plans
    • Assessments
    • Payroll
    • Blog
    • Calculator

0

52
Views
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:

---
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!
7 months ago · Juan Pablo Isaza
2 answers
Answer question

0

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));
7 months ago · Juan Pablo Isaza Report

0

useEffect runs based on the dependency array that is passed as the second argument of the useEffect hook.

Try this,

const [recipes, setRecipes] = useState([]);

useEffect(() => {
  if(!recipes && recipes.length == 0){
    fetchAllRecipes();
  }
}, [recipes])

This will cause your useEffect hook to call as soon as the recipes state changes but will only cause the fetchRecipes() function to call if the recipes state is null or the length of the array is 0

7 months ago · Juan Pablo Isaza Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post job Plans Our process Sales
Legal
Terms and conditions Privacy policy
© 2023 PeakU Inc. All Rights Reserved.