React router v4 routes setup - react-router

During the migration from v2 to v4, my routes are now set up like so:
ReactDOM.render(
<Provider store={store}>
<Router>
<Route path='/admin' component={App} />
</Router>
</Provider>
, document.getElementById('root'))
with the app component being
class App extends Component {
render() {
return (
<AppContainer>
<Switch>
<Route path="/admin/dashboard" component={Dashboard} />
<Route path="/admin/signin" component={SignIn} />
<Route path="/admin/settings" component={Settings} />
</Switch>
</AppContainer>
);
}
}
In the app container, it calls an action which checks the login and then router.history.push('/admin/dashboard') if the login is valid. The rest of the AppContainer component is
render() {
return (
<div>
{this.props.children}
<Detached />
</div>
)
}
Going to /admin sends you to /admin/dashboard correctly.
Going to /admin/dashboard 404's, seemingly not even matching the first route path "/admin".
Am I doing anything blatantly wrong? Shouldn't going to /admin/xxxxx be matched by the first route?

It would be helpful to know where your 404 route is and whether there is any logic governing the push to '/admin/dashboard.
'/admin/xxxxx' should definitely result in a match for '/admin' as long as there is no 'strict' or 'exact' prop.
Potential error: If the logic in AppContainer checks login status and performs push to '/admin/dashboard' regardless of current pathname, then your app may be falling into the below recursive cycle:
User navigates to '/admin'
Route checks pathname '/admin' against path prop '/admin' and finds a match
Route renders 'App' component
AppContainer verifies that user is logged in
AppContainer pushes user to '/admin/dashboard'
Application rerenders with pathname '/admin/dashboard'
Route checks pathname '/admin/dashboard' against path prop '/admin' and finds a match
Route renders App component
AppContainer verifies that user is logged in
AppContainer pushes user to '/admin/dashboard'
Application rerenders with pathname '/admin/dashboard'
...
The simplest fix to implement would be to only push to '/admin/dashboard' if pathname is '/admin'.
A fix with arguably less cognitive overhead would be to remove the manual history.push to '/admin/dashboard' and change App to the following:
class App extends Component {
render() {
return (
<AppContainer>
<Switch>
<Route exact path="/admin" render={() => <Redirect to='/admin/dashboard' />} />
<Route path="/admin/dashboard" component={Dashboard} />
<Route path="/admin/signin" component={SignIn} />
<Route path="/admin/settings" component={Settings} />
</Switch>
</AppContainer>
);
}
}

Related

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>

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

React Router - Take over at root URL

So I've got React Router set up and I'm trying to run it from WordPress.
The app routes correctly as long as you start from the root "/". However if you manually navigate to any subpage via the address bar, React Router seems to only take over from there.
For example.
Hitting / will render the homepage. If you click the link 'style-guide' it will correctly route you to /style-guide and render the page.
However, if you manually navigate to /style-guide in your address bar, react will render the homepage there, and if you now click the style-guide link it will bring you to /style-guide/style-guide
What I need to do is tell react-router to always start from the root URL.
My Routes Look Like this
import {
BrowserRouter as Router,
Route,
Redirect,
Switch,
} from 'react-router-dom'
import PageContainer from 'containers/pageContainer'
class RoutesList extends React.Component {
render() {
return (
<Router>
<div>
<Switch>
<Route path="/" component={PageContainer} />
<Route path="style-guide" component={PageContainer} />
<Route
render={() => {
return <Redirect to="/" />
}}
/>
</Switch>
</div>
</Router>
)
}
}
export default RoutesList
Make your routes exact paths
<Route exact path="/" component={PageContainer} />
<Route exact path="/style-guide" component={PageContainer} />

Call stack exceeded when using react-router in an auth pattern

I'm using react-router rc6 in the following auth pattern:
const isLoggedIn = false
function requireAuth (nextState, replaceState) {
console.log(nextState.location.pathname)
if(!isLoggedIn) {
replaceState({ nextPathName: nextState.location.pathname }, '/login')
}
}
ReactDOM.render((
<Router history={browserHistory}>
<Route path='/' component={Main} onEnter={requireAuth}>
<Route path='login' component={Login} />
</Route>
</Router>
), document.getElementById('app'));
I've seen this as a common pattern, but according to https://github.com/rackt/react-router/issues/2773, I can't redirect in an onEnter hook because the function requireAuth above gets called in an infinite loop. How should I do it instead? I want to redirect to the /login page if not authenticated.
This is because the / onEnter handler tries to redirect to a route that executes the same / onEvent handler.
I.e. what happens is:
Router tries to handle request to / (first match of /login).
/ has an onEnter handler. Handler is executed.
Handler wants to navigate to /login.
Router tries to handle request to / (back to step 1).
So as you can see, the reason why you are getting a call stack exceeded error is because it is circular.
Try to change your routes to the following:
ReactDOM.render((
<Router history={browserHistory}>
<Route path='/' component={Main}>
<Route path='/login' component={Login} />
<Route path='/user' onEnter={requireAuth}>
<Route path='/profile' component={Profile}>
</Route>
</Route>
</Router>
), document.getElementById('app'));
This way only the routes that require authentication are protected behind your requireAuth handler.
If you'd rather have a simple auth solution for React, take a look at the React SDK for Stormpath that I've built.
It will take care of all of this. And instead of having to use hacky onEnter handlers, all you need to do is use the SDK's AuthenticatedRoute. E.g.
<Router history={createBrowserHistory()}>
<HomeRoute path='/' component={MasterPage}>
<IndexRoute component={IndexPage} />
<LoginRoute path='/login' component={LoginPage} />
<LogoutRoute path='/logout' />
<Route path='/verify' component={VerifyEmailPage} />
<Route path='/register' component={RegisterPage} />
<Route path='/forgot' component={ResetPasswordPage} />
<AuthenticatedRoute>
<HomeRoute path='/profile' component={ProfilePage} />
</AuthenticatedRoute>
</HomeRoute>
</Router>
The example above is a real-world example extracted from the React SDK example project. See: https://github.com/stormpath/stormpath-express-react-example/blob/master/src/app.js#L11-L23.
Let me know if this helped you.