最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to implement componentDidMount with hooks in React to be in line with the EsLint rule "react-hooksexha

programmeradmin0浏览0评论

According to the official React documentation, componentDidMount is translated in hooks as:

useEffect(() => {
 //code here 
},[])

So assuming I want to do an api call within this hook:

useEffect(() => {
 getActiveUser(); 
},[])

After adding the eslint rule "react-hooks/exhaustive-deps", this is a lint error. In order to silence it I can just drop the getActiveUser function inside the array and everything works just fine.

But does that go against the documentation? I was under the impression that the array checks for prop changes. I would like also to point out that the API call is being made without a prop/id, so I could understand the fact of having to do something like that:

useEffect(() => {
 getActiveUser(someId); 
},[getActiveUser, someId])

So what's going on here? Adding the Eslint rule mean that the array inside the effect can't be empty again?

According to the official React documentation, componentDidMount is translated in hooks as:

useEffect(() => {
 //code here 
},[])

So assuming I want to do an api call within this hook:

useEffect(() => {
 getActiveUser(); 
},[])

After adding the eslint rule "react-hooks/exhaustive-deps", this is a lint error. In order to silence it I can just drop the getActiveUser function inside the array and everything works just fine.

But does that go against the documentation? I was under the impression that the array checks for prop changes. I would like also to point out that the API call is being made without a prop/id, so I could understand the fact of having to do something like that:

useEffect(() => {
 getActiveUser(someId); 
},[getActiveUser, someId])

So what's going on here? Adding the Eslint rule mean that the array inside the effect can't be empty again?

Share Improve this question edited Jul 19, 2019 at 14:57 Retsam 33.4k11 gold badges71 silver badges94 bronze badges asked Jul 19, 2019 at 10:56 KT-mongoKT-mongo 2,2124 gold badges19 silver badges30 bronze badges 5
  • 3 "A common mistake is to think functions shouldn’t be dependencies. ....But the problem with simply omitting local functions is that it gets pretty hard to tell whether we’re handling all cases as the component grows!" via So you can't be sure (or can you) this function will change where do you define it in time. So, to be safe either you add it in the dependencies or define it in the useEffect. – devserkan Commented Jul 19, 2019 at 11:10
  • Possible duplicate of stackoverflow.com/questions/55840294/… – Shubham Khatri Commented Jul 19, 2019 at 11:50
  • put your function inside useEffect and Id to array only – victor zadorozhnyy Commented Jul 19, 2019 at 11:51
  • @Shubham Khatri not a duplicate, I'm adressing a different issue here which is: Using the eslint rule, does that mean array can't be empty again? – KT-mongo Commented Jul 19, 2019 at 12:32
  • maybe better re-think how it works and provide getActiveUser as a dependency. see discussion github.com/facebook/react/issues/14920#issuecomment-470913287 – skyboyer Commented Jul 19, 2019 at 15:07
Add a comment  | 

1 Answer 1

Reset to default 20

It matters where getActiveUser is declared. The question doesn't specify, but I assume your component looks something like this:

const MyComponent = (props) => {
    const getActiveUser() => {
       //...
    }
    useEffect(() => {
        getActiveUser();

    }, []) // Lint error.
    return <></>;
}

If instead your component looked like this, you wouldn't get a linter error:

const getActiveUser() => {
    //...
}
const MyComponent = (props) => {
    useEffect(() => {
        getActiveUser(); 

    }, []) // No error
    return <></>;
}

So why is the first a linter error and the second not? The point of the linter rule is to avoid issue due to stale props or state. While getActiveUser is not itself a prop or state, when its defined inside the component, it may depend on props or state, which may be stale.

Consider this code:

const MyComponent  = ({userId}) => {
    const [userData, setUserData] = useState(null);
    const getActiveUser() => {
        setUserData(getData(userId)); // More realistically this would be async
    }
    useEffect(() => {
        getActiveUser();
    }, []);

    //...
}

Even though that useEffect depends on the userId prop, it only runs once, and so the userId and the userData will be out of sync if the userId changes. Maybe this is your intent, but for the purposes of the linter rule it looks like a bug.

In the case where getActiveUser is defined outside the component, it can't possibly (or at least not reasonably) depend on the state or props of the component, so there's no issue for the linter rule.


So how to fix this? Well, if getActiveUser doesn't need to be defined inside the component, just move it out of the component.

Alternatively, if you're sure you only want this behavior to run when the component mounts, and that won't cause issue due to props changing (it's best to assume all props can change), then you can just disable the linter rule.

But assuming neither of those is the case...

A non-solution (too many effects)

As you've noted, adding getActiveUser to the linter array makes the issue go away:

const MyComponent = ({userId}) => {
    const getActiveUser() => {
       //...
    }
    useEffect(() => {
        getActiveUser();

    }, [getActiveUser]) // No error... but probably not right.
    return <></>;
}

But getActiveUser is a different function instance every render, so as far as useEffect is concerned, the deps array changes every render, which will cause an API call after every render, which is almost certainly not what you want.

A fragile solution

Since the root issue in my example is that the userId prop might change, you could also fix this issue by adding userId to the useEffect dependencies:

const MyComponent = ({userId}) => {
    const getActiveUser() => {
       // Uses userId
    }
    useEffect(() => {
        getActiveUser();

    // Linter is still unhappy, so:
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [userId])
    return <></>;
}

This behaves correctly - no extra API calls or stale data - but the linter is still unhappy: it isn't clever enough to know that we've fixed the dependency on getActiveUser by depending on all the things that getActiveUser depends on.

And this is fragile: if you add a prop or state in the future that getActiveUser depends on, and forget to add it here, you're going to have stale data issues.

A better solution

So the recommended solution is:

const MyComponent = ({userId}) => {
    const getActiveUsers = useCallback(() => {
        // uses userId
    }, [userId]);

    useEffect(() => {
        getActiveUser(); 

    }, [getActiveUsers]) // No error
    return <></>;
}

By wrapping getActiveUsers in useCallback, the function instance is only replaced when needed: when userId changes. This means that the useEffect also only runs when needed: when getActiveUsers changes (which is whenever userId changes).

The linter is happy with this solution and if you introduce new dependencies to getActiveUser, you'll only need to change its useCallback deps, not the useEffect.


Dan Abramov's blogpost A Complete Guide to useEffect goes into this in more detail.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论