0

I need to call the dropbox api twice. The first useEffect gets the names and paths and then pushs them into a blank array in context. Then I need the second useEffect to fire which relies on the arrays being populated from the first useEffect. I have attempted to useState and create loading and setLoading and then wrap the inside of the useEffect in and if(loading) but that didn't work because I am guessing it only tries to fire when the component is mounted. How do I make the second useEffect run once the first has completed?

const [user, setUser] = useContext(UserContext);

First useEffect:

useEffect(() => {
    var myHeaders = new Headers();
    myHeaders.append(
      'Authorization',
      'Bearer XXXXXXXXXXXXXXX',
    );
    myHeaders.append('Content-Type', 'application/json');

    var raw = JSON.stringify({path: `/${user.username}`});

    var requestOptions = {
      method: 'POST',
      headers: myHeaders,
      body: raw,
      redirect: 'follow',
    };

    fetch('https://api.dropboxapi.com/2/files/list_folder', requestOptions)
      .then(response => response.json())
      .then(data => {
        let tmpArray = [];
        let tmpArrayTwo = [];
        for (var i = 0; i < data.entries.length; i++) {
          tmpArray.push(data.entries[i].name);
          tmpArrayTwo.push(data.entries[i].path_lower);
        }
        setUser({
          ...user,
          imageNames: tmpArray,
          imagePaths: tmpArrayTwo,
        });
      })
      .catch(error => console.log('error', error));
  }, []);

Second useEffect:

useEffect(() => {
    let tmpArray = [];
    var myHeaders = new Headers();
    myHeaders.append(
      'Authorization',
      'Bearer XXXXXXXXXXXXXX',
    );
    myHeaders.append('Content-Type', 'application/json');

    for (var i = 0; i < user.imagePaths.length; i++) {
      var raw = JSON.stringify({path: `/${user.imagePaths[i]}`});
      var requestOptions = {
        method: 'POST',
        headers: myHeaders,
        body: raw,
        redirect: 'follow',
      };
      fetch(
        'https://api.dropboxapi.com/2/files/get_temporary_link',
        requestOptions,
      )
        .then(response => response.json())
        .then(data => {
          tmpArray.push(data.link);
        })
        .catch(error => console.log('error', error));
    }
    console.log(tmpArray);
    setUser({
      ...user,
      imageLinks: tmpArray,
    });
  }, []);

I am thinking it has something to do with the dependency array but everything I have put in there (like user, setUser) gives me an infinite loop and the console.log for the user.imagePaths keeps logging as []. I know user.imagePaths is populated after the first useEffect but the second useEffect keeps getting undefined for the user.imagePaths.length for the loop.

gmacerola
  • 57
  • 1
  • 7

2 Answers2

1

You should add user.imagePaths to the dependency list of the second useEffect.

I would suggest using async/await and making the calls in single useEffect though.

If the user is undefined by default then you might need to add optional chaining.

[UPDATE]

Try changing the second useEffect to this:

useEffect(() => {
    var myHeaders = new Headers();
    myHeaders.append(
        'Authorization',
        'Bearer XXXXXXXXXXXXXX',
    );
    myHeaders.append('Content-Type', 'application/json');

    if (!user?.imagePaths) {
        return;
    }

    Promise.all(user.imagePaths.map(path => {
        var raw = JSON.stringify({path: `${path}`});
        var requestOptions = {
          method: 'POST',
          headers: myHeaders,
          body: raw,
          redirect: 'follow',
        };
        return fetch(
          'https://api.dropboxapi.com/2/files/get_temporary_link',
          requestOptions,
        )
          .then(response => response.json())
          .then(data => data.link)
          .catch(error => console.log('error', error));
      }),
    ).then(imageLinks => {
      setUser({
        ...user,
        imageLinks,
      });
    });
  }, [user?.imagePaths]);
slaid3r
  • 176
  • 6
  • I tried putting user.imagePaths inside the [] of the second useEffect but I get "TypeError: undefined is not an object (evaluating 'user.imagePaths.length')" I know that user.imagePaths is valid because if I comment out the second useEffect is can render user.imagePaths in the render part of this component. – gmacerola Mar 21 '21 at 20:12
  • Have you tried using optional chaining on this line `user.imagePaths.length`? – slaid3r Mar 21 '21 at 20:15
  • I dont know what you are referring to with optional chaining.... – gmacerola Mar 21 '21 at 20:33
  • Change `user.imagePaths.length` to `user?.imagePaths?.length` Here's a MDN link: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining – slaid3r Mar 21 '21 at 20:38
  • I did that as well, nothing. I am seeing that the second useEffect is running twice....I don't know why. – gmacerola Mar 21 '21 at 20:58
  • The second useEffect will run twice as at first the user is undefined (or has some default value) then the first useEffect is fired, it updated the user so that triggers the second useEffect because the reference to user.imagePaths chaged (we have a different object there now). – slaid3r Mar 21 '21 at 21:03
  • 1
    Also take a look at Aymer-El's response, there is other issue with the second useEffect which is the fact that you're setting the user before fetching of images is done (you're not waiting for the promises to resolve). When you push image to the tmpArray the call to setUser is already done. – slaid3r Mar 21 '21 at 21:08
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/230197/discussion-between-gmacerola-and-slaid3r). – gmacerola Mar 21 '21 at 21:32
  • Your second effect is running twice because you've set the useEffect dependency to user. That goes from {} or undefined to a defined user. Hence, you'll pass twice. I would have personally add a condition as I mentioned in my solution. Do not forget that what you put as dependency [] in the useEffect makes the useEffect happens when this value changes. And Thank for the answer @slaid3r, regarding the promise all. – AymericFi Mar 22 '21 at 13:55
1

From what I understand. I would suggest you to first use a state in order to have your data in the lifecycle of React.

const [listFolders, setListFolders] = React.useState([])
const [user, setUser] = React.useState({})

Your first useEffect will load on the first mount of the component and will not get call back afterthe thanks to the [] place at the end of the function. This allow to tell to React to trigger the effect only on no-dependency.

useEffect(() => {
   ...
}, [])

Your second useEffect will load only when the array listFolders is changed. Aside, the goal here is to concatenate all your promise, so that it updates only one time the user. Otherwise we would have trouble of async.

useEffect(() => {
   if(listFolders.length){

      Promise.all([*put all your promise here*]).then((data) => {
          * concatenate your array of link
          setUser({
             ...user,
             imageLinks: arrayOfLinks,
          })
      })
    }
}, [listFolders])
AymericFi
  • 140
  • 1
  • 8
  • I tried this but I don't exactly understand what I put inside the () after promise.all, just the fetch because the loop is before the fetch and it is what needs the length to start as it has to loop through and call over and over again. – gmacerola Mar 21 '21 at 21:02