react router query parameters changed component will unmount - react-router

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

Related

React Router - are nested <Switch> components an anti-pattern?

From React Router's docs:
All children of a <Switch> should be <Route> or <Redirect> elements. Only the first child to match the current location will be rendered.
Nonetheless, nested <Switch> statements are allowed. I use the pattern to break up large numbers of <Routes>:
<Switch>
<Route path="/foo" component={FooRouter} />
<Route path="/bar" component={BarRouter} />
<Route path="/baz" component={BazRouter} />
</Switch>
...
const FooRouter = () => (
<Switch>
<Route exact path="/foo/:id" component={ViewFoo} />
<Route exact path="/foo/new" component={NewFoo} />
</Switch>
)
const BarRouter = () => (
<Switch>
<Route exact path="/bar/new" component={NewBar} />
</Switch>
)
....
Curious if there is a better method for breaking up large numbers of routes and if nested <Switch> statements should be avoided?
as you solve it just fine when you have a lot of nested route yo can speared them across the app and make a dynamic routes
but soon react-router-dom v6 will be release with a huge upgrade one of them is useRoutes
that let you configure your routes like this:
let element = useRoutes([
// A route object has the same properties as a <Route>
// element. The `children` is just an array of child routes.
{ path: '/', element: <Home /> },
{
path: 'users',
element: <Users />,
children: [
{ path: '/', element: <UsersIndex /> },
{ path: ':id', element: <UserProfile /> },
{ path: 'me', element: <OwnUserProfile /> },
]
}
]);
introduction to react-router-dom v6 they have some cool new feature that worth to watch for
one of them is the replace of with witch help you a lot with nested routes and fun thing you don't gonna need to use the exact anymore
<Routes>
<Route path="/" element={<UsersIndex />} />
<Route path=":id" element={<UserProfile />} />
<Route path="me" element={<OwnUserProfile />} />
</Routes>
this is how it gonna look with the new feature
A note on nested conditional routes: Switch must only have Route children. If you declare Switch inside Switch, every route after Switch won't be used, i.e.
<Switch>
<Route path="/1" />
<Switch> ... </Switch>
<Route path="/2" /> // this one won't work!
</Switch>
So don't do this, stick to declaring one route per condition or render routes as an array under common condition:
<Switch>
{condition && <Route path="/1" >}
{condition && <Route path="/2">}
{/* or */}
{anotherCondition && [
// notice `key`. React will warn you about rendering a list without key prop
<Route key="3" path="/3">,
<Route key="4" path="/4">,
]}
</Switch>
This is true for react-router-dom v5, not sure about 6.

React router v4 routes setup

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>
);
}
}

react-router render menu when path does not match

I'm using react-router and I want to render a menu component when the user is not in the root and not in the /login path. This is what I have so far
<Route path="/:subpath" component={TopMenuComponent} />
<div>
<Route
exact path="/"
render={props => (
<LoginContainer {...props} setTitle={this.setTitle} />
)}
/>
<Route path='/landing' component={LandingComponent} />
</div>
takes care of not rendering the TopMenuComponent component in the '/' location, however how do I avoid it rendering TopMenuComponent when the user is in the /login path? I could always create another component and wrap it up, but I think that is too much just for this.
Simplest Implementation
Use a ternary expression or short-circuit evaluation to conditionally render your component based on location.pathname, like so:
<Route
render={({ location }) => ['/', '/login'].includes(location.pathname)
? <Component/>
: null
}
/>
Regex Implementation
React Router's matching of path strings relies on path-to-regexp#^1.7.0.
As a result, you can instruct routes to not render for certain paths using regular expressions.
The following implementations should render given any path value, bar "/" and "/login":
// With Regex Inside String.
<Route path={"^(?!.*(\/|\/login)).*$"} component={TopMenuComponent}/>
// With Explicit Regex.
<Route path={new RegExp('^(?!.*(\/|\/login)).*$')} component={TopMenuComponent}/>
Regex in the route path didn't work for me. What worked for me was this. Just add the other condition.
<Route render={({ location }) => {
return location.pathname.indexOf('/login') === -1 ? TopMenuComponent : null
}} />
If you don't wish to use Regular Expressions directly, you can place your login Route in a Switch with the top menu component Route. It will only run the first matching Route and routes without a path attribute match anything.
<div>
<Switch>
<Route
exact path="/"
render={props => (
<LoginContainer {...props} setTitle={this.setTitle} />
)}
/>
<Route path="/:subpath" component={TopMenuComponent} />
</Switch>
<Route path='/landing' component={LandingComponent} />
</div>
For your example, you would need to reorder your divs.
Taken Regex from Arman's answer.
const notInLogin = /^(?!.*(\/login)).*$/
export default () => (
<Router history={history}>
<>
<Route path={notInLogin} component={NavBar} />
<Switch>
<Route exact path="/login" component={Login} />
<Route exact path="/accounts" component={Account} />
<Route exact path="/profile" component={Profile} />
<Route exact path="/" component={Home} />
</Switch>
</>
</Router>
)
If you get PropsType error: https://stackoverflow.com/a/50439120/1099314
Similar to taylor michels answer, but the following accounts for both the '/login' and the '/' (root) routes:
<Route
render={({ location }) =>
location.pathname !== "/" && location.pathname !== "/login" ? (
<TopMenuComponent />
) : null
}
/>>
This also renders the component as a jsx tag <TopMenuComponent />, which works for me where the other approach did not.
You can use useRouteMatch hook
const ParentComponent = props => {
const matched = useRouteMatch(['/', '/login'])
if (matched && matched.isExact) return null
return <ChildComponent {...props} />
}

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

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.

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.