Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
React Hook recipe from https://usehooks.com
import React, { useState, useEffect, useRef } from 'react';
// Usage
function MyComponent({ obj }) {
const [state, setState] = useState();
// Use the previous obj value if the "id" property hasn't changed
const objFinal = useMemoCompare(obj, (prev, next) => {
return prev && prev.id === next.id;
});
// Here we want to fire off an effect if objFinal changes.
// If we had used obj directly without the above hook and obj was technically a
// new object on every render then the effect would fire on every render.
// Worse yet, if our effect triggered a state change it could cause an endless loop
// where effect runs -> state change causes rerender -> effect runs -> etc ...
useEffect(() => {
// Call a method on the object and set results to state
return objFinal.someMethod().then((value) => setState(value));
}, [objFinal]);
// So why not pass [obj.id] as the dependency array instead?
useEffect(() => {
// Then eslint-plugin-hooks would rightfully complain that obj is not in the
// dependency array and we'd have to use eslint-disable-next-line to work around that.
// It's much cleaner to just get the old object reference with our custom hook.
return obj.someMethod().then((value) => setState(value));
}, [obj.id]);
return <div> ... </div>;
}
// Hook
function useMemoCompare(next, compare) {
// Ref for storing previous value
const previousRef = useRef();
const previous = previousRef.current;
// Pass previous and next value to compare function
// to determine whether to consider them equal.
const isEqual = compare(previous, next);
// If not equal update previousRef to next value.
// We only update if not equal so that this hook continues to return
// the same old value if compare keeps returning true.
useEffect(() => {
if (!isEqual) {
previousRef.current = next;
}
});
// Finally, if equal then return the previous value
return isEqual ? previous : next;
}
@zarcode

This comment has been minimized.

Copy link

@zarcode zarcode commented Apr 9, 2020

It seams to me that your compare function prev => prev && prev.id === obj.id accepts only one argument, but later on you are calling it with two compare(previous, value)

@gragland

This comment has been minimized.

Copy link
Owner Author

@gragland gragland commented Apr 10, 2020

It seams to me that your compare function prev => prev && prev.id === obj.id accepts only one argument, but later on you are calling it with two compare(previous, value)

In this example, since the compare function is defined inline, I already have obj in scope so don't need to use the second arg. If the function was extracted out though I'd need that arg.

@antonioru

This comment has been minimized.

Copy link

@antonioru antonioru commented Jul 8, 2020

@gragland
I really like the idea behind useMemoCompare but I think one of the purpose of useMemo is to avoid computation by memoization, if we pass the value to useMemoCompare the purpose of the hook might be a bit fuzzy, especially if we consider it an improvement over useMemo.
I suggest to rewrite it as the following:

const useMemoCompare = (fn, comparingFn) => {
  const valueRef = useRef(fn()); // perform the fn on the first render
  const isEqual = comparingFn(valueRef.current);

  // If not equal update previous to new value (for next render)
  // and then return new new value below.
  useEffect(() => {
    if (!isEqual) {
      valueRef.current = fn(valueRef.current);
    }
  }, [isEqual]);

  return valueRef.current;
};
@gragland

This comment has been minimized.

Copy link
Owner Author

@gragland gragland commented Jul 30, 2020

@antonioru That's a great point. I'm wondering if useMemoCompare is really the right name for this hook, as it's more about getting a previous value than avoiding computation. Maybe useRefCompare or usePreviousCompare? Here's an upcoming useHooks post I'm working on where this hook is used: https://gist.github.com/gragland/383b0b77b4d05792c3a5a3c6e8a265af

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.