Call function to get data and navigate to link on data arrival

Viewed 49

I have a situation where I should get a song item by id to get the path for that song, and then navigate to that song on button click.

Is there any specific hook that can be used to navigate on data arrival, useEffect will be called any time that state changes but the problem is that first needs to be dispatched the action to get the song, check if it returns any item and then navigate. Typically if it is has been published on the list, it should exist on the db, but the problem might be at the API side, so that check results.length > 0 is why that check is necessary.

useEffect(() => {
  const handleClick = (myId: string) => {
    dispatch(SongActions.searchSong(myId));
    if (results.length > 0) {
      if (Object.keys(results[0]).length > 0) {
        // navigate(`/songs/${results[0].myPath}`);
      }
    }
  }
}, [dispatch, results])

When user clicks on list item which has a song title, it should call the function handleClick(id) with id of the song as parameter, that is to get the metadata of the song, src path etc.

<Typography onClick={() => handleClick(songItem.songId)} sx={styles.songListItemText}>{songItem.Title}</Typography>

Edit

And this is how I have setup the searchSong action:

searchSong: (obj: SearchSongInputModel): AppThunk<SearchPayload> => async (dispatch) => {
  dispatch({
    payload: { isLoading: true },
    type: SearchActionType.REQUEST,
  });
  try {
    const response = await SearchApi.searchSongAsync(obj);
    if (response.length === 0) {
      toast.info(`No data found: ${obj.SongId}`)
    }
    dispatch({
      type: SearchActionType.RECEIVED_SONG,
      payload: { results: response },
    });
  } catch (e) {
    console.error("Error: ", e);
  }
}
2 Answers

You can create a state such as const [isDataPresent, setIsDataPresent] = useState(false) to keep track of if the data has arrived or not. And as David has mentioned in the comments you cannot call the function inside the useEffect on handleClick. Instead what you can do is create that function outside the useEffect hook and inside the same function you fetch the data and check if the data is at all present, if present then you can set the above boolean state to true and then redirect from that function itself.

Since you are already fetching the data from the same API and different endpoint, what you can do is -

  1. Create a new component.
  2. Since you are mapping over the data send the data to this component by rendering it inside the map function. It'd allow the data to be passed and components to be rendered one by one.
  3. Create a state in the new component.
  4. Use useEffect hook to fetch the data for a single song since when you are passing the data from the previous component to this one you would also get access to the ID and store it inside the state. This would be occurring inside the newly created component.

You appear to be mixing up the purpose of the useEffect hook and asynchronous event handlers like button element's onClick handlers. The useEffect hook is to meant to issue intentional side-effects in response to some dependency value updating and is tied to the React component lifecycle, while onClick handlers/etc are meant to respond to asynchronous events, i.e. a user clicking a button. They don't mix.

Assuming SongActions.searchSong is an asynchronous action, you've correctly setup Redux middleware to handle them (i.e. Thunks), and the action returns the fetched response data, then the dispatched action returns a Promise that the callback can wait for.

Example:

const navigate = useNavigate();
const dispatch = useDispatch();

const handleClick = async (myId: string) => {
  const results = await dispatch(SongActions.searchSong(myId));
  if (results.length > 0 && Object.keys(results[0]).length > 0) {
    navigate(`/songs/${results[0].myPath}`);
  }
};

...

<Typography
  onClick={() => handleClick(songItem.songId)}
  sx={styles.songListItemText}
>
  {songItem.Title}
</Typography>

The searchSong action creator should return a resolved value for consumers to await for.

searchSong: (obj: SearchSongInputModel): AppThunk<SearchPayload> => async (dispatch) => {
  dispatch(startRequest());
  try {
    const results = await SearchApi.searchSongAsync(obj);
    if (!results.length) {
      toast.info(`No data found: ${obj.SongId}`)
    }
    dispatch(receivedSong({ results }));
    return results; // <-- return resolved value here
  } catch (e) {
    console.error("Error: ", e);
  } finally {
    dispatch(completeRequest());
  }
}
Related