Why is my react router calling apis twice?

Viewed 2530

I have explored whole web for the solution but nothing worked for me as expected. My Api's are being called twice, resulting in duplicating data in db. I want them to trigger just once.

Find the code below:

   <Route
        path="/apis/wallet_verification"
        render={(routerProps) => {
          //deals with some params
          console.log("I am triggering");

          axios.get(`http://website/apis/save_ride_payments?payment_type=WALLET`).then((saveRides) =>{
            
            console.log("I am inside get");

            axios.post(`http://website/apis/verify_token?token=${usertoken}`).then((response) => {

              if(response.data.status !== "error") {
                console.log("I am inside post");
                routerProps.history.push({
                  pathname: '/paymentsuccess',
                  state: {initRide}
                });
              }
            })
          })
          console.log("I am out");
          return (
            <div>
              Wallet Transaction
            </div>
          )   
        }}
      />

Output of console:

I am triggering
I am out
I am inside get
I am triggering
I am out
I am inside post
I am inside get
I am inside post

Network tab is showing this Api sequence:

save_ride_payments
verify_token
save_ride_payments
verify_token

my index.js

ReactDOM.render(
<Provider store={store}>
    <App />
</Provider>,
document.getElementById('root'));

Definitely, there might be some logical mistake. Would love if someone were to point that out.

2 Answers

You are doing your Api call at every render. You need a useEffect hook to only do it once.

An arbitrary function, in this case the anonymous function you send to component is not a React Functional Component. To make it a component, you need 1. a named function and 2. this named function to have a Capital as first letter doc

To make your code work, you need to define the component you want to render, only then can you call useEffect.

Here is a quick example, with some missing props/variable as I am not sure how and where your are getting them:

Your new component:

const Wallet = ({ history }) => {
  useEffect(() => {
    axios
      .get(`http://website/apis/save_ride_payments?payment_type=WALLET`)
      .then((saveRides) => {
        return axios.post(`http://website/apis/verify_token?token=${usertoken}`);
      }).then((response) => {
        if (response.data.status !== 'error') {
          history.push({
            pathname: '/paymentsuccess',
            state: {
              initRide
            },
          });
       }
    });
  }, []);

  return <div>Wallet Transaction</div>;
};

Your route:

<Route
    path="/apis/wallet_verification"
    component={(routerProps) => <Wallet history={routerProps.history}/>}
/>

If you are using react-router, you can use the useHistory hook to get the history object instead of sending it via props.

Issue

You are making the data calls, GET and POST requests, right in the function body instead of in an useEffect, so they are executed anytime react renders the component for DOM diffing purposes.

Fix

Use an useEffect hook to run the requests once when the route matches and the anonymous component is rendered. Use an empty dependency array so the effect is called only once when mounted.

const WalletVerification = ({ history }) => {
  React.useEffect(() => {
    //deals with some params
    console.log("I am triggering");

    axios
      .get(`http://website/apis/save_ride_payments?payment_type=WALLET`)
      .then((saveRides) => {
        console.log("I am inside get");

        axios
          .post(`http://website/apis/verify_token?token=${usertoken}`)
          .then((response) => {
            if (response.data.status !== "error") {
              console.log("I am inside post");
              routerProps.history.push({
                pathname: "/paymentsuccess",
                state: { initRide }
              });
            }
          });
      });
    console.log("I am out");
  }, []);

  return <div>Wallet Transaction</div>;
}

Since App is connected to a redux store updates to the store likely trigger a rerender. This has the effect of also rerendering route components if they use an inline function.

When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop

Either use the Route's component prop

<Route
  path="/apis/wallet_verification"
  component={WalletVerification}
/>

or the render prop and proxy the route props

<Route
  path="/apis/wallet_verification"
  render={renderProps => <WalletVerification {...renderProps} />}
/>

Additionally, you can use React's memo Higher Order Component to further hint to React that the component shouldn't rerender.

If your component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.

const WalletVerification = React.memo(({ history }) => { ... });
Related