3

I have problem with async function. I need track.user in another function but my func getTracks() async. I don't have clue how can i get this.

const Player = ({trackUrl, index, cover, id}) => {
    const [track, setTrack] = useState({})
    const [user, setUser] = useState({})

    useEffect(() => {
        const getTracks = async () => {
            await httpClient.get(`/track/${id}`)
                .then((response) => {
                    setTrack(response.data);
                })
        }
        getTracks();
        getUser() // track.user undefined 
    }, [])

    const getUser = async() => {
        await httpClient.get(`/profile/${track.user}/`)
            .then((response) => {
                setUser(response.data);
            })
    }
}
2
  • There's a issues here re. async code and how effects should be used. How are you planning to run that getUser function? Do you just want it to run on component mount? Or will it be some event handler's function, like when they click a button? Commented Jul 14, 2022 at 10:01
  • I want use getUser when component did mount. Commented Jul 14, 2022 at 10:07

3 Answers 3

2

I would declare both functions at the beginning of the component (you can later optimise them with useCallback but it's not that important in this phase).

const getTracks = async () => {
  await httpClient.get(`/track/${id}`)
    .then((response) => {
      setTrack(response.data);
    })
  }

const getUser = async() => {
  await httpClient.get(`/profile/${track.user}/`)
    .then((response) => {
      setUser(response.data);
  })
}

I would then call an async function inside the useEffect hook. There are a couple of ways of doing it: you can either declare an async function in the useEffect hook and call it immediately, or you can call an anonymous async function. I prefer the latter for brevity, so here it is:

useEffect(() => {
  (async () => {
    await getTracks();
    getUser();
  })();
}, []);

Now when you call getUser you should be sure that getTracks has already set the track variable.

Here is the complete component:

const Player = ({trackUrl, index, cover, id}) => {
  const [track, setTrack] = useState({})
  const [user, setUser] = useState({})

  const getTracks = async () => {
    await httpClient.get(`/track/${id}`)
      .then((response) => {
        setTrack(response.data);
      })
    }

  const getUser = async() => {
    await httpClient.get(`/profile/${track.user}/`)
      .then((response) => {
        setUser(response.data);
      })
    }

  useEffect(() => {
    (async () => {
      await getTracks();
      getUser();
    })();
  }, []);
}

EDIT 07/18/22

Following Noel's comments and linked sandbox, I figured out that my answer wasn't working. The reason why it wasn't working is that the track variable was't available right after the getTrack() hook execution: it would have been available on the subsequent render.

My solution is to add a second useEffect hook that's executed every time the track variable changes. I have created two solutions with jsonplaceholder endpoints, one (see here) which preserves the most of the original solution but adds complexity, and another one (here) which simplifies a lot the code by decoupling the two methods from the setTrack and setUser hooks.

I'll paste here the simpler one, adapted to the OP requests.

export default function Player({ trackUrl, index, cover, id }) {
  const [track, setTrack] = useState({});
  const [user, setUser] = useState({});

  const getTracks = async () => {
    // only return the value of the call
    return await httpClient.get(`/track/${id}`);
  };

  const getUser = async (track) => {
    // take track as a parameter and call the endpoint
    console.log(track, track.id, 'test');
    return await httpClient.get(`profile/${track.user}`);
  };

  useEffect(() => {
    (async () => {
      const trackResult = await getTracks();
      // we call setTrack outside of `getTracks`
      setTrack(trackResult);
    })();
  }, []);

  useEffect(() => {
    (async () => {
      if (track && Object.entries(track).length > 0) {
        // we only call `getUser` if we are sure that track has at least one entry
        const userResult = await getUser(track);
        console.log(userResult);
        setUser(userResult);
      }
    })();
  }, [track]);
  return (
    <div className="App">{user && user.id ? user.id : "Not computed"}</div>
  );
}
Sign up to request clarification or add additional context in comments.

8 Comments

Now when you call getUser you should be sure that getTracks has already set the track variable.You havnnt done this in your code.The present code calls getUser with empty obj
Sorry @Noel I don't understand your comment. Is it because I don't pass track as a parameter to the getUser function? Please notice that the user variable is set by the setUser function inside getUser, and that the await keyword before getTracks makes sure that getUser will only get called once everything in getUser will have been executed (including the setUser part).
No my friend.You have a wrong understanding of the await keyword. await doesnt wait for the setUser part. Check this sandbox console for demo.
Yes, you're right that my code was incorrect, but the reason is not that the await doesn't wait for the setUser part. Even when the setUser part is executed before, the user is not available to use: it will be on the next render. The solution is to use the user variable in a separate useEffect hook (as already proposed in this answer). Here is my sandbox. Thanks for the heads up, I'll edit my answer.
I'm writing with reference to your first comment await keyword before getTracks makes sure that getUser will only get called once everything in getUser will have been executed (including the setUser part). the last line isn't true
|
2

You can move the second request to the then block of the dependent first request,i.e., getTracks. Also, you shouldn't mix then and await.

useEffect(() => {
    const getTracks = () => {
        httpClient.get(`/track/${id}`)
            .then((response) => {
                setTrack(response.data);
                httpClient.get(`/profile/${response.data.user}/`)
               .then((response) => {
                 setUser(response.data);
               })
            })
    }
    getTracks();
}, [])

6 Comments

I have an error Unexpected reserved word 'await'.
Because you can't use both then and async-await.
@Volear You shouldn't use then inside await. I've updated my answer
Because, in this code, async also needs to be on the then callback. But really you shouldn't be mixing thenables with async/await code anyway. @Volear
What's the point of async in this code now you've edited it? @NeERAJTK
|
1

You shouldn't be mixing thens with async/await. You should be using another useEffect that watches out for changes in the track state and then calls getUser with that new data.

function Player(props) {

  const { trackUrl, index, cover, id } = props;

  const [ track, setTrack ] = useState({});
  const [ user, setUser ] = useState({});

  async function getTracks(endpoint) {
    const response = await httpClient.get(endpoint);
    const data = await response.json();
    setTrack(data);
  }

  async function getUser(endpoint) {
    const response = await httpClient.get(endpoint);
    const data = await response.json();
    setUser(data);
  }

  useEffect(() => {
    if (id) getTracks(`/track/${id}`);
  }, []);

  useEffect(() => {
    if (track.user) getUser(`/profile/${track.user}`);
  }, [track]);

}

1 Comment

if(track) inside second useEffect would always return true as state declaration is in object, thus breaking the app.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.