Home > Software design >  Variable assignment from JSON not setting all the values at the same time (React) and returning unde
Variable assignment from JSON not setting all the values at the same time (React) and returning unde

Time:03-01

I'm making an async call to an API and then setting the data I get back to my state variable all inside a useEffect function. Out side of that function I then destructure the values into variables and the render them to the screen.

The issue is that the instructions variable is still undefined at time of render and I'm a bit confused why when the others render fine.

(The instructions variable is also an array of objects)

Top Component

const RecipePage = () => {

const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
const params = useParams();
const recipeSlug = params.recipeSlug;
const recipeID = params.id;

useEffect(() => {

    const options = {
        method: 'GET',
        url: 'https://tasty.p.rapidapi.com/recipes/detail',
        params: { id: recipeID },
        headers: {
            'x-rapidapi-host': 'tasty.p.rapidapi.com',
            'x-rapidapi-key': process.env.REACT_APP_RAPID_API_KEY
        }
    };

    const fetchData = async () => {
        const recipeInstructions = await axios.request(options);
        setData(recipeInstructions.data);
        console.log(recipeInstructions.data);
        console.log(data);
    }
    fetchData().catch(console.error);

}, [recipeID]);

const { name, country, slug, instructions, beauty_url } = data;

console.log(data.instructions);
console.log(name);

return (
    
    <>
        <header className="topBar">
            <Link to={`/`} className='normal'>
                <div className='title'>
                    <SectionTitle titleText="Test Test" />
                    <SectionTitle titleText="Cooking Club" />
                </div>
            </Link>
        </header>
        <div>
            <SectionTitle titleText={name} />
            <div className="image">
                <img src={beauty_url} alt="recipe" />
            </div>
        </div>
        <section>
            <p>Serves {data.num_servings} </p>
            <p>Prep Time {data.num_servings} </p>
        </section>
        <section>
            <h3>Ingredients</h3>
        </section>
        <div>{country}</div>
        <div>{slug}</div>
        <div>slug: {recipeSlug}</div>
        <RecipeInstructions instructions={instructions} />         
    </>
)

}

Instructions Component

const RecipeInstructions = ({instructions}) => {
console.log(instructions);
const instructionList = instructions.map((instruction, i) =>{
    return <li key={instruction.id} id ={instruction.id}>{instruction.display_text}</li>
});

return(
    <ul className="instructionList">
       {instructionList}   
    </ul>
)

}

export default RecipeInstructions;

JSON Shape

