jaredpalmer / the-platform

Web. Components. 😂
MIT License
4.4k stars 118 forks source link

Mutation Observer hook #25

Open danielr18 opened 6 years ago

danielr18 commented 6 years ago

Would you be interested to add a useMutationObserver hook?

My take on the implementation: https://github.com/danielr18/react-hook-mutation-observer/blob/master/index.js

jackjocross commented 6 years ago

Definitely!

I wonder if you would want an API without a callback so that you could use it in combination with React.useMemo:

let mutationRecord = useMutationObserver(
  document.getElementById("to-observe"),
  { attributes: true },
);

let updateAfterMutations = React.useMemo(handleMutations, [mutationRecord]);
danielr18 commented 6 years ago

That's another option as well, I think it depends on the usage.

I normally use it to process the mutations once as they occur, and the callback is a good fit for that. Using React.useMemo would achieve basically the same, except that it would additionally run an equality check on each render, as far as I know.

The good thing I see is that the return value of the hook is more easy to understand, the mutation records vs the result of the MutationObserver's callback.

However, we could modify it so that if no callback is passed, the value will be the mutationRecords, and then you could use React.useMemo as in your example.

let { value: mutationRecords } = useMutationObserver(
    document.getElementById("to-observe"),
    { attributes: true }
);

let updateAfterMutations = React.useMemo(
  () => {
    // ...
  },
  [mutationRecord],
);

What do you think?

sag1v commented 6 years ago

Came here to suggests useElementResize (using ResizeObserver under the hood), and saw this.
We may do something like this:

const useElementResize = ref => {
  const [rect, setRect] = React.useState({});

  React.useEffect(
    () => {
      const ro = new ResizeObserver((entries, observer) => {
        for (const entry of entries) {
          if (entry.target === ref.current) {
            // update state if this is our DOM Node
            // (we may support multiple refs)
            setRect(entry.contentRect);
          }
        }
      });
      // observe the passed in ref
      ro.observe(ref.current);
      return () => {
        // cleanup
        ro.disconnect();
      };
    },
    [ref] // only update on ref change
  );

  return rect;
};

Here is a demo if you want to explore

danielr18 commented 6 years ago

I modified it to always return the callback value, instead of an object.

const defaultCallback = mutationList => mutationList;

function useMutationObserver(targetNode, config, callback = defaultCallback) {
  const [value, setValue] = useState(undefined);
  const observer = useMemo(
    () =>
      new MutationObserver((mutationList, observer) => {
        const result = callback(mutationList, observer);
        setValue(result);
      }),
    [callback]
  );
  useEffect(
    () => {
      if (targetNode) {
        observer.observe(targetNode, config);
        return () => {
          observer.disconnect();
        };
      }
    },
    [targetNode, config]
  );

  return value;
}