Vuk
Vuk

Reputation: 873

Handling OAuth with React 18 useEffect hook running twice

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.

Upvotes: 2

Views: 1131

Answers (1)

Ozgur Sar
Ozgur Sar

Reputation: 2204

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

Upvotes: 2

Related Questions