{
"country": "US",
"name": "Tomato And Ginger Pressure Cooker Short Ribs",
"id": 8115,  
"original_video_url": "https://s3.amazonaws.com/video-api-prod/assets/394e5b5d88d44a8abff0bf146ccda0b8/Campbells_TomatoGingerRibs_BFV88856_SQHero.mp4",
"cook_time_minutes": null,
"description": "",
"slug": "tomato-and-ginger-pressure-cooker-short-ribs",
"instructions": [
    {
        "appliance": null,
        "id": 70698,
        "display_text": "Season the short ribs all over with salt and pepper.",
        "position": 1,
        "start_time": 4166,
        "end_time": 14133,
        "temperature": null
    },
    {
        "display_text": "Turn the pressure cooker on to the Sauté setting. Add the olive oil to the pot and heat until shimmering. Working in batches, add the short ribs and sear on all sides until browned, 2–3 minutes per side. Once all of the short ribs have been seared, return to the pot, nestling to fit in an even layer.",
        "position": 2,
        "start_time": 0,
        "end_time": 0,
        "temperature": null,
        "appliance": null,
        "id": 70699
    },
    {
        "start_time": 16500,
        "end_time": 30333,
        "temperature": null,
        "appliance": null,
        "id": 70700,
        "display_text": "In a small bowl, whisk together the soy sauce, beef broth, sesame oil, red pepper flakes, brown sugar, garlic, and ginger.",
        "position": 3
    },
    {
        "end_time": 61500,
        "temperature": null,
        "appliance": null,
        "id": 70701,
        "display_text": "Pour the sauce and Campbell’s® Tomato Soup into the pot over the short ribs. Secure the lid and turn off the Sauté setting, then set to pressure cook on high for 1 hour.",
        "position": 4,
        "start_time": 50500
    },
    {
        "end_time": 0,
        "temperature": null,
        "appliance": null,
        "id": 70702,
        "display_text": "In a small bowl, whisk together the cornstarch, water, and mirin until smooth.",
        "position": 5,
        "start_time": 0
    },
    {
        "end_time": 47000,
        "temperature": null,
        "appliance": null,
        "id": 70703,
        "display_text": "Release the pressure valve until all the steam has been released. Transfer the short ribs to a bowl and skim off any fat from the surface of the braising liquid. Turn on the Sauté setting and stir the cornstarch slurry into the braising liquid. Bring to a simmer and cook until the sauce has thickened, about 3 minutes. Return the short ribs to the pressure cooker and turn to coat in the sauce.",
        "position": 6,
        "start_time": 35000
    },
    {
        "position": 7,
        "start_time": 93000,
        "end_time": 98333,
        "temperature": null,
        "appliance": null,
        "id": 70704,
        "display_text": "Serve the short ribs over white rice and garnish with sliced scallions and sesame seeds."
    },
    {
        "position": 8,
        "start_time": 100000,
        "end_time": 103000,
        "temperature": null,
        "appliance": null,
        "id": 70705,
        "display_text": "Enjoy!"
    }
], 
"beauty_url": "https://img.buzzfeed.com/video-api-prod/assets/dc16dd0e35f2480ba1d68badac0eec99/InstantPotShortRibs_Pinterest.jpg",
"num_servings": 4,
"facebook_posts": []

}

CodePudding user response:

The useEffect hook is first executed after the first initial render, also the request is asynchronous meaning there will be at least one render before receiving the network response and the data object being populated with the value for instructions.

This is expected behaviour, you can choose to defer rendering part of the component until the data is fetch, or provide some loading state.

CodePudding user response:

You're already redirected to the dedicated page so you have or not the recipeId no need to use array of dependencies in the useEffect You can use a conditionnal render since your call is asynchrone , you can display a gif loading for exemple once done you can display the informations.

const RecipePage = () => {

const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState([]);
const params = useParams();
const recipeSlug = params.recipeSlug;
const recipeID = params.id;

useEffect(() => {
   setIsLoading(true)
    const options = {
        method: 'GET',
        url: 'https://tasty.p.rapidapi.com/recipes/detail',
        params: { id: recipeID },
        headers: {
            'x-rapidapi-host': 'tasty.p.rapidapi.com',
            'x-rapidapi-key': process.env.REACT_APP_RAPID_API_KEY
        }
    };

    const fetchData = async () => {
        const recipeInstructions = await axios.request(options);
        setData(recipeInstructions.data);
        console.log(recipeInstructions.data);
        console.log(data);
    }
    fetchData().catch(console.error);
    setIsLoading(false)
}, []);

const { name, country, slug, instructions, beauty_url } = data;

console.log(data.instructions);
console.log(name);
if(isLoading) {
return (
<div> here dispolay a gif loading </div>
)};
return (
    
    <>
        <header className="topBar">
            <Link to={`/`} className='normal'>
                <div className='title'>
                    <SectionTitle titleText="Test Test" />
                    <SectionTitle titleText="Cooking Club" />
                </div>
            </Link>
        </header>
        <div>
            <SectionTitle titleText={name} />
            <div className="image">
                <img src={beauty_url} alt="recipe" />
            </div>
        </div>
        <section>
            <p>Serves {data.num_servings} </p>
            <p>Prep Time {data.num_servings} </p>
        </section>
        <section>
            <h3>Ingredients</h3>
        </section>
        <div>{country}</div>
        <div>{slug}</div>
        <div>slug: {recipeSlug}</div>
        <RecipeInstructions instructions={instructions} />         
    </>
)

  • Related