useHooks(🐠)

Easy to understand React Hook recipes by Gabe Ragland
What's all this about?

Hooks are a new addition in React that lets you use state and other React features without writing a class. This website provides easy to understand code examples to help you learn how hooks work and inspire you to take advantage of them in your next project. You may also like my React starter kit ✨️

📩  Get new recipes in your inbox
Join 7,031 subscribers. No spam ever.

useMemoCompare

This hook gives us the memoized value of an object, but instead of passing an array of dependencies (like with useMemo) we pass a custom compare function that gets both the previous and new value. The compare function can then compare nested properties, call object methods, or whatever else you need to do in order to determine equality. If the compare function returns true then the hook returns the old object reference.

Most of the time this hook shouldn't be necessary. Where it really comes in handy is if you want to offer a library to other developers and it would be awkward to force them to always memoize a value before passing it to your library. Read through the recipe and inline comments below to get a better sense of how this all works. While this example is fairly abstract, I'll be posting a new recipe soon that would have been really difficult to build without using this internally. Be sure to subscribe above if you don't want to miss it ;)

import React, { useState, useEffect, useRef } from 'react';

// Usage
function MyComponent({ obj }) {
  const [state, setState] = useState();
  
  // We want the previous obj if obj.id is the same as the new obj.id
  // We pass a custom equality function as the second arg to our hook.
  const theObj = useMemoCompare(obj, prev => prev && prev.id === obj.id);
  
  // Here we want to fire off an effect if theObj 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.
  // (effect runs -> state change causes rerender -> effect runs -> etc ...)
  useEffect(() => {
    // Call a method on the object and set results to state
    return theObj.someMethod().then((value) => setState(value));
  }, [theObj]);
  
  // So why not just pass [obj.id] as dependecy array?
  useEffect(() => {
    // Well, then eslint-plugin-hooks would rightfully complain that obj is not in the
    // dependency array. By using our hook above we are more explicit about our custom
    // equality checking and can separate that concern from that of our effect logic.
    return obj.someMethod().then((value) => setState(value));
  }, [obj.id]);
    
  return <div> ... </div>;
}
  
// Hook
function useMemoCompare(value, compare) {
  // Ref for storing previous value
  const previousRef = useRef();
  const previous = previousRef.current;

  // Pass previous and new value to compare function
  const isEqual = compare(previous, value);

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

  return isEqual ? previous : value;
}
  • useEffect custom comparator - Related discussion in the React Github repo that has other potential solutions
  • Divjoy - React starter kit from the creator of usehooks.com
Next recipe: