React-Router v4: Cannot read property 'route' of undefined - react-router

I want to redirect when I hit a button, so I used the withRouter to get the access to the history props.
But I get the error:
Uncaught TypeError: Cannot read property 'route' of undefined
at Route.computeMatch (react-router.js:1160)
error when I wrap my component with the withRouter HOC.
If I remove withRouter function, it just works.
My code looks like the following:
class App extends Component {
// ...some unrelated functions
handleTitleTouchTap = e => {
e.preventDefault()
const { history } = this.props
history.push('/')
}
render() {
//...other components
<Router>
<div>
<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/search" component={Search}/>
<Route path="/gamelist/:listId" component={GameListDetail}/>
<Route path="/game/:gameId" component={GameDetail}/>
<Route path="/manageuser" component={ManageUser} />
<Route path="/addgamelist" component={AddGameList} />
<Route path="/addgame" component={AddGame} />
<Route path="/test" component={Test} />
<Route component={NoMatch} />
</Switch>
<LoginForm isLoginFormOpen={isLoginFormOpen} closeLoginForm={closeLoginForm} handleLogin={handleLogin}/>
<RegisterForm isRegisterFormOpen={isRegisterFormOpen} closeRegisterForm={closeRegisterForm} register={register}/>
</div>
</Router>
)
}
const mapStateToProps = state => ({
//some props
})
const mapDispatchToProps = dispatch => ({
//some functions
})
const Container = connect(mapStateToProps, mapDispatchToProps)(App)
export default withRouter(Container)

I've got the same issue and I solved it enclosing the wrapped component in a Router component (namely BrowserRouter).
In your example, it would become:
// assuming this file is Container.js
export default withRouter(Container)
// index.js
import Container from './Container'
render(
<BrowserRouter>
<Container/>
</BrowserRouter>,
document.getElementById('root')
)
Working example from the docs here: https://codepen.io/pietro909/pen/RVWmwZ
I also opened an issue on the repo because the example from the docs is not clear enough in my opinion https://github.com/ReactTraining/react-router/issues/4994.

Related

react-router-dom 6 upgrade help: All component children of <Routes> must be a <Route> or <React.Fragment>

Our application recently updated to the beta versions of react-router-dom, and things were fine. Then when I try to update to 6.0.2, I get lots of invariant errors about All component children of <Routes> must be a <Route> or <React.Fragment>. This is because we have our routes defined as follows:
Feature.jsx:
export const FeatureRoutes = () => (
<Routes>
<Route path="/" element={<Feature1 />} />
<Route path="/*" element={<NotFound />} />
</Routes>
);
routes.jsx:
export const routes = [
{
path: "feature",
component: FeatureRoutes,
},
/* lots of other routes, defined the same way: <Route> wrapped in a component */
];
App.jsx:
<Routes>
{routes.map((route) => (
<Route key={route.path} path={`${pathPrefix}/${route.path}/*`}>
<route.component />
</Route>
))}
</Routes>
This now results in the error above, because the inner routes (for example FeatureRoutes) are wrapped in a functional component. I've tried returning the literal JSX but then get another error. I'm not sure how to fix this: is the only answer to completely rewrite how we define our routes? We also have some routes that are stored in the back-end and map to custom components - again I'm not sure how I can wrap these now I'm not allowed to have a component between Routes and Route.
Any advice appreciated.
I believe a small refactor will get your app rendering again.
In the routes array rename component to Component so it can be rendered as a React component, i.e. as a properly named React component (PascalCased).
const routes = [
{
path: "feature",
Component: FeatureRoutes
}
/* lots of other routes, defined the same way: <Route> wrapped in a component */
];
When mapping the routes render the Component out on the Route component's element prop as JSX.
<Routes>
{routes.map(({ path, Component }) => (
<Route
key={path}
path={`${pathPrefix}/${path}/*`}
element={<Component />}
/>
))}
</Routes>

How to pass props from <Route /> to a component

Even after using the render option in the component, the component inside render is not receiving an new prop.
Have checked if other default props are accessible and they are.
Layout.js:
<Switch>
<Route
exact
path="*"
render={(props) => <HomeComponent {...props} loc={constants["404"]} />}
/>
</Switch>
HomeComponent.js:
componentDidMount() {
console.log(this.props.loc) // Output is shown as undefined
}
I expect the prop loc to be filled with the data for 404 page, but receiving undefined

React Router props `location` / `match` not updating with `ConnectedRouter`

