Material-ui: setRef is possibly leaky

Created on 7 Nov 2018  路  4Comments  路  Source: mui-org/material-ui

Did some experimenting after reading reactjs/reactjs.org#834 and it turns out that changing a *ref prop can indeed cause a memory leak if it is handled by utils/reactHelpers#setRef. React will automatically clean up every ref attached to a node on update/unmount but every ref that is not attached to the node will still hold a reference to that node.

Live example: https://codesandbox.io/s/pkm11r8kjx

We would need to bookkeep every *ref prop that is passed to one of our components during its lifecycle and clean it up on cDU and cWU.

Not sure if this has any actual impact. Just documenting it in case someone actually encounters this issue.

discussion

Most helpful comment

A fix naturally emerges by using a custom hook:

// useForkRef.js
export default function useForkRef(refA, refB) {
  /**
   * This will create a new function if the ref props change.
   * This means react will call the old forkRef with `null` and the new forkRef
   * with the ref. Cleanup naturally emerges from this behavior
   */
  return useCallback(
    refValue => {
      setRef(refA, refValue);
      setRef(refB, refValue);
    },
    [refA, refB]
  );
}
// usage.js
function Component({ buttonRef: buttonRefProp }) {
  const buttonRef = React.useRef();
  const handleRef = useForkRef(buttonRef, buttonRefProp);

  React.useEffect(() => buttonRef.current.focus(), []);

  return <button ref={handleRef} />
}

The codesandbox is updated.

Once every component that uses setRef is a function component we can use this hook instead and make setRef private.

All 4 comments

A fix naturally emerges by using a custom hook:

// useForkRef.js
export default function useForkRef(refA, refB) {
  /**
   * This will create a new function if the ref props change.
   * This means react will call the old forkRef with `null` and the new forkRef
   * with the ref. Cleanup naturally emerges from this behavior
   */
  return useCallback(
    refValue => {
      setRef(refA, refValue);
      setRef(refB, refValue);
    },
    [refA, refB]
  );
}
// usage.js
function Component({ buttonRef: buttonRefProp }) {
  const buttonRef = React.useRef();
  const handleRef = useForkRef(buttonRef, buttonRefProp);

  React.useEffect(() => buttonRef.current.focus(), []);

  return <button ref={handleRef} />
}

The codesandbox is updated.

Once every component that uses setRef is a function component we can use this hook instead and make setRef private.

@eps1lon I believe that we solve the problem correctly now. Do we need the issue to be open? Is there any work left to be done?

I'll take a look.

Menu and RootRef still use it. Menu looks safe but RootRef is not. Until we remove this component we should keep this open for visibility.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mattmiddlesworth picture mattmiddlesworth  路  3Comments

pola88 picture pola88  路  3Comments

mb-copart picture mb-copart  路  3Comments

reflog picture reflog  路  3Comments

finaiized picture finaiized  路  3Comments