Skip to content

Instantly share code, notes, and snippets.

@kentcdodds
Created November 9, 2018 16:10
Show Gist options
  • Save kentcdodds/fb8540a05c43faf636dd68647747b074 to your computer and use it in GitHub Desktop.
Save kentcdodds/fb8540a05c43faf636dd68647747b074 to your computer and use it in GitHub Desktop.
a custom react hook that I want feedback on because it feels like a lot of work and maybe I'm missing something...
// Feedback requested on the useDeepCompareEffect
// it just feels like a bit of work...
// HERE'S THE REASON I NEED THIS:
// when people use the useQuery hook, they'll typically
// do so like this: `useQuery(myQuery, {var1: props.value})`
// which means that passing `variables` to `useEffect` will
// trigger a rerun of the callback even if they didn't
// actually change (referrential equality)
function useQuery({query, variables}) {
useDeepCompareEffect(
() => {
// make a request with the query and variables
},
[query, variables],
)
// more stuff goes here...
}
function useDeepCompareEffect(callback, inputs) {
const previousInputs = usePrevious(inputs)
const inputsAreEqual = _.isEqual(inputs, previousInputs)
// every time our effect callback runs, the cleanup
// function for the effect will change. If we don't
// actually call the callback, then the user's cleanup
// function wont be updated. So we'll keep track of the
// most recent cleanup function and return that if
// we don't actually call the callback.
const cleanupRef = useRef()
useEffect(
() => {
if (!inputsAreEqual) {
cleanupRef.current = callback()
}
return cleanupRef.current
},
// run the effect callback if equality changes
// this can change from [true] to [false]
// or from [false] to [true]
// both changes will cause the callback to re-run
// which is why we have the `if (!inputsAreEqual)`
// condition above.
[inputsAreEqual],
)
}
function usePrevious(value) {
const ref = useRef()
useEffect(() => {
ref.current = value
})
return ref.current
}
// P.S. if you ask me what font/theme this is, what editor it is,
// or how I made this screenshot... I'm not going to answer.
// --> http://kcd.im/mft
@cdock1029
Copy link

import isEqual from 'react-fast-compare'

function useQuery(params) {
  const [cachedParams, setCachedParams] = useState(params)
  const [data, setData] = useState()

  // ok to update the state during rendering, like "getDerivedStateFromProps"
  // https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
  if (!isEqual(cachedParams, params)) {
    setCachedParams(params)
  }

  useEffect(
    () => {
        const {query, variables} = cachedParams
        // make a request with the query and variables
        // setData(data)
    },
    [cachedParams],
  )
  return data
}

@kentcdodds
Copy link
Author

I think this is the proper solution actually:

function useDeepCompareMemoize(value) {
  const ref = React.useRef()

  if (!_.isEqual(value, ref.current)) {
    ref.current = value
  }

  return ref.current
}

function useDeepCompareEffect(callback, dependencies) {
  React.useEffect(callback, useDeepCompareMemoize(dependencies))
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment