Fábio Santos
Fábio Santos

Reputation: 553

React Router only updating after second click

So, basically I have multiple nested routes like /:platform/:account/products/:tab/:productId/summary. Everything works fine down to my actual single product component, but react-router stops working properly in my single product tabs. I have a products view, with tabs (routes) , and when clicking an item, there's a popup that is the single product view, which, has 3 more tabs inside.

The child routes of my product are updating, but only on the next render cycle.

  1. I'm In Summary and I click Prices: nothing happens (route changes to /:platform/:account/products/:tab/:productId/prices)
  2. I click Summary: Rendered component changes to Prices (route changes to /:platform/:account/products/:tab/:productId/summary)
  3. I click Translations: Rendered component changes to Summary (route changes to /:platform/:account/products/:tab/:productId/translations)
  4. I click Translations again: Rendered component changes to Translations (no route change at all)

I've been fighting with this for over 4 hours, I checked if I had any PureComponent in my tree, I used withRouter pretty much everywhere, I also checked if my product component was not updating (maybe my main view component was blocking the update), but everything seems fine, new props are correct. I also tried using { pure: false } in connect() but nothing helped. I also removed this localize HOC just in case, but that didn't solve it.

Version

4.2.0

Sample code

Here is a stripped down render method from my component:

class ProductView extends Component {
  componentWillReceiveProps() {
    console.log("UPDATED PRODUCT TAB")
  }

  render() {
    const { match, history, translate, lang, loading, data } = this.props
    console.log(match)
    return (
      <Modal>
          <TabList>
        <TabItem>
          <NavLink
            activeClassName="active"
            to={{
              pathname: `/${match.params.platform}/${match.params.account}/products/${match.params.tab}/${match.params.productId}/summary`,
              search: location.search,
            }}
          >
            {translate("Summary")}
          </NavLink>
        </TabItem>
        <TabItem>
          <NavLink
            activeClassName="active"
            to={{
              pathname: `/${match.params.platform}/${match.params.account}/products/${match.params.tab}/${match.params.productId}/prices`,
              search: location.search,
            }}
          >
            {translate("Prices")}
          </NavLink>
        </TabItem>
        <TabItem>
          <NavLink
            activeClassName="active"
            to={{
              pathname: `/${match.params.platform}/${match.params.account}/products/${match.params.tab}/${match.params.productId}/translations`,
              search: location.search,
            }}
          >
            {translate("Translations")}
          </NavLink>
        </TabItem>
      </TabList>
          <Switch>
            <Route path="/:platform/:account/products/:tab/:id/summary" component={Summary} />
            <Route path="/:platform/:account/products/:tab/:id/prices" component={Prices} />
            <Route path="/:platform/:account/products/:tab/:id/translations" component={Translations} />
          </Switch>
      </Modal>
    )
  }
}

const mapStateToProps = state => (...)

const mapDispatchToProps = dispatch => (...)

export default withRouter(connect(
  mapStateToProps,
  mapDispatchToProps,
)(localize("translate")(ProductView)))

The Route to get to the product Component looks like: <Route exact path="/:platform/:account/products/:tab/:productId/:productTab" component={ProductView} /> Adding or removing :productTab makes no difference. Inside the component with this Product Route I have other NavLinks for tabs and Router is working correctly.

What's weird here is that components update all the way down to my Product Route, with correct match props, but those child Routes, including NavLinks only update after another click.

screenshot from 2017-09-20 18-44-22

Upvotes: 4

Views: 2332

Answers (3)

Bruno Dias
Bruno Dias

Reputation: 36

If you are using nested Route or NavLink (a wrapper of Route), you will need to use Switch (as convenience) to wrap all the modal content (or at least the part which uses Route/NavLink).

const location = (this. - if you component is not a function)props.location;
<Switch location={location}>
  <NavLink ... />
  <Route ... />
</Switch>

This will make sure that the location will be updated.

react-modal does not interfere on the update of user components. The location that the Routes receives comes from getChildContext(), which only updates after the react component lifecycle. By giving to Switch the location on render(), the context will be the new one and it will be given to the children Routes. It will work as expected.

Upvotes: 2

F&#225;bio Santos
F&#225;bio Santos

Reputation: 553

So, I managed to track it down (Not sure why I didn't try this 3 hours before...) and figured out it's due to react-modal.

There's someone else with the same issue, here's the ticket: https://github.com/reactjs/react-modal/issues/499

Upvotes: 0

MayThrow
MayThrow

Reputation: 2201

React-redux implements pure render in its connect function. So that might be blocking your updates

Try using withRouter early like this. It will stop connect from blocking updates coming from router.

export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(localize("translate")(withRouter(ProductView)))

Upvotes: 0

Related Questions