Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mutation Observer hook #25

Open
danielr18 opened this issue Oct 26, 2018 · 4 comments
Open

Mutation Observer hook #25

danielr18 opened this issue Oct 26, 2018 · 4 comments

Comments

@danielr18
Copy link

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

@danielr18 danielr18 changed the title Mutation Observer hhok Mutation Observer hook Oct 26, 2018
@jackjocross
Copy link
Contributor

jackjocross commented Oct 26, 2018

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
Copy link
Author

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
Copy link

sag1v commented Oct 27, 2018

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
Copy link
Author

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;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants