Home > other >  Why is react react-hooks/exhaustive-deps changing my code?
Why is react react-hooks/exhaustive-deps changing my code?

Time:10-12

I would like to do something like this:

//snippet 1
const logFooOnlyOnce = (foo: string) => console.log(foo) //"...only once during the lifetime of this component"

export const MyComponent = ({ foo }: { foo: string }): JSX.Element => {
  React.useEffect(() => {
    logFooOnlyOnce(foo)
  }, []) //<-- error
  return <div />
}

Ie. i want to look at a prop of a react component only once and only in a certain scope. This code works with plain JS as i would expect it.

But the eslint rule won't let me. It changes my code to do this:

//snippet 2
import React from 'react'

const logFooOnlyOnce = (foo: string) => console.log(foo) //"...only once during the lifetime of this component"

export const MyComponent = ({ foo }: { foo: string }): JSX.Element => {
  React.useEffect(() => {
    logFooOnlyOnce(foo)
  }, [foo])
  return <div />
}

However this is not what i want, if foo changes it's value, it will be logged more than once.

This seems to work, though:

//snippet 3
const logFooOnlyOnce = (foo: string) => console.log(foo) //"...only once during the lifetime of this component"

export const MyComponent = ({ foo }: { foo: string }): JSX.Element => {
  const fooRef = React.useRef(foo)
  React.useEffect(() => {
    logFooOnlyOnce(fooRef.current)
  }, [])
  return <div />
}

If snippet 3 is valid, can i ignore the empty dep list in snippet 1? I don't want to handle an edge case, i want to ignore it altogether.

Also, i notice that the rule can somewhat be cheated with:

//snippet 4
const logFooOnlyOnce = (foo: string) => console.log(foo) //"...only once during the lifetime of this component"
const myCallback = (foo: string) => () => {
  logFooOnlyOnce(foo)
}
export const MyComponent = ({ foo }: { foo: string }): JSX.Element => {
  React.useEffect(myCallback(foo), [])
  return <div />
}

As well as this:

//snippet 5
const logFooOnlyOnce = (foo: string) => console.log(foo) //"...only once during the lifetime of this component"
export const MyComponent = ({ foo }: { foo: string }): JSX.Element => {
  const myCallback = (foo: string) => () => logFooOnlyOnce(foo)
  React.useEffect(myCallback(foo), [])
  return <div />
}

Why is this rule complaining in this case, and what would be the cleanest way to achieve what i want? In order to apply an eslint-ignore directive, i need to have a very good case to argue with a higher power.

CodePudding user response:

Just ignore the rule, using this comment

// eslint-disable-next-line react-hooks/exhaustive-deps

Ex.

export const MyComponent = ({ foo }: { foo: string }): JSX.Element => {
  React.useEffect(() => {
    logFooOnlyOnce(foo)
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])
  return <div />
}

CodePudding user response:

If you dislike ignoring the rule, you can also make a helper function that makes it clear what you want without having to put eslint-disables everywhere:

import { EffectCallback, useEffect } from 'react';

const useEffectOnce = (effect: EffectCallback) => {
    useEffect(effect, []);
};

(You can reference this but it's extremely short)

  • Related