1

In my React application I have a redux store that contains a user with the following model:

{
  id: "",
  name: "",
  email: "",
  isAdmin: false,
  client: { id: undefined },
  token: "",
  tokenExpiry: null
};

On a simple page, I am querying a database for other data associated to that user. To do so I am taking advantage of the useEffect hook:

  useEffect(() => {
    // Check JWT auth token and refresh if near expiry
    auth.refresh(
      { token: user.token, tokenExpiry: user.tokenExpiry },
      // Request user "field" data
      loadFields(user.client.id, user.token)
    );
  }, [user.client.id, user.token]);

Upon building, React presents the React Hook useEffect has missing dependencies: 'loadFields' and 'user.tokenExpiry'. Either include them or remove the dependency array warning.

If I understand useEffect correctly, the second parameter is the values which Reach will "watch", upon any change the component will be re-rendered (similar to how componentDidUpdate works)...

Whilst there are other values used within my useEffect call, I only want to reprocess this function when the user.client.id or user.token is changed. This is to avoid unnecessary re-renders when other parts of the user model is changed, so I don't want to add user.tokenExpiry as a dependency.

As the loadFields function is a function also used outside of from the useEffect logic, I cannot place it within useEffect so I've tried adding it as a dependency, but this causes a re-render loop as it has a dispatch call within it:

const loadFields = async (clientId, token) => {
  setIsLoading(true);
  try {
    await dispatch(fetchFields(clientId, token));
    setIsLoading(false);
  } catch (error) {
    setIsLoading(false);
    dispatch(resetFields());
  }
};

So what is the correct way to impelment useEffect in this scenario?

7
  • It's worth noting that this appears to be operating as I want it at present, but I would like to know the correct way to approach this which would not cause the warnings to appear. Commented Jan 20, 2020 at 14:49
  • 1
    what about loadFileds function - try to wrap it with React.useCallback hook like this React.useCallback(async (clientId, token) => { // ... }, []); and add it to dependency list. According user.tokenExpiry I can only think of a dirty hack: 1) create state with user.tokenExpiry value, 2) create useEffect that updates tokenExpiry state on some condition, 3) add this state as a dependency to your existing useEffect. But what is worse? This trick or a warning?:) Commented Jan 20, 2020 at 15:11
  • Sorry, I don't see your minimal reproducible example? The code shown is not enough to understand what you're actually doing. Commented Jan 20, 2020 at 15:18
  • Thanks @Mike'Pomax'Kamermans, as this is a React application, utilising Redux for state management, the only way for me to achieve a working reproducible example is to create codesandbox mockup of my application. As this is quite time-consuming can I ask if I can provide any more details before needing to do this? Ultimately I'm looking to clarification on how to use useEffect as a dependency with an external script which utilises useEffect. I am also interested to know if it is possible to exclude dependencies without causing the warning (such as I've highlighted with user.tokenExpiry). Commented Jan 20, 2020 at 16:00
  • If need be I'll create the full mock-up of the app, I'd just thought the scripts used and documented in the question would point to where I am going wrong. I'm happy to provide any additional information I can. Thank you all in advance for your help. Commented Jan 20, 2020 at 16:01

2 Answers 2

1

To include loadFileds function in the dependency list try to wrap it with React.useCallback hook like this

React.useCallback(async (clientId, token) => { ... }, []);

and add it to dependency list. useCallback "remembers" the function and doesn't create it on every render, thus loadFields stays the same across renders and it doesn't trigger effect, see Documentation.

What about tokenExpiry - well, sometimes such situations happen, I usually leave a comment, that describes why some variable is not in a dependency list.

Sign up to request clarification or add additional context in comments.

Comments

1

Either add all the dependencies to the array

useEffect(() => {
  // Check JWT auth token and refresh if near expiry
  auth.refresh(
    { token: user.token, tokenExpiry: user.tokenExpiry },
    // Request user "field" data
    loadFields(user.client.id, user.token)
  );
}, [user.client.id, user.token, user.tokenExpiry]);

Or it is likely just being flagged by your react-hooks linter, you can disable it for that line.

useEffect(() => {
  // Check JWT auth token and refresh if near expiry
  auth.refresh(
    { token: user.token, tokenExpiry: user.tokenExpiry },
    // Request user "field" data
    loadFields(user.client.id, user.token)
  );
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user.client.id, user.token]);

I usually also leave a comment above the override as for the reason for it. Use this with caution as when you make any changes to the effect hook it can change what you want the hook to "watch" changes of.

Suggestion: Add tokenExpry to the array and conditionally call your endpoint if you can tell it is close to needing refreshing.

useEffect(() => {
  // Check JWT auth token and refresh if near expire
  if ( /* tokenExpiry almost expired */ ) {
    auth.refresh(
      { token: user.token, tokenExpiry: user.tokenExpiry },
      // Request user "field" data
      loadFields(user.client.id, user.token)
    );
  }
}, [user.client.id, user.token, user.tokenExpiry]);

6 Comments

I can add the user.tokenExpiriy to the dependency list without error... it will just cause unnecessary calls to my API as the loadFields function would be called when the component re-renders when the token is updated. So that's not ideal.
The main issue, is I am unable to add loadFields to the dependency list as it causes a re-render loop; the component mounts, useEffect calls loadFiels (which does an API call and runs useDispatch to update my redux store which in-tern causes the original component to re-render and so an infinite loop would be created.
Ah, do you want this to run only on mount then? Otherwise you need to trigger on something other than the values this effect untimely updates or do some conditional tests within the effect, or possibly even both, to not create the infinite loop.
No, I'm looking to run on two situations; 1.) On mount 2.) If the user.client value is changed in the redux store (as a user can change their dataset). I've seemed to solve the loop issue thanks to a comment by @ValeryStatinov making me aware of the useCallback hook.
@Sheixt Well, my real suggestion was to add the lint disable comment since you say the effect works as you expect it to and you just have this lingering warning. ValeryStatinov's suggestion may work, but it's just more moving parts.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.