Nested react hooks exposing static references

Viewed 121

I was looking the implementation of eslint-plugin-react-hooks and it looks like useState's setState and useReducer's dispatch functions are static references which are not required to be declared in the dependency array in useEffect.

However, this rule does not seem to work properly when you write a custom rule that abstracts an internal logic. for example, I create a custom hook:

const useCustom = () => {
    const [number, setNumber] = React.useState(0);
    return [number, setNumber];
};

which is then used the following way:

const [number, setNumber] = useCustom();
React.useEffect(() => {
   if (something) {
       setNumber(1);
   }
}, [something]); // useEffect has a missing dependency: 'setNumber'

Adding the dependency to the array does not seem to cause extra render cycles. However, it raises a another question: What if my hook returns a reference to an value returned from useRef() would that be safe to add it to the dependencies array?

2 Answers

The value from useRef will never change unless you unmount and remount the component.

Changing value.current will not cause the component to re render but if the value.current is a dependency of your effect then your effect will re run when the component re renders.

Not sure if this answers your question, I added sample code below to demonstrate:

const useTimesRendered = () => {
  const rendered = React.useRef(0);
  rendered.current++;
  return rendered;
};
const useCustomRef = () => {
  const customRef = React.useRef(0);
  return customRef;
};
const App = () => {
  const rendered = useTimesRendered();
  const custom = useCustomRef();
  const [, reRender] = React.useState({});
  React.useEffect(
    () => console.log('rendered ref only on mount', rendered.current),
    [rendered]
  );
  const customVal = custom.current;
  React.useEffect(
    () => console.log('custom ref.current only when re rendering', customVal),
    [customVal]
  );
  return (
    <div>
      <h1>Times rendered: {rendered.current}</h1>
      <button onClick={() => custom.current++}>
        increase custom
      </button>
      <button onClick={() => reRender({})}>
        Re render app
      </button>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

If I've understood your question correctly, than will go with yes. If ref never changes (capturing some node on component mount), or if it changes frequetly (like in usePrevious hook) it should be added to the list of dependencies. Only potential issue I could think of is when someone, for example use ref on input and than expect that will get a new updated ref after user type somenthing. But that is a topic for some other time

Related