-1

I have this Component:

/* eslint-disable */
import React, { useEffect, useState } from "react";
import { usePostValidateMutation } from "./features/apiSlice";

export const App = () => {
  const [postValidate] = usePostValidateMutation();

  const [token, setToken] = useState(undefined);

  useEffect(() => {
    const queryParameters = new URLSearchParams(window.location.search);
    const tokenParam = queryParameters.get("token");
    console.log(tokenParam);
    setToken(tokenParam);
    validate();
  }, []);

  const validate = async () => {
    await postValidate(token)
      .unwrap()
      .then((data) => console.log(data))
      .catch((error) => console.log(error));
  };

  return (
    <div className="container-fluid">
      <Stats />
    </div>
  );
};

export default App;

App is called with a token in querystring: http://localhost?token=a1b2c3.

I need to get that (short live) token and make an API call (Via RTK Query mutation) to get a long lived token.

My issue is that useEffect run twice and not once (I see two API call and twice printed token in console).

sineverba
  • 5,059
  • 7
  • 39
  • 84
  • 1
    Could [Strict Mode](https://react.dev/reference/react/StrictMode#:~:text=Strict%20Mode%20enables%20the%20following%20development%2Donly%20behaviors%3A) be enabled? It causes multiple mounts/unmounts – Harrison Aug 04 '23 at 09:27
  • Bingo! Forgot Strict Mode. Thank you – sineverba Aug 04 '23 at 09:35
  • One of the reasons for strict mode is to detect bugs, I would say that using React state to store your token state is the bug here. – Keith Aug 04 '23 at 09:45
  • @Keith can you explain better? – sineverba Aug 04 '23 at 09:48
  • 1
    @Harrison if you write an answer, I'll accept it. – sineverba Aug 04 '23 at 09:50
  • I'll try, I'll see if I can make more sense in an answer, removing Strict Mode in development mode is not really solving the issue. Also in production mode this is removed for you anyway. – Keith Aug 04 '23 at 09:55

4 Answers4

2

Removing Strict Mode to solve a double render is almost certainly overkill, and its there for a reason, in theory if your components are written properly a double render should never cause you issues, even if external API etc are required. Also worth noting the double render is only performed in development mode, not in production.

One rule I would give for React State, use it for Visual reasons only. At the end of the day, that's what its designed for. State changes are there to let React know it needs to do another DOM / Virtual DOM compare, putting other state in there is just pointless. Put it another way, why would something visual been rendered twice cause you bugs / issues, if you think about it that makes no sense.

So how would I solve your issue of only requesting the token once. Simple just move the state outside of React, create a simple state manager to hold the current token etc.

eg. make a function like below outside of the App component->

async function getToken(urlToken) {
  //this could check to see we are already getting this token
  //if so just return the current token Promise.
}

You could then use the above function inside your component, and because the above function will be handling state, there would be no need for another request. So although you might still be calling this twice inside your React component, its not going to matter.

Keith
  • 22,005
  • 2
  • 27
  • 44
1

One solution is to use a "query state" to wrap the call into a condition only when the query hasn't been sent already.

Something like

import React, { useEffect, useState } from "react";
import { usePostValidateMutation } from "./features/apiSlice";

export const App = () => {
  const [postValidate] = usePostValidateMutation();

  const [token, setToken] = useState(undefined);
  const [hasRequestBeenSent, setHasRequestBeenSent] = useState(false);

  useEffect(() => {
    const queryParameters = new URLSearchParams(window.location.search);
    const tokenParam = queryParameters.get("token");
    console.log(tokenParam);
    setToken(tokenParam);
    if (!hasRequestBeenSent)
      validate();
  }, [hasRequestBeenSent]);

  const validate = async () => {
    await postValidate(token)
      .unwrap()
      .then((data) => console.log(data))
      .then(() => {
        setHasRequestBeenSent(true)
      })
      .catch((error) => console.log(error));
  };

  return (
    <div className="container-fluid">
      <Stats />
    </div>
  );
};

export default App;
3Dos
  • 3,210
  • 3
  • 24
  • 37
1

try to remove <React.StrictMode> from index.js file,

ReactDOM.render(
  < React.StrictMode>
    <App />
  </React.StrictMode>,
  rootElement
);
Sanu Kumar
  • 86
  • 7
1

It's likely caused by StrictMode having been enabled.

This is on by default in newer version of React (React 18), but can be disabled for some development purposes.

const root = createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

Strict Mode enables the following development-only behaviors:

Additionally, editing and saving your code can also cause repeated re-renders due to the React hot-reload feature that is typically enabled from apps created with React-Scripts

Harrison
  • 1,654
  • 6
  • 11
  • 19
  • Thank you to pointing in right way. For the moment, I removed the strict and solved my issue. But, I will try to refactor my code to reenable it. – sineverba Aug 04 '23 at 10:49