I've got my app setup as in the docs:
Step 1
...
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
...
const history = createBrowserHistory()
const store = createStore(
connectRouter(history)(rootReducer), // new root reducer with router state
initialState,
compose(
applyMiddleware(
routerMiddleware(history), // for dispatching history actions
// ... other middlewares ...
),
),
)
Step 2
...
import { Provider } from 'react-redux'
import { Route, Switch } from 'react-router' // react-router v4
import { ConnectedRouter } from 'connected-react-router'
...
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ }
<div> { /* your usual react-router v4 routing */ }
<Switch>
<Route exact path="/" render={() => (<div>Match</div>)} />
<Route render={() => (<div>Miss</div>)} />
</Switch>
</div>
</ConnectedRouter>
</Provider>,
document.getElementById('react-root')
)
I click on a Link or even dispatch(push('/new-url/withparam'))
However the props for match location are remaining the previous values or whatever the first page was.
What is happening?
This one has bitten me many times.
Your Switch and Route etc. MUST NOT BE INSIDE A CONNECTED COMPONENT!
If the component is connected, the props for match, location, etc. don't seem to get updated and propagate down to your routes.
This means don't connect your top level App or Root, or any other nested containers between the ConnectedRouter and Route
--
Update:
You may just need to wrap your component with
<Route render={ (routerProps) => <YourConnectedComponent { ...routerProps } />
I decided to add example to here as I feel it is valuable input - even tho, it's already answered.
I had similar problem, when I pushed url into router history, it changed URL but it didn't navigate properly on the component I wanted. I googled and searched for answer for hours, until I found this thread which finally helped me to find out what I made wrong. So all credits to #ilovett.
So here is an example, if someone will need it for better understanding:
I had code similar to this:
export const routes =
<Layout>
<Switch>
<Route exact path='/' component={ Component1 } />
<Route path='/parameter1/:parameterValue' component={ Component2 } />
</Switch>
</Layout>;
<Provider store={ store }>
<ConnectedRouter history={ history } children={ routes } />
</Provider>
It was working fine when I came to a project, but then I decided to refactor Layout component and I connected it to the store which caused that Component2 stopped receiving correct values in the ownProps.match.params.parameter1 and because of that it rendered component completely wrong.
So only thing what you need to do is move Layout outside of ConnectedRouter. Nothing between ConnectedRouter and Route can be connected to the store.
Working example is this then:
export const routes =
<Switch>
<Route exact path='/' component={ Component1 } />
<Route path='/parameter1/:parameterValue' component={ Component2 } />
</Switch>;
<Provider store={ store }>
<Layout>
<ConnectedRouter history={ history } children={ routes } />
</Layout>
</Provider>

Trouble migrating to react-router v4

I am having trouble migrating to react router 4 with nested routes. Here was some snippets from my previous code. was my layout container and everything was rendered within that if logged in (otherwise redirect to login)
ReactDOM.render(
<Provider store={ store }>
<div>
<Router history={ history }>
<Route path='/login' component={ Login } />
<Route path="/password/reset" component={PasswordReset} />
<Route path='/register' component={ Register } title={ 'Register' } />
<Route path='/password/change/:token' component={ ChangePassword } title={ 'Register' } />
<Route component={ EnsureLoggedInContainer }>
<Redirect from='/' to='/dashboard' />
<Route path='/' component={ App }>
<Route path='/logout' component={ Logout } />
....
</Router>
</div>
within to render the children components:
class ContentLayout extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
let children = null;
if (this.props.children) {
children = React.cloneElement(this.props.children, {
updateHeader: this.props.updateHeader,
});
}
return (
<div className={ this.props.cls }>
<MainHeader
updateHeader={ this.props.updateHeader }
header={ this.props.header }
/>
{children}
</div>
);
}
}
With v4 I've been trying to figure out the proper way to render as the layout and any child components within. So far I've been trying to get it working but feel I am on wrong path. (Currently props.match always points to '/')
ReactDOM.render(
<Provider store={ store }>
<ConnectedRouter history={history}>
<div>
<Switch>
<Route exact path='/login' component={ Login } />
<Route exact path="/password/reset" component={PasswordReset} />
<Route exact path='/register' component={ Register } />
<Route exact path='/password/change/:token' component={ ChangePassword } />
<Route path='/' component={ App } />
</Switch>
</div>
</ConnectedRouter>
</Provider>,
document.getElementById('app')
);
Within App
const RouteWithProps = ({ component: Component, props, ...extraProps})=> {
return (<Route
{...extraProps}
render={() => <Component {...props} {...extraProps} />}
/>
);
}
and with the component rendering
{securedRoutes.map((route, i) => (
<RouteWithProps key={i} {...route} updateHeader={this.props.updateHeader} location={this.props.location} match={this.props.match} />
))}
What is the proper way or a good example of how to structure the app so for all logged in routes the layout is
<App>
<ContentLayout>
<Child>
with App passing props like updateHeader and anything else to all children.
I got it working by removing passing location and match to RouteWithProps.
I had an issue with the RouteWithSubRoutes example in dealing with nested routes for things like /.../:id and /.../:submit ended up doing this to make work so I can continue working. Do not think this is ideal but will work till another answer on best practices.
const RouteWithProps = ({ component: Component, ...extraProps }) => {
return (<Route exact path={extraProps.path}
{...extraProps}
render={matchProps => <Component {...matchProps} {...extraProps}
/>}
/>
);
}
Also removed passing this.props.location and match to this component.

react router query parameters changed component will unmount

When query parameters changed, the same component will unmount and then mount.for example:
I have a url like /admin and also have a component called Admin. In Admin, there are some inputs for searching. I add a query parameters after /admin like /admin?userId=123.The componet's componentDidMount will excute again. Is there any way to prevent this?
and setting likes this
export default function (history, app) {
return (
<Switch>
<Route exact path='/admin/settings/user' component={getComponent(User,app,userModel)} />
<Route path='/admin/settings/user/:id' component={getComponent(UserEdit,app,userModel)} />
<Route path='/admin/settings/role' component={getComponent(Role,app,roleModel)} />
<Route path='/admin/settings/menu' component=
</Switch>
)
}
getComponent is a layload component.
#Alex Brazh I used v4 and the router likes this;
<Router>
<Switch>
<Route exact path='/' component={getComponent(Login,app,loginModel)}/>
<Route path='/admin' render={ props => (
<Layout>
{ settings(history, app) }
</Layout>
)}/>
<Route path='/finance' render={ props => (
<Layout>
{ finance(history, app) }
</Layout>
)}/>
</Switch>
</Router>
You can use the URL interface to set query string values without unmount and mount your components:
const queryStringValue = 'bar'
const url = new URL(window.location.toString());
url.searchParams.set('foo', queryStringValue);
window.history.replaceState(null, '', url.toString());
Also, this solution won't add a new item in browser navigation stack