Connected-react-router: Double-rendering

Created on 1 Dec 2018  路  20Comments  路  Source: supasate/connected-react-router

Please run the following case:

import React from 'react';
import { createMemoryHistory } from 'history';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { Button, Text, View } from 'react-native';
import { Route, Switch, Router } from 'react-router-native';
import { connectRouter, ConnectedRouter } from 'connected-react-router';

const normalHistory = createMemoryHistory();
const reduxHistory = createMemoryHistory();
const rootReducer = combineReducers({
  router: connectRouter(reduxHistory),
});
const store = createStore(rootReducer, undefined);

function TestCase({ name, history }) {
  return (
    <Switch>
      <Route
        path="/"
        exact
        render={() => {
          console.log('render foo', { name });
          return <Button title="foo" onPress={() => history.push('bar')} />;
        }}
      />
      <Route
        path="/bar"
        render={() => {
          console.log('render bar', { name });
          const handlePress = () => {
            setTimeout(() => {
              history.push('/baz');
            }, 1000);
          };
          return <Button title="bar" onPress={handlePress} />;
        }}
      />
      <Route
        path="/baz"
        render={() => {
          console.log('render baz', { name });
          return <Text>baz</Text>;
        }}
      />
    </Switch>
  );
}

export default function Entrypoint() {
  return (
    <React.Fragment>
      <View style={{ padding: 20 }}>
        <Router history={normalHistory}>
          <TestCase name="normal" history={normalHistory} />
        </Router>
      </View>
      <View style={{ padding: 20 }}>
        <Provider store={store}>
          <ConnectedRouter history={reduxHistory}>
            <TestCase name="connected" history={reduxHistory} />
          </ConnectedRouter>
        </Provider>
      </View>
    </React.Fragment>
  );
}

Click on foo -> bar -> baz in both of the routers:

  • "normal" router will output: render baz only once;
  • "connected" router will output render baz twice.

Why?

    "connected-react-router": "5.0.1",
    "react-router-native": "^4.3.0",

Most helpful comment

Getting this on 6.6.1.

Edit: found the culprit: I had wrapped everything in React.StrictMode which apparently causes double rendering.

All 20 comments

Our history.push() is called in an asynchronous redux-action, so we need it to work as expected in this case. We discovered it by calling dispatch(push()) in one of the actions.

I'm also dealing with this same issue

Also receiving this issue.

I found that changing any routes from using component prop to render prop did the trick:

Works

        <Switch>
          <Route path={'login'} render={props => <LoginContainer {...props} />} />
        </Switch>

Renders 2x

        <Switch>
          <Route path={'login'} component={props => <LoginContainer {...props} />} />
        </Switch>

I'm also having this issue. Note that I've followed the connected-react-router documentation in the README to setup the Redux store properly. I'm also on [email protected] and [email protected].

I've created a simple use case to test:

// AppContainer.js

const AppContainer = () => (
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <App />
    </ConnectedRouter>
  </Provider>
)

On initial application load the App component will render two times:

// App.js

const App = () => {
  console.log('App rendering') /* logs twice */
  return (
    <div>
      <Nav />
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
      </Switch>
    </div>
  )
}

This becomes a major issue depending on the complexity of the application. For instance, Nav will also render two times on initial load:

// Nav.js

const Nav = () => {
  console.log('Nav is rendering') /* logs twice */
  return (
    <nav>
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
    </nav>
  )
}

Depending on the route that loads, that component will also render two times, and so on and so on.

The takeaway here is that ConnectedRouter is rendering twice, as anything outside of it is not affected:

// AppContainer.js

const AppContainer = () => (
  <Provider store={store}>
    <Test /> {/* renders once */}
    <ConnectedRouter history={history}> {/* renders twice */}
      <App />
    </ConnectedRouter>
  </Provider>
)
// Test.js

const Test = () => {
  console.log('Test rendering ') /* logs once */
  return <div />
}

EDIT: I've attempted to try @gregnb solution of changing the Route prop from component to render but that has no affect for me. Components are still rendering twice on intial load.

This is issue is still not resolved, still getting this on 6.5.2

Getting this on 6.6.1.

Edit: found the culprit: I had wrapped everything in React.StrictMode which apparently causes double rendering.

For me the use of render instead of component worked when i remove lazy load functionality.

