ReactJs: PrivateRoute component gets called twice

Viewed 399

I have this privateRoute component that I use to manage authentication:

const PrivateRoute = ({ component: Component, auth, ...rest }) => {
  // This gets logged twice when I navigate in the app
  // Using history.push("/url")
  // And gets logged once when I type the url and hit enter on the browser
  console.log(
    "===============INSIDE PrivateRoute Component========================"
  );
  return (
    <Route
      {...rest}
      render={(props) =>
        auth.isAuthenticated === true ? (
          <Component {...props} />
        ) : (
          <Redirect to="/login" />
        )
      }
    />
  );
};

The strange thing is that this component gets logged twice when I navigate in the app. For example, when I hit a button that triggers this code:

this.props.history.push("/edit-page-after-login");

And I have this in App.js:

    <PrivateRoute
      path="/edit-page-after-login"
      component={EditProfileAfterLogin}
    />

And I have a component that gets rendered in that route:

export default class EditProfileInSettings extends Component {
  componentDidMount() {
    console.log(
      " ~ file: EditProfileInSettings.js ~ line 5 ~ EditProfileInSettings ~ componentDidMount ~ componentDidMount"
    );
  }
  render() {
    return <div>Test</div>;
  }
}

So when I navigate to that component using history.push, this gets logged:

===============INSIDE PrivateRoute Component========================
~ file: EditProfileInSettings.js ~ line 5 ~ EditProfileInSettings ~ componentDidMount ~ componentDidMount
===============INSIDE PrivateRoute Component========================

For some strange reason, PrivateRoute component gets called TWICE which is causing me some issues in the logic I am trying to implement.

But, when I write the url in the browser and enter, it behaves correctly and it only gets called ONCE:

===============INSIDE PrivateRoute Component========================
~ file: EditProfileInSettings.js ~ line 5 ~ EditProfileInSettings ~ componentDidMount ~ componentDidMount

Any idea what's going on here?


EDIT 1: I have noticed this error only occurs when I do an API call to the backend inside the component:

class PrivateRouteTestComponent extends Component {
  componentDidMount() {
    console.log("PrivateRouteTestComponent.componentDidMount is called!");
    // If I comment this out, the problem will not occur.
    // It only occurs with this line
    // It does an API call to the backend to get user profile
    this.props.getAuthenticatedUserProfile();
  }
  render() {
    return (
      <div>
        <button
          onClick={() => {
            this.props.history.push("/videos-page");
          }}
        >
          Home
        </button>
        <h6>Private route test component</h6>
      </div>
    );
  }
}

EDIT 2: I finally found why this error occurs. Calling a function that dispatches something to the store will update the PrivateRoute so it will get called again:

class PrivateRouteTestComponent extends Component {
  componentDidMount() {
    console.log("PrivateRouteTestComponent.componentDidMount is called!");
    // This doesn't cause the problem
    testBackendCall();
    // This causes the problem
    // Because it dispatches an action to the store
    // So PrivateRoute gets updated
    this.props.testBackendCallThatDispatchesSomethingToTheStore();
  }
  render() {
    return (
      <div>
        <button
          onClick={() => {
            this.props.history.push("/videos-page");
          }}
        >
          Home
        </button>
        <h6>Private route test component</h6>
      </div>
    );
  }
}
2 Answers

You're mixing functional components and React class-based components, and expect that the logging in PrivateRoute and the one in EditProfileInSettings to be done at the same moment in the rendering cycle - but it's not.

In EditProfileInSettings, you log on the mounting phase (componentDidMount), which happens once in a component's rendering (if not unmounted).

In PrivateRoute, you log on the rendering phase (think the equivalent of render on a class Component), which happens every time React needs to update your component because of its props being changed.

If you want the two logging to be equivalent, either place your logging in a useEffect() on your PrivateRoute, or place your logging at the render() on your EditProfileInSettings.


Then, to know why your functional component is rendered two times, log all your props and spot the differences between two cycles.

This is what solved my problem. Using React hooks to execute code in functional component only when it's mounted.

const PrivateRoute = ({ component: Component, auth, ...rest }) => {
  React.useEffect(() => {
    // EXECUTE THE CODE ONLY ONCE WHEN COMPONENT IS MOUNTED
  }, []);
  return (
    <Route
      {...rest}
      render={(props) =>
        auth.isAuthenticated === true ? (
          <Component {...props} />
        ) : (
          <Redirect to="/login" />
        )
      }
    />
  );
};
Related