Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Define a function inside useEffect or outside?

Why the fetchData function is defined inside the useEffect and not outside ?

Link: https://github.com/zeit/next.js/blob/canary/examples/with-graphql-faunadb/lib/useFetch.js

import { useState, useEffect } from 'react'

export default function useFetch(url, options) {
  const [data, setData] = useState(null)
  const [error, setError] = useState(null)

  useEffect(() => {
    const fetchData = async () => {
      try {
        const res = await fetch(url, options)
        const json = await res.json()

        setData(json)
      } catch (error) {
        setError(error)
      }
    }
    fetchData()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [url])

  return { data, error }
}

I would have done that:

import { useState, useEffect } from 'react'

export default function useFetch(url, options) {
  const [data, setData] = useState(null)
  const [error, setError] = useState(null)

  // Defined outside of useEffect
  // `u` instead of `url` for not overlapping
  // with the one passed in useFetch()
  const fetchData = async (u) => {
    try {
      const res = await fetch(u, options)
      const json = await res.json()

      setData(json)
    } catch (error) 
      setError(error)
    }
  }

  useEffect(() => {
    // Using url as an argument
    fetchData(url)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [url])

  return { data, error }
}

It seems easier to read and better organized. I'm thinking it's maybe an anti-pattern or something else ?

like image 820
Adrien Avatar asked Apr 30 '20 16:04

Adrien


Video Answer


1 Answers

I typically define the functions inside the useEffect, there are several reasons for that

  1. By definining the function outside of the use effect, you either need to disable exhaustive-deps and risk accidentally having a stale function or you need to useCallback to make the function not-update every render
  2. If the function is only used in the useEffect, you don't need to recreate the function on every render as that's just wasted cycles
  3. It's easier to work with cleanup on the asynchronous functions by defining it within useEffect as you can define variables that are able to be modified within the effect.

On that last one, for instance, you can do some actions to prevent state being called when the effect cleans up.

You could also use AbortController with fetch to cancel the fetch.

import { useState, useEffect } from 'react'

export default function useFetch(url, options) {
  const [data, setData] = useState(null)
  const [error, setError] = useState(null)

  useEffect(() => {
    let isUnmounted = false;
    const fetchData = async () => {
      try {
        const res = await fetch(url, options)
        const json = await res.json()
        if(!isUnmounted) setData(json)
      } catch (error) {
        if(!isUnmounted) setError(error)
      }
    }
    fetchData()
    return ()=>{isUnmounted = true;}
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [url])

  return { data, error }
}
like image 62
Zachary Haber Avatar answered Oct 05 '22 03:10

Zachary Haber