Deykun
Deykun

Reputation: 1271

Can I utilize eslint-disable-line to disable react-hooks/exhaustive-deps, but exempt particular props from being disabled not all of them?

I can turn that rule of for entire array:

useEffect(() => {
    if (id) {
        stableCallback(id);
        dynamicCallback(id);
    }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, dynamicCallback]);

But my preference would be to achieve something along these lines (pseudocode):

useEffect(() => {
    if (id) {
        stableCallback(id);
        dynamicCallback(id);
    }
// eslint-disable-next-line react-hooks/exhaustive-deps stableCallback
}, [id, dynamicCallback]);

In my imagination, the usage of "stableCallback" does not trigger the warning, but if a new dependency emerges within it, I will see the warning about it (I know that if stableCallback is not changed then it should not matter - but that's only an example).

Is there a rule similar to exhaustive-deps, or any alternative approach that would allow me to utilize it in a similar manner?

I haven't found any alternative to it.

Upvotes: 4

Views: 1710

Answers (3)

brandonscript
brandonscript

Reputation: 72865

I've spent quite a bit of time on this since I assigned the bounty to it, and I think that building semantically well-named custom hooks may be a great way to handle this. Instead of using a useEffect in place, you can create a custom hook that takes the specific dependency as a prop:

const useIdChanged = (onChange: (...args: unknown[]) => void, id: string) => {
  // very important that you call, not return the useEffect here
  useEffect(() => {
    onChange();
    // eslint-disable-next-line react-hooks/exhaustive-deps
    // or if you memoize the `onChange` function in the parent,
    // you can safely pass it as a dependency here, too
  }, [id]);
});

// ...

const SomeComponent = () => {

  useIdChanged(() => {
    if (id) {
      stableCallback(id);
      dynamicCallback(id);
    }
  }, id);

  return <></>

};

Despite the fact that you still have to use the eslint-disable-next-line flag, because you gave the hook a specific meaningful name, and the observable(s) are explicitly passed as named props, it's got some robust protection against "I don't know what this hook is doing, therefore, I will change it".

Upvotes: 1

Krzysztof Grajek
Krzysztof Grajek

Reputation: 11

Not a clean solution but it will let you mark the variable as intentionally ignored and perhaps could be easily implemented by 'react-hooks/exhaustive-deps' contributors.

Pass the dependency you intentionally ignore with a void operator:

useEffect(() => {
  if (id) {
    stableCallback(id);
    dynamicCallback(forgottenId);
  }
}, [
  void stableCallback, 
  id, 
  dynamicCallback
]);

This way, the dependency is listed in dependencies and won't cause useEffect to retrigger on change.

However 'react-hooks/exhaustive-deps' will complain about complex dependency anyways and won't see its literal, but it should be easily implemented in:

https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js (as else if near if (declaredDependencyNode.type === 'Literal') {).

Upvotes: 1

Ahmed Sbai
Ahmed Sbai

Reputation: 16189

You cannot do this and there is no rule for excluding a specific dependency. In fact, you don't have to follow Eslint, especially when it comes to including dependency, sometimes it suggests including values that you don't really want to include.

You should think of dependencies as data that once change value between renders, the corresponding hook is triggered.
If one dependency is immutable (string, number, boolean) then there is no problem but when it is mutable (object, array) then you have to memorize it using useMemo to avoid triggering the hook on every component render.

Now when you include a callback function as a dependency, this will make the hook run each time this function is recreated, I don't think you want to achieve that. maybe you just need to keep the callback function recreated each time it needs to, and trigger useEffect to run only when id is changed:

const dynamicCallback = useCallback((id) => {
  console.log(id + x + y);
},[x,y])

//...
useEffect(() => {
 if (id) {
   stableCallback(id);
   dynamicCallback(id);
 }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]);

This way, the most recent version of dynamicCallback will run each time id changes


If you really want to trigger useEffect every time dynamicCallback is recreated, maybe you could make it a simple function inside useEffect and include the necessary dependencies right there, so whenever the effect is triggered, the function is recreated and then called

useEffect(() => {
 const dynamicCallback = () => {
   console.log(id + x + y)
 }
 dynamicCallback();
}, [id, x, y]);

Upvotes: 4

Related Questions