skip to Main Content

I need to access an API in a loop, but for the life of me I cannot seem to store the data to be used outside the loop. I can’t use useState because it continuously calls the API and I have a limited amount of calls to the API I’ll be using (and I’ve been reading that I shouldn’t use it in loops/conditions anyway), I’m just using a random free API now for testing.

Here’s the code:

    const formatRec = () => {
        let rec = [];
        let no_movies = document.getElementById("no_of_movies").value;
        
        for (let i = 1; i <= no_movies; i++) {

            let imdbHolder = "No";
            fetch(`https://catfact.ninja/fact`, {
                method: 'GET',
            })
            .then(res => res.json())
            .then(data => {
                imdbHolder = data.fact;
                console.log(imdbHolder);
            });

            rec.push(<p>{prediction.data.Recommendatons[0][i]} - {imdbHolder}</p>);
            console.log("catfact?: " + imdbHolder);
        }

        return rec;
    };

The only thing I’ve tried that even nearly works is using global.imdbHolder instead of just imdbHolder, but the problem is that the data itself is the same on every item pushed with global.imdbHolder only storing the last response outside of the fetch request (The console log in the fetch request does return the different data though).

Does anyone know how I could go about solving this?

Sorry if this has already been answered, I’ve seen some people with similar problems, but their solutions either weren’t applicable for me or didn’t work.

Edit: Here’s my entire App.js file, incase it can help at all:

import { useState } from 'react';

const App = () => {
    const [prediction, setPrediction] = useState(null);
    //const [imdb, setImdb] = useState(null);

    const onSubmit = async e => {
        e.preventDefault();

        const formData = new FormData();
        formData.append("target_user", document.getElementById("target_user").value)
        formData.append("no_of_highest", document.getElementById("no_of_highest").value)
        formData.append("no_of_similar_users", document.getElementById("no_of_similar_users").value)
        formData.append("no_of_movies", document.getElementById("no_of_movies").value)

        fetch('http://localhost:5000/upload', {
            method: 'POST',
            body: formData
        })
        .then(res => res.json())
        .then(data => {
            console.log("checkpoint1");
            console.log(data.data);
            setPrediction(data);
        });
    };

    const formatTop = () => {
        let top = [];
        for (let i = 0; i <= document.getElementById("no_of_highest").value; i++) {
            top.push(<p>{prediction.data.Users_Top_Movies[0][i]}</p>);
        }
        return top;
    };

    const formatSim = () => {
        let sim = [];
        for (let i = 0; i <= document.getElementById("no_of_similar_users").value-1; i++) {
            sim.push(<p>User #{prediction.data.Similar_Users[i]}, seperated by a distance of {prediction.data.Sim_User_distances[i]}</p>);
        }
        return sim;
    };

    const formatRec = () => {
        let rec = [];
        let no_movies = document.getElementById("no_of_movies").value;
        
        for (let i = 1; i <= no_movies; i++) {

            // The real API I want to call from and attach to each movie
            // fetch(`https://imdb-api.com/en/API/SearchMovie/<my API key>/${prediction.data.Recommendatons[0][i]}`, {
            fetch(`https://catfact.ninja/fact`, {
                method: 'GET',
            })
            .then(res => res.json())
            .then(data => {
                imdbHolder = data.fact;
                shitest.push(data.fact)
            });
            
            // How I want to display the data from both APIs
            // rec.push(<p>{prediction.data.Recommendatons[0][i]} - <a href={'https://www.imdb.com/title/' + imdbHolder}>IMDb link</a></p>);
            rec.push(<p>{prediction.data.Recommendatons[0][i]} - {global.imdbHolder}</p>);
        }

        return rec;
    };

    return (
        <div classMovieName='App'>
            <form onSubmit={onSubmit}>
                <div className='custom-file'>
                    <label for="target_user">target_user</label>
                    <input type="number" id="target_user" name="target_user" />
                    <br />
                    <br />
                    <label for="no_of_highest">no_of_highest_rated_movies_by_target_user</label>
                    <input type="number" id="no_of_highest" name="no_of_highest" />
                    <br />
                    <br />
                    <label for="no_of_similar_users">no_of_similar_users</label>
                    <input type="number" id="no_of_similar_users" name="no_of_similar_users" />
                    <br />
                    <br />
                    <label for="no_of_movies">no_of_movies_to_recommend</label>
                    <input type="number" id="no_of_movies" name="no_of_movies" />
                </div> 
                <input
                    type='submit'
                    value='Submit'
                    className='btn btn-primary btm-block mt-4'
                />
            </form>
            <h1>Users Top Movies</h1>
            { prediction && formatTop() }
            <br />
            <h1>Users Most Similar to Target User</h1>
            { prediction && formatSim() }
            <br />
            <h1>Recommendations Based on Similar Users</h1>
            { prediction && formatRec() }
        </div>
    );
};

export default App;

So basically, I’m already taking in data (movie data) from my own backend through Flask, and I’m receiving that data as JSON in React, now I’m trying to access a a different API (IMDb, but testing with this cat fact one) and get information that can be tied to each data point from the flask and then output on the same line.

Edit #2: Here’s what it looks like (The IMDb links all only link to the first one (using global.imdbHolder)):

Client view

2

Answers


  1. From what I understood, you want to fetch data from an API multiple times and store the final result outside so that you can use it in other places.

    You can use setState to store the final value of the rec to store. Updating the state values will rerender the component, but it will not run the functions.

    I’ve added my answer here: https://codesandbox.io/s/winter-rgb-bcvelx?file=/src/App.js

    Hope this helps.

    Login or Signup to reply.
  2. There is a lot of information/question in your problem, I will try to cut them one by one to help you the best.

    I can’t use useState because it continuously calls the API

    Unfortunately, you haven’t provided any code for your UseEffect, but it looks like there is a misuse of the dependency array. UseEffect only applies if and only if the array changes. No array or a bad dependency can quickly create an infinite loop and, effectively, call the API a lot of times. To better understand, I advise you to take a look to the react documentation. And also see the previous anwser for a practical code example.

    the problem is that the data itself is the same on every item pushed

    I assume you are talking about the data display (returned by the component). Since you’re not using UseState, react has no way of knowing that it needs to update. If react isn’t aware of the changes, it won’t render it, and so the data displayed stays the same (even if your local variables change). I’ll let you look at the UseState documentation to better understand.

    The only thing I’ve tried that even nearly works is using global.imdbHolder

    Variables used by UseState and global variables are two completely different things. UseState only lives locally, within a component. When it comes to storing global variables (synchronizing at the application level and not at the component level), there are several solutions, for example:

    Hope this helps.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search