3

Background

I have recently upgraded a fairly sizeable React app to React 18 and for the most part it has been great. One of the key changes is the new double mount in development causing useEffect hooks to all run twice, this is clearly documented in their docs.

I have read their new effect documentation https://beta.reactjs.org/learn/lifecycle-of-reactive-effects and although it is quite detailed there is a use case I believe I have found which is not very well covered.

The issue

Essentially the issue I have run into is I am implementing OAuth integration with a third-party product.
The flow:
-> User clicks create integration
-> Redirect to product login
-> Gets redirected back to our app with authorisation code
-> We hit our API to finalise the integration (HTTP POST request)

The problem comes now that the useEffect hook runs twice it means that we would hit this last POST request twice, first one would succeed and the second would fail because the integration is already setup.

This is not potentially a major issue but the user would see an error message even though the request worked and just feels like a bad pattern.

Considered solutions

Refactoring to use a button

I could potentially get the user to click a button on the redirect URL after they have logged into the third-party product. This would work and seems to be what the React guides recommend (Although different use case they suggested - https://beta.reactjs.org/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers).

The problem with this is that the user has already clicked a button to create the integration so it feels like a worse user experience.

Ignore the duplicate API call

This issue is only a problem in development however it is still a bit annoying and feels like an issue I want to explore further

Code setup

I have simplified the code for this example but hopefully this gives a rough idea of how the intended code is meant to function.

const IntegrationRedirect: React.FC = () => {
  const navigate = useNavigate();
  const organisationIntegrationsService = useOrganisationIntegrationsService();

  // Make call on the mount of this component
  useEffect(() => {
    // Call the method
    handleCreateIntegration();
  }, []);

  const handleCreateIntegration = async (): Promise<void> => {
    // Setup request
    const request: ICreateIntegration = {
      authorisationCode: ''
    };

    try {
      // Make service call
      const setupIntegrationResponse = await organisationIntegrationsService.createIntegration(request);

      // Handle error
      if (setupIntegrationResponse.data.errors) {
        throw 'Failed to setup integrations';
      }

      // Navigate away on success
      routes.organisation.integrations.navigate(navigate);
    }
    catch (error) {
      // Handle error
    }
  };

  return ();
};

What I am after

I am after suggestions based on the React 18 changes that would handle this situation, I feel that although this is a little specific/niche it is still a viable use case. It would be good to have a clean way to handle this as OAuth integration is quite a common flow for integration between products.

9
  • It could be due to StrictMode. Please check this question stackoverflow.com/questions/72489140/… Commented Sep 15, 2022 at 14:14
  • Yeah so it is I understand why it is happening, what I am looking for is a pattern of handling it that does not require removing strict mode. I want to create a pattern for handling this without just avoiding the safety of strictmode Commented Sep 15, 2022 at 14:15
  • Does this answer your question? React 18, useEffect is getting called two times on mount Commented Sep 17, 2022 at 11:47
  • I had a look, the answer mostly describes the issue. They did talk about canceling API calls however that would mostly only work for GET requests you could end up with some weird situations canceling a post request half way through from the frontend only. I can't imagine how you would know if it worked or didn't before you cancel it. Commented Sep 18, 2022 at 5:52
  • Did you give it a try? Do you have a way to stop the request in your case? If so I would give it a shot, since the mount/unmount is very quick, I think it would work. Commented Sep 18, 2022 at 7:40

1 Answer 1

2

You can use the useRef() together with useEffect() for a workaround

const effectRan = useRef(false)
            
useEffect(() => {
  if (effectRan.current === false) {
  // do the async data fetch here
  handleCreateIntegration();
  }

  //cleanup function
  return () => {
  effectRan.current = true  // this will be set to true on the initial unmount
  }

}, []);

This is a workaround suggested by Dave Gray on his youtube channel https://www.youtube.com/watch?v=81faZzp18NM

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

2 Comments

So I did get this to work, but I also do not love having a ref every time you in theory need to do this. It does seem like there aren't many alternatives.
In the docs, it is specified that this feature is implemented in React 18 to help developers write proper cleanup functions. beta.reactjs.org/learn/synchronizing-with-effects

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.