WillKre
WillKre

Reputation: 6158

Get updated variable from within react hook before return

I have this custom fetch data hook

function fetchData(currentPage) {
  const [error, setError] = useState(null)
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    async function getData() {
      axios.get(`https://${MY_API}${currentPage}`)
        .then((res) => setData(res.data))
        .catch((err) => setError(err))
        .finally(() => setLoading(false))
    }
    getData()
  }, [currentPage])

  return { data, error, loading }
}

Which is being used as such:

const { data, error, loading } = fetchData(currentPage)

So whenever the currentPage changes, the hook is called.

I'm having trouble getting the loading variable to change to true on changing page, it's always false which makes sense as that's what's being returned at the end of the hook.

How do I deal with resetting loading to false WITHIN the hook? Or is there another way to deal with this?

Upvotes: 1

Views: 75

Answers (2)

Przemysław Zalewski
Przemysław Zalewski

Reputation: 3986

In order to simply fix your issue, the loading flag should be set by the effect itself:

function fetchData(currentPage) {
  const [error, setError] = useState(null)
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    setLoading(true);

    async function getData() {
      axios.get(`https://${MY_API}${currentPage}`)
        .then((res) => setData(res.data))
        .catch((err) => setError(err))
        .finally(() => setLoading(false))
    }
    getData()
  }, [currentPage])

  return { data, error, loading }
}

If you want to cancel pending requests and make sure they do not override the fetched data after page changes, you must cancel them out. The easiest solution would be simply to ignore the response (canceled flag). However, axios supports cancelation so the proper implementation is not that hard:

function fetchData(currentPage) {
  const [error, setError] = useState(null)
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    const CancelToken = axios.CancelToken;
    const source = CancelToken.source();
    let canceled = false;

    async function getData() {
      axios.get(
        `https://${MY_API}${currentPage}`,
        { cancelToken: source.token }
      )
        .then((res) => setData(res.data))
        .catch((err) => {
          if (!axios.isCancel(err)) {
            setError(err);
          }
        }
        .finally(() => {
          if (!canceled) {
            setLoading(false)
          }
        )
    }

    setLoading(true);

    getData();

    return () => {
      canceled = true;
      source.cancel();
    }
  }, [currentPage])

  return { data, error, loading }
}

Upvotes: 2

charliemei
charliemei

Reputation: 11

how about using await keyword?

function fetchData(currentPage) {
  const [error, setError] = useState(null)
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(true)

  useEffect(async () => {
    async function getData() {
      axios.get(`https://${MY_API}${currentPage}`)
       .then((res) => setData(res.data))
        .catch((err) => setError(err))
        .finally(() => setLoading(false))
    }
    await getData()
    getData()
  }, [currentPage])

  return { data, error, loading }
}

Upvotes: 0

Related Questions