craigmiller160
craigmiller160

Reputation: 6283

React Router not changing displayed component

I'm working on a React app using, among other things, react-router. I'm having a problem where I'm changing the route, the change is reflected in the URL, but the mounted component doesn't change. Here is the component, it's one of my main container components:

class AppContent extends Component {

    state = {
        isStarted: false
    };

    componentDidMount = async () => {
        try {
            await this.props.checkIsScanning();
            this.setState({ isStarted: true });
        }
        catch (ex) {
            this.props.showErrorAlert(ex.message);
        }
    };

    componentWillUpdate(nextProps) {
        if (nextProps.history.location.pathname !== '/' && !nextProps.isScanning) {
            this.props.history.push('/');
        }
    }

    render() {
        return (
            <div>
                <VideoNavbar />
                {
                    this.state.isStarted &&
                    <Container>
                        <Row>
                            <Col xs={{ size: 8, offset: 2 }}>
                                <Alert />
                            </Col>
                        </Row>
                        <Switch>
                            <Route
                                path="/scanning"
                                exact
                                render={ (props) => (
                                    <Scanning
                                        { ...props }
                                        isScanning={ this.props.isScanning }
                                        checkIsScanning={ this.props.checkIsScanning }
                                    />
                                ) }
                            />
                            <Route path="/"
                                   exact
                                   render={ (props) => (
                                       <VideoListLayout
                                           { ...props }
                                           isScanning={ this.props.isScanning }
                                       />
                                   ) }
                            />
                        </Switch>
                    </Container>
                }
            </div>
        );
    }
}

const mapStateToProps = (state) => ({
    isScanning: state.scanning.isScanning
});

const mapDispatchToProps = (dispatch) => bindActionCreators({
    checkIsScanning,
    showErrorAlert
}, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(withRouter(AppContent));

So, let's focus on the important stuff. There is a Redux property, isScanning, that is essential to the behavior here. By default, when I open the app, the route is "/", and the VideoListLayout component displays properly. From there, I click a button to start a scan, which changes the route to "/scanning", and displays the Scanning component. The Scanning component, among other things, calls the server on an interval to check if the scan is done. When it is complete, it sets "isScanning" to false. When AppContent re-renders, if "isScanning" is false, it pushes "/" onto the history to send the user back to the main page.

Almost everything here works. The Scanning component shows up when I start the scan, and it polls the server just fine. When the scan is complete, redux is properly updated so "isScanning" now is false. The componentWillUpdate() function in AppContent works properly, and it successfully pushes "/" onto the history. The URL changes from "/scanning" to "/", so the route is being changed.

However, the Scanning component remains mounted, and the VideoListLayout component is not. I can't figure out why this is happening. I would've thought that once the route was changed, the components would change as well. What can I try next?

Upvotes: 2

Views: 725

Answers (1)

TLadd
TLadd

Reputation: 6894

I'm pretty sure you're running into this issue described in the react-router docs where react-redux's shouldComponentUpdate keeps your component from re-rendering on route change: https://reacttraining.com/react-router/core/guides/redux-integration/blocked-updates. This can definitely be a pain and pretty confusing!

Generally, React Router and Redux work just fine together. Occasionally though, an app can have a component that doesn’t update when the location changes (child routes or active nav links don’t update).This happens if: The component is connected to redux via connect()(Comp). The component is not a “route component”, meaning it is not rendered like so: The problem is that Redux implements shouldComponentUpdate and there’s no indication that anything has changed if it isn’t receiving props from the router. This is straightforward to fix. Find where you connect your component and wrap it in withRouter.

So in your case, you just need to swap the order of connect and withRouter:

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(AppContent));

Upvotes: 1

Related Questions