Versions -
"react-router": "5.0.0",
"react-router-dom": "5.0.0"
In my app container component, I am using withRouter to access location and history props. I use it like -
export default withRouter(connect(mapStateToProps)(AppContainerComponent));
The result is, I get a blank page. No errors, just a blank page.
If I remove the withRouter HOC, it works.
Also, it used to work in v4.0.0-beta.8.
I import the withRouter as -
import { withRouter } from 'react-router';
Not sure what the problem is.
Note - I have gone through this link which talks about how the shouldCOmponentUpdate method does not take into account the context changes (which react-router uses now), and it suggests to wrap the component with 'withRouter' HOC, but it is itself not working for me.
Update -
Following is the route path I am using -
index.js -
<Provider store={store}>
<BrowserRouter>
<AppContainerComponent />
</BrowserRouter>
</Provider>
appcontainer.component (its redux connected) renders following component -
<AppRoutes isAuthenticated={isAuthenticated} />
appRoutes.component.ts - This component renders the 'UnauthenticatedRoute' and 'AuthenticatedRoute' custom HOC like -
<UnauthenticatedRoute
path="/"
exact
component={SignupComponent}
isAuthenticated={isAuthenticated} />
<AuthenticatedRoute
path="/app"
exact
component={AppComponent}
isAuthenticated={isAuthenticated} />
The 'UnauthenticatedRoute' and 'AuthenticatedRoute' HOC will render the passed component if the user has been successfully authenticated, otherwise it will redirect to '/signup' path using react-router's Redirect component.
The AppComponent has bunch of routes defined by Route component as -
<Route exact
path="/path1"
render={}
/>
Another update -
Came across this link which talks exactly about the problem I am facing, but the solution - using withRouter. Maybe I am not importing something from right location? Something similar here
I am using react-router and react-router-dom with preact (8.4.2), preact-cli (v2.2.1) and preact-compact (3.18.4). I am not sure if this is the root cause? Isn't the react-router supported out of the box for preact?
I have set up this link to demonstrate the issue.(issue_example branch)
Try wrapping just AppContainerComponent with your withRouter, like this:
export default connect(mapStateToProps)(withRouter(AppContainerComponent));
I managed to solve the issue by creating custom withRouter hoc which takes in a Component to render and wraps that component inside a Router component. Router component has the props I need (history, location) and I pass them down to the component I am rendering, eliminating the use of RRD's withRouter completely.
import { Route } from 'react-router-dom';
const withRouter = (ConnectedComponent) => {
const witRouterComponent = (props) => (
<Route render={routeProps =>
<ConnectedComponent {...routeProps} {...props} />} />
);
return witRouterComponent;
};
export default withRouter;
To use it in (for ex. AppContainerComponent)
const ConnectedComponent = connect(mapStateToProps)(AppContainerComponent);
export default withRouter(ConnectedComponent);
More on this here.
Related
When I go to one functional component using react-router, it renders twice.
However, when I refresh the page of that component, it only renders once.
For the test, created empty functional component like that:
import React from 'react'
const TestFunctional: React.FC<any> = () => {
console.log('Test===>>>') // console log twice when navigate to this component
return <></>
}
export default TestFunctional
Here is Router in App.tsx
import React from 'react'
import { Route, Switch, useLocation, withRouter } from 'react-router-dom'
import TestFunctional from 'views/Test'
const AnimatedSwitch = withRouter(({ location }) => (
<Switch>
<Route exact path="/" component={StartPage} />
<Route exact path="/test" component={TestFunctional} />
</Switch>
))
const App = () => {
return (
<div className="app">
<Web3ReactManager>
<AnimatedSwitch />
</Web3ReactManager>
</div>
)
}
export default App
I did not use React.StrictMode in index.tsx.
ReactDOM.render(
<ApolloProvider client={client}>
<Provider store={store}>
<ConnectedRouter history={history}>
<Web3ReactProvider getLibrary={getLibrary}>
<Web3ProviderNetwork getLibrary={getLibrary}>
<App />
</Web3ProviderNetwork>
</Web3ReactProvider>
</ConnectedRouter>
</Provider>
</ApolloProvider>,
document.getElementById('root')
)
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers
serviceWorker.unregister()
So it is some weird.
When I refresh this page, console.log('Test===>>>') show only once.
What is a mistake and how to fix the double render problem?
Why is that a problem? You should design/write your components assuming that it could re-render at anytime. React is even working on a new rendering mode where your component might be rendered multiple times before it actually gets "rendered in DOM".
As for why it actually renders twice? Not sure, might just be a quick of ReactDOM. As a side note, the documentation for component does have this warning for Route though:
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 (below).
While that shouldn't apply in this case, still handy to know.
I am trying to test a component which listens to the history being changed by creating a wrapper in Enzyme which places my component within a MemoryRouter; i.e.:
mount(
<MemoryRouter initialEntries={'/path/param1}>
<Switch>
<Route
path="/path"
component={MyComponent}
/>
</Switch>
</MemoryRouter>
)
For the initial path, this works fine, however, I specifically want to test what happens when it starts at say /path/param1 but then the history is changed to /path/param2
The monitoring of the path is being done by wrapping the component's export with withRouter, like this:
export default withRouter(MyComponent)
And then upon construction, I am using history.listen to subscribe to the history changes.
You can use a base <Route> component with a custom history object to do this.
First, install the history package (to devDevpendencies if you are only using it in tests).
In your test file:
// we are using memory history only
import { createMemoryHistory } from 'history';
import { Router, Route} from 'react-router-dom';
Then, setup your component like so: (you can drop the Switch if there is only one route)
const history = createMemoryHistory({
initialEntries: ['/path/param1'],
});
// mount with custom history object
mount(
<Router history={history}>
<Route
path="/path"
component={MyComponent}
/>
</Router>
)
Later in your test, you can check what's the current location of the history after your component does its thing.
// check current path has been changed
expect(history.entries[0].pathname).to.eq('/path/param2');
You can find all the other stuff you can get access to with history here:
https://github.com/ReactTraining/history
When a url param change, I need to update two components, but one of them is outside the route with the param. The routes in App.js are like this:
<BrowserRouter>
<div>
<Route exact path="/" render={ (props) =>
<Home products={this.state.products} }
/>
<Route path="/products/:product" render={ (props) =>
<Product {...props} /> }
/>
<Route path="/" render={ props =>
<ProductHistory {...props}/> }
/>
</div>
</BrowserRouter>
The ProductHistory which is always visible has links pointing to products, like:
<Link to={`/products/${product.product_id}`}> {product.name}</Link>
When following such a link, the Product component is updated using ComponentWillReceiveProps method:
componentWillReceiveProps(nextProps) {
if(nextProps.match.params.product !== this.props.match.params.product){
But how do I update the ProductHistory component at the same time when the product param change? Since it isn't within the /products/:product route, checking this.props.match.params.product in ProductHistory's componentWillReceiveProps results in undefined.
(edit - and withRouter doesn't help, since it already is within a route, but a different one: "/")
In componentWillReceiveProps I could use location.pathname to check that the path begins with "/product", and I could find the param by substr(path.lastIndexOf('/') + 1.
Edit: But I also have to compare the current id param with the next product id param to avoid unnecessary updates. But when clicking the link, the url have already changed when componentWillReceiveProps fires so location.pathname and nextProps.location.pathname always match, so it updates unnecessarily (repeated api calls).
So I would have to find a different solution - rearrange the routing in some way? The idea is that ProductHistory should always be visible though.
You can render the Route simply like this:
<BrowserRouter>
<div>
<Switch>
<Route exact path="/" render={ (props) =>
<Home products={this.state.products} }
/>
<Route path="/products/:product" render={ (props) =>
<Product {...props} /> }
/>
</Switch>
<ProductHistory />
</div>
</BrowserRouter>
And then in the ProductHistory class you use the withRouter HOC
You can get access to the history object's properties and the closest
Route's match via the withRouter higher-order component. withRouter
will pass updated match, location, and history props to the wrapped
component whenever it renders.
example:
class ProductHistory extends Component { ... }
export default withRouter(ProductHistory);
or using decorators
#withRouter
export default class ProductHistory extends Component { ... }
With this you will be able to access match, location and history through props like this:
this.props.match
this.props.location
this.props.history
For anyone stumbling across this, there is a new solution afforded by hooks, in the form of useRouteMatch in react-router-dom.
You can lay your code out like João Cunha's example, where ProductHistory is not wrapped within a Route. If the ProductHistory is anywhere else but inside the Route for products, all the normal routing information will seemingly give the wrong answer (I think this might have been where the problems with withRouter arose in the replies to João's solution), but that's because it's not aware of the product route path spec. A little differently from most MVC routers, React-router-dom won't be calculating the route that matched up front, it will test the path you're on with every Route path spec and generate the specific route matching info for components under that Route.
So, think of it in this way: within the ProductHistory component, you use useRouteMatch to test whether the route matches a path spec from which you can extract the params you require. E.g.
import { useRouteMatch } from 'react-router-dom';
const ProductHistory = () => {
const { params: { product } } = useRouteMatch("/products/:product");
return <ProductList currentProduct={product || null} />;
};
This would allow you to test and match against multiple URLs that might apply to products, which makes for a very flexible solution!
I am using react-router-dom in a redux app.
This is my initial setup in index.js:
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
, document.getElementById('root'));
Then in my App.js I have:
render() {
return (
<div className="App">
<Route exact path="/" render={ () => {
return (
<div>
{
this.props.categories.map((category)=>{
console.log('category', category)
return (
<Link key={category.name} to="/category" >{category.name}</Link>
)
})
}
</div>
)
}}
/>
<Route path="/category" render={ () => {
console.log('category path this.props', this.props)
return (<p>Category is whatever</p>)
}}
/>
</div>
);
}
I would think that whenever I click any of the Links displayed the browser would automatically know how to render the new Route path /category but for some reason it does not.
What am I doing wrong?
The above post by Dane has the solution.
But in the spirit of presenting the solution with more clarity, I will copy and paste the relevant codes that made react router work well with redux and other middleware.
import { withRouter } from 'react-router-dom'
export default withRouter(connect(
mapStateToProps,
)(App))
From React Router docs,
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: <Route component={SomeConnectedThing}/>
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 maybe it's a problem with using render props. So:
either replace render with component, or
try their solution, with withRouter ( even there you have to make them into components )
https://reacttraining.com/react-router/core/guides/redux-integration/blocked-updates
Both Link and Router is compulsory.
Not Work!
import { BrowserRouter as Link } from "react-router-dom";
Work in my case.
import { BrowserRouter as Router, Link } from "react-router-dom";
In my case, this is working properly. If you will import router and link both together.
import { BrowserRouter as Router, Link } from "react-router-dom";
I am trying to make my Header component render when the Route is changed.
I tried using onChange and onValueChange on the Switch but neither one gets called.
Is there a way to call a function whenever the route gets changed?
<Switch>
<Route path="/posts/new" component={PostsNew}/>
<Route path="/posts/:id" component={PostsShow}/>
<Route exact path="/" component={PostsIndex}/>
</Switch>
After digging through the React Router docs (https://reacttraining.com/react-router/web/api/withRouter) I found a solution.
To make my header component update when the route changes all I had to do was import withRouter:
import {withRouter} from 'react-router-dom';
and export the class using withRouter:
export default withRouter((connect(null, mapStateToProps)(Header)));