// This causes double re-render
<Route path="/institution" component={WaitingComponent(() => <InstitutionContainer hideHeader={state => hideHeader(state)} />)} />

// This causes double re-render
<Route path="/institution" render={() => <InstitutionContainer hideHeader={state => hideHeader(state)} />} />

// This doesn't cause double re-render
<Route path="/institution" render={() => <InstitutionContainer hideHeader={state => hideHeader(state)} />} />

I am on 6.8.0 and the issue still happens. However, as @AlexReff pointed out this is caused by React.StrictMode component.

I am on 6.8.0 and the issue still happens. However, as @AlexReff pointed out this is caused by React.StrictMode component.

thanks bro! it worked for me

I am on 6.8.0 and the issue still happens. However, as @AlexReff pointed out this is caused by React.StrictMode component.

It worked form me as well, Thank you!

Is there any way to make connected-react-router correctly working in conjunction with React.StrictMode?

thanks

I changed index.js file from

ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );

To

ReactDOM.render( <App />, document.getElementById('root') );

After doing this wrapper "React.StrictMode" removed, Solved this issues well.

I changed index.js file from

ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );

To

ReactDOM.render( <App />, document.getElementById('root') );

After doing this wrapper "React.StrictMode" removed, Solved this issues well.

Worked for me. Thanks!
Didn't know this:
https://stackoverflow.com/questions/61254372/my-react-component-is-rendering-twice-because-of-strict-mode

Thanks to this discussion, I could fix my problem with double renderings due to strict mode, too. I use version 5.2.0.

Should definitely be in the README ...

@Cethy completely agree with you !

Hey everyone! Thanks for this awesome library!

I'm using 6.8.0 and still seeing the issue without React.StrictMode.
I'm using it inside Chrome Extension. Redux Store is wrapped with webext-redux, so it's located in the extension's background page and the React app is running in a separate Chrome extension's popup window.

Here's a fragment from package.json:

"connected-react-router": "^6.8.0",
"history": "^4.10.1",
"react": "^16.13.1",
"react-browser-extension-scripts": "3.3.1",
"react-dom": "^16.13.1",
"react-redux": "7.2.0",
"react-redux-firebase": "3.1.0",
"react-router-dom": "5.1.2",
"redux-saga": "1.1.3",
"redux-saga-firebase": "^0.15.0",
"webext-redux": "2.1.6"

Here's my main render:

  .ready()                            // webext-redux wraps regular redux store, so here it is a promise
  .then(() => store.getState())
  .then(selectLocation)               // getting the route to start the Chrome Extension from the latest screen
  .then((startLocation) => {
    ReactDOM.render(
      <Provider store={store}>
        <ErrorBoundary>               // this is a simple class component with `componentDidCatch`.
          <ReactReduxFirebaseProvider // this guy comes from `react-redux-firebase` and works as Firebase instance connected to the redux store
            firebase={firebase}
            config={{
              userProfile: 'users',
              useFirestoreForProfile: true,
              enableClaims: true,
            }}
            dispatch={store.dispatch}
          >
            <Router history={history}>
              <Routes startLocation={startLocation} />
            </Router>
          </ReactReduxFirebaseProvider>
        </ErrorBoundary>
      </Provider>,
      document.getElementById('root'),
    );
  })
  .catch((e) => console.error(e));

I already spent a few days and nights trying to solve this issue but no luck. Fortunately, I'm here after days of googling.

I tried the following magic after I found this thread:

  • removed ErrorBoundary
  • moved ReactReduxFirebaseProvider inside Router

And there were a looot of magic tricks and workarounds before I landed here.

This issue causes initializing the state in useStates twice in each component and all the app is not working as expected. And the app has already grown, so returning back to the regular router is not an option.

Any help is appreciated! Thanks!

React.StrictMode is probably going to be useful going forward. Maybe this would solve the issue and also allow you to watch for mistakes in functional components.

They say in the docs you can put it anywhere https://reactjs.org/docs/strict-mode.html

<ConnectedRouter history={history}>
  <React.StrictMode>
    <Switch>
    </Switch>
  </React.StrictMode>
</ConnectedRouter>
Was this page helpful?
0 / 5 - 0 ratings

Related issues

abeledovictor picture abeledovictor  路  3Comments

jhwheeler picture jhwheeler  路  3Comments

Guymestef picture Guymestef  路  5Comments

jjdp picture jjdp  路  4Comments

pachuka picture pachuka  路  4Comments