简体   繁体   中英

Reset React state before a render

I'm building a web application that consumes TMDB Api. I have the following code that fetch all information about a TV Show

export const useShowInfoFetch = ({showId}) => {

  const [data, setData] = useState({})
  const [loading, setLoading] = useState(false)
  const [_error, _setError] = useState(false)

  const fetchShowInfo = useCallback(() => {
    setLoading(true)

    try {
      axios.get(getShowInfo(showId))
        .then(response => {
          setData(response.data)
        })
    } catch (error) {
      _setError(true)
    } finally {
      setLoading(false)
    }
  }, [showId])

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

  return [data, loading, _error]
}

All the information fetched is displayed in page, that also has Links with react-router-dom . Those links goes to another tv show page. The problem is that when I'm in a page with a tv show that has X amount of seasons and I click a tv show with less seasons, the seasons from the page I was are persisting for a little bit of time. So, when I fetch the information for each season I got a 404 in the page that has less seasons.

Here is a screenshot of the error

在此处输入图像描述

The orange circle is what it's displayed since I click the tv show with less seasons. As you can see, the seasons from the previous page are persisting for a little time, and because The Alienist has only 2 seasons (not 9) I get the 404. You can also note that latter, the correct amount of seasons are displayed.

I've tried to add a cleanup method in the useEffect hook. Something like this:

useEffect(() => {
    fetchShowInfo()
    
    return function cleanup() {
      setData({})
    }
  }, [fetchShowInfo])

But this did not work.

I know that I can handle that with a catch after the then Axios promise, but I want to figure out why this is happening and fix the issue with a good solution instead of avoiding it.

Any help is welcomed and I can share the repository with all the code if needed.

EDIT:

To display the similar movies I use another custom hook

export const useSimilarFetch = (elementType, elementId) => {

  const [similarElements, setSimilarElements] = useState({elements: []})
  const [similarLoading, setSimilarLoading] = useState(false)
  const [_error, _setError] = useState(false)

  const fetchSimilarElements = useCallback(async (endpoint) => {
    console.log(">>> fetching similar elements <<<")
    setSimilarLoading(true)

    try {
      await axios.get(endpoint)
        .then(response => {
          setSimilarElements(() => ({
            elements: [...response.data.results],
            currentPage: response.data.page,
            totalPages: response.data.total_pages
          }))
        })
    } catch (error) {
      _setError(true)
    } finally {
      setSimilarLoading(false)
    }
  }, [])

  useEffect(() => {
    fetchSimilarElements(getSimilar(elementType, elementId));
  }, [fetchSimilarElements, elementType, elementId])

  return [{similarElements, similarLoading, _error}, fetchSimilarElements]
}

Then, in my ShowInfoComponent I call all the needed hooks like this:

const {showId} = useParams()
const [data, loading, _error] = useShowInfoFetch({showId})
const [{similarElements, similarLoading}] = useSimilarFetch("tv", showId)

Thanks.

By the time showId changes, data has to wait one additional render cycle, so showId is already used even though data has not yet been fetched. The UI relies on both showId and data , yet data depends on showId . One way to solve this could be having your UI to rely on data alone. What about the id? Add it to data for example. We merely want to avoid the desynchronization.

Something like this:

export const useShowInfoFetch = ({showId}) => {

  const [data, setData] = useState({})
  const [loading, setLoading] = useState(false)
  const [_error, _setError] = useState(false)

  const fetchShowInfo = useCallback(() => {
    setLoading(true)

    try {
      axios.get(getShowInfo(showId))
        .then(response => {
          setData({ id: showId, info: response.data})
        })
    } catch (error) {
      _setError(true)
    } finally {
      setLoading(false)
    }
  }, [showId])

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

  return [data, loading, _error]
}

Then use data.id to build your links. If response.data already contains the id, then even better, use that.

That's just an example, of course but hopefully you get the idea.

I might be wrong but I believe you are not watching the correct value on the useEffect. You should be watching showId and not the function fetchShowInfo . That is:

 useEffect(() => {
  fetchShowInfo()
 }, [showId]) --> HERE

And as you are memoized the callback, if you are watching the wrong variable then you will get back the 'last answered'.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM