This question is about React router v4.
Here's my App component:
export default function() {
return (
<ApolloProvider client={apolloClient}>
<Router onUpdate={() => window.scrollTo(0, 0)}>
<ScrollToTop>
<Grid>
<TopBar>
...
</TopBar>
<SideBar>
<SideBarHeader>Classes</SideBarHeader>
<ClassList/>
</SideBar>
<Main>
<Switch>
<Route path="/class/:id" component={ClassDoc}/>
</Switch>
</Main>
<Footer>
...
</Footer>
</Grid>
</ScrollToTop>
</Router>
</ApolloProvider>
)
}
The <Main> content is rendering correctly. However, in my <ClassList/> I want to gain access to the "class id" (same as used in the <Route> below).
I don't want to do
<Route path="/class/:id" component={ClassList}/>
Because I want to render the ClassList unconditionally, whether or not that path matches. If there's an id available I just want to highlight something.
I tried using withRouter but it's giving me
{path: "/", url: "/", params: {}, isExact: false}
instead of the /class route. i.e. the root route which is missing the params.
So how do I get the class id to ClassList?
For the time being, I've copied the Switch twice, but this isn't going to be good for maintainability.
<SideBar>
<SideBarHeader>Classes</SideBarHeader>
<Switch>
<Route path="/class/:classId(\d+)::releaseId(\d+)" render={({match:{params}}) => <ClassList {...params}/>}/>
<Route path="/class/:classId(\d+)" render={({match:{params}}) => <ClassList {...params}/>}/>
<Route component={ClassList}/>
</Switch>
</SideBar>
<Main>
<Switch>
<Route path="/class/:classId(\d+)::releaseId(\d+)" render={({match:{params}}) => <ClassDoc {...params}/>}/>
<Route path="/class/:classId(\d+)" render={({match:{params}}) => <ClassDoc {...params}/>}/>
</Switch>
</Main>
So withRouter should work (or <Route>{()=>}</Router>, see: https://reacttraining.com/react-router/web/api/Route/children-func)
Do you have a shouldComponentUpdate or redux connect in your sidebar? This can be blocking the withRouter update.
Related
So I have a route like in the documentation: <Route path="*"><Redirect to={'/'}/></Route>.
To make sure that all routes lead to '/' if the route doesnt exists.
All my routes work with a <Link> tag but not when I type them in the URL bar.
How can this be? I cant find information about this online.
index file:
return(
<Fragment>
<BrowserRouter basename={`/`}>
<Switch>
<App>
<TransitionGroup>
{routes.map(({ path, Component }) => (
<Route key={path} exact path={`${process.env.PUBLIC_URL}${path}`}>
{({ match }) => (
<CSSTransition in={match != null} timeout={100} classNames={anim} unmountOnExit>
<div><Component/></div>
</CSSTransition>
)}
</Route>
))}
<Route exact path="*"><Redirect to={'/'}/></Route>
</TransitionGroup>
</App>
</Switch>
</BrowserRouter>
</Fragment>
)
Route file:
export const routes = [
{
path:`/`,
Component:Home
},
{
path:`/login`,
Component:Login
},
{
path:`/signup`,
Component:Signup
},
{
path:`/event/:name`,
Component:Event
},
{
path:`/create-event`,
Component:CreateEvent
},
]
Link tag:
<Link to={"/event/" + events[i].name} className="text-dark">
From the react-router documentation,
All children of a <Switch> should be <Route> or <Redirect> elements. Only the first child to match the current location will be rendered.
I can see <Route> is not a direct or first child to <Switch>
Use Switch from react-router to renders the first child that matches the location. And be sure to keep, all the known routes before * to avoid redirection to the / path. Following is an example, for your case.
import React from "react";
import {
BrowserRouter as Router,
Route,
Switch,
Redirect,
} from "react-router-dom";
class App extends React.Component {
render() {
return (
<Router>
<Switch>
<Route exact path="/" component={() => <div>{"MAIN"}</div>} />
<Route
exact
path="/event/:name"
component={() => <div>{"SELECTED EVENT"}</div>}
/>
<Route exact path="*" component={() => <Redirect to={"/"} />} />
</Switch>
</Router>
);
}
}
export default App;
Hope this would solve your issue.
Please read this properly before marking as duplicate, I assure you I've read and tried everything everyone suggests about this issue on stackoverflow and github.
I have a route within my app rendered as below;
<div>
<Header compact={this.state.compact} impersonateUser={this.impersonateUser} users={users} organisations={this.props.organisations} user={user} logOut={this.logout} />
<div className="container">
{user && <Route path="/" component={() => <Routes userRole={user.Role} />} />}
</div>
{this.props.alerts.map((alert) =>
<AlertContainer key={alert.Id} error={alert.Error} messageTitle={alert.Error ? alert.Message : "Alert"} messageBody={alert.Error ? undefined : alert.Message} />)
}
</div>
The route rendering Routes renders a component that switches on the user role and lazy loads the correct routes component based on that role, that routes component renders a switch for the main pages. Simplified this looks like the below.
import * as React from 'react';
import LoadingPage from '../../components/sharedPages/loadingPage/LoadingPage';
import * as Loadable from 'react-loadable';
export interface RoutesProps {
userRole: string;
}
const Routes = ({ userRole }) => {
var RoleRoutesComponent: any = null;
switch (userRole) {
case "Admin":
RoleRoutesComponent = Loadable({
loader: () => import('./systemAdminRoutes/SystemAdminRoutes'),
loading: () => <LoadingPage />
});
break;
default:
break;
}
return (
<div>
<RoleRoutesComponent/>
</div>
);
}
export default Routes;
And then the routes component
const SystemAdminRoutes = () => {
var key = "/";
return (
<Switch>
<Route key={key} exact path="/" component={HomePage} />
<Route key={key} exact path="/home" component={HomePage} />
<Route key={key} path="/second" component={SecondPage} />
<Route key={key} path="/third" component={ThirdPage} />
...
<Route key={key} component={NotFoundPage} />
</Switch>
);
}
export default SystemAdminRoutes;
So the issue is whenever the user navigates from "/" to "/second" etc... app re-renders Routes, meaning the role switch logic is rerun, the user-specific routes are reloaded and re-rendered and state on pages is lost.
Things I've tried;
I've tried this with both react-loadable and React.lazy() and it has the same issue.
I've tried making the routes components classes
Giving all Routes down the tree the same key
Rendering all components down to the switch with path "/" but still the same problem.
Changing Route's component prop to render.
Changing the main app render method to component={Routes} and getting props via redux
There must be something wrong with the way I'm rendering the main routes component in the app component but I'm stumped, can anyone shed some light? Also note this has nothing to do with react-router's switch.
EDIT: I've modified one of my old test project to demonstrate this bug, you can clone the repo from https://github.com/Trackerchum/route-bug-demo - once the repo's cloned just run an npm install in root dir and npm start. I've got it logging to console when the Routes and SystemAdminRoutes are re-rendered/remounted
EDIT: I've opened an issue about this on GitHub, possible bug
Route re-rendering component on every path change, despite path of "/"
Found the reason this is happening straight from a developer (credit Tim Dorr). The route is re-rendering the component every time because it is an anonymous function. This happens twice down the tree, both in App and Routes (within Loadable function), below respectively.
<Route path="/" component={() => <Routes userRole={user.Role} />} />
needs to be
<Routes userRole={user.Role} />
and
loader: () => import('./systemAdminRoutes/SystemAdminRoutes')
Basically my whole approach needs to be rethought
EDIT: I eventually fixed this by using the render method on route:
<Route path="/" render={() => <Routes userRole={user.Role} />} />
Bumped into this problem and solved it like this:
In the component:
import {useParams} from "react-router-dom";
const {userRole: roleFromRoute} = useParams();
const [userRole, setUserRole] = useState(null);
useEffect(()=>{
setUserRole(roleFromRoute);
},[roleFromRoute]}
In the routes:
<Route path="/generic/:userRole" component={myComponent} />
This sets up a generic route with a parameter for the role.
In the component useParams picks up the changed parameter und the useEffect sets a state to trigger the render and whatever busines logic is needed.
},[userRole]);
Just put the "/" in the end and put the other routes above it.
Basically it's matching the first available option, so it matches "/" every time.
<Switch>
<Route key={key} exact path="/home" component={HomePage} />
<Route key={key} path="/second" component={SecondPage} />
<Route key={key} path="/third" component={ThirdPage} />
<Route key={key} exact path="/" component={HomePage} />
<Route key={key} component={NotFoundPage} />
</Switch>
OR
<Switch>
<Route path="/second" component={SecondPage} />
<Route exact path="/" component={HomePage} />
<Route path="*" component={NotFound} />
</Switch>
Reorder like this, it will start working.
Simple :)
I'm trying to do layouts with react-router.
When my user hits / I want to render some layout. When my user hits /login, or /sign_up I want the layout to render, with the relevant component for /login or /sign_up rendered.
Currently, my App.js looks like this
return (
<div className={className}>
<Route path="/" component={Auth} />
<ModalContainer />
</div>
);
My Auth.js looks like this
return (
<AuthFrame footerText={footerText} footerClick={footerClick}>
<Route path="/login" component={LoginContainer} />
<Route path="/sign_up" component={SignUpContainer} />
</AuthFrame>
);
So AuthFrame will get rendered when I hit /, and then react router looks for login or sign_up to render the other containers.
However, when I hit /, only the AuthFrame will render.
I would like for / to be treated as /login.
How do I achieve this?
The Switch component is useful in these cases:
return (
<AuthFrame footerText={footerText} footerClick={footerClick}>
<Switch>
<Route path="/login" component={LoginContainer} />
<Route path="/sign_up" component={SignUpContainer} />
{/* Default route in case none within `Switch` were matched so far */}
<Route component={LoginContainer} />
</Switch>
</AuthFrame>
);
see: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Switch.md
I think you're forced to introduce a prop/state which indicates the status of your viewer. This means is he signed in or just a guest of your website.
Your router can't obviously render /login if you you hit / but the router allows you to redirect to another page:
class AuthContainer extends React.Component {
defaultProps = {
loggedIn: false
}
render() {
return <div>
<Route path="/login" component={LoginContainer}/>
<Route path="/sign_up" component={SignUpContainer}/>
</div>
}
}
class PublicHomePage extends React.Component {
render() {
return <div>
<Route path="/settings" component={SettingsComponent}/>
<Route path="/profile" component={ProfileComponent}/>
<Route path="/and_so_on" component={AndSoOnComponent}/>
</div>
}
}
class App
extends React.Component {
defaultProps = {
loggedIn: false
}
render() {
const {loggedIn} = this.props;
if (loggedIn) {
return <PublicHomePage/>
}
return <Route exact path="/" render={() => (
<Redirect to="/login"/>
)}/>
}
}
I hope this code works for you. It isn't quite perfect but it should give you an idea how you could solve your problem.
In your case I would probably manipulate a bit with Routes in react-router. This code in AuthFrame should do the trick:
return (
<AuthFrame footerText={footerText} footerClick={footerClick}>
{["/", "/login"].map((path, ind) =>
<Route exact key={ind} path={path} component={LoginContainer} />
)}
<Route exact path="/sign_up" component={SignUpContainer} />
</AuthFrame>);
Note the usage of exact on the routes, this is to prevent matching login component on /sign_up since it will also match / and prevent rendering both login and signup when accessing the root path (/).
I am building a small project to test the React Router 4. So far so good, my url updates and my props.locations shows up with withRouter. But I can't seem to change my navBar base on the props.location.
This is what my Routes look like:
<Provider store={ store }>
<BrowserRouter onUpdate={() => window.scrollTo(0, 0)}>
<div className="root">
<App/>
<Switch>
<Route exact path="/" component={HomePageContainer}/>
<Route eact path="/signin" component={SignInContainer}/>
<Route eact path="/reviews" component={Reviews}/>
<Route path="/favorites" component={Favorites}/>
<Route render={() => (
<p>Page Not Found</p>
)}/>
</Switch>
</div>
</BrowserRouter>
</Provider>
My component basically contains my HeaderBar and navBar, I have messages thats in navBar that I want to change so I would have title of the page, My App looks like this:
const App = (props) => {
let toRender = null;
if(props.location.pathname !== '/signin'){
toRender = (
<div>
<HeaderContainer />
<NavBarContainer />
</div>
);
} else {
toRender = null;
}
return(
<div className="App">
{ toRender }
</div>
);
}
I can import my navBar container into each of the routes i have for '/', '/reviews', and '/favorites'. But I don't think that would be a modular way to do it. I also have a shouldComponentUpdate lifecycle method inside NavBar, and I tested with a console.log to print something when it does update when I switch url, but it doesn't. Does anyone have any suggestions on a clean solution to pass in the props to my NavBar without importing it into every single one of the components? I also tried putting App component in the place of Route so I would have:
<App exact path="/" component={HomePageContainer}/>
<Route eact path="/signin" component={SignInContainer}/>
<App eact path="/reviews" component={Reviews}/>
<App path="/favorites" component={Favorites}/>
But then my Components aren't rendering besides the App. I'm not sure what's happening or why it's not rendering the components. Any suggestions would be much appreciate it. Thank you.
I know that this question has been asked before, but I keep having issues with this.
The issue I have is that when I use a Page Layout-like component to wrap my routes, this page layout is re-rendered when changing path.
In react-router v3 I did something like this:
<Router history={this.props.history}>
<Route path="/">
<IndexRedirect to="/Dossiers" />
<Route path="/Dossiers" component={MainLayout}>
<IndexRoute component={DossiersPage} />
<Route path="/Dossiers/:dossierId/:title" component={DossierDetailsPage} />
</Route>
</Route>
</Router>
When moving paths, this would NOT re-render the MainLayout component (which is easily checked by putting something in state inside MainLayout).
Now, in react-router v4 I tried a couple of approaches already:
Wrapping Switch with the MainLayout component
Creating a RouteWithMainLayout component which wraps Route (as described here: https://simonsmith.io/reusing-layouts-in-react-router-4/)
Some of the approaches described here: https://github.com/ReactTraining/react-router/issues/3928
However, all solutions I've tried seem to re-render the MainLayout component, basically causing state to reset to its initial value(s).
tldr; How do I create a wrapping component in react-router v4 which doesn't re-render when changing paths
I put together a codesandbox example of how I'm using a "page layout" type of component. It uses React Router v4.1.2.
https://codesandbox.io/s/Vmpy1RzE1
As you described in your question, and as was described in Matt's answer, the MainLayout component wraps the routes.
<BrowserRouter>
<MainLayout>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" exact component={About} />
</Switch>
</MainLayout>
</BrowserRouter>
It is true that the MainLayout component re-renders when I navigate the app, in the sense that render is called by React. But, the MainLayout component is never unmounted, so the state never re-initializes.
I've placed some console.logs around my example to show this. My MainLayout looks like this:
export default class MainLayout extends React.Component {
state = {
layoutCreatedOn: Date(),
};
componentDidMount() {
//This will not fire when we navigate the app.
console.log('layout did mount.');
}
componentWillUnmount() {
//This won't fire,
// because our "shared page layout" doesn't unmount.
console.log('layout will unmount');
}
render() {
//This does fire every time we navigate the app.
// But it does not re-initialize the state.
console.log('layout was rendered');
return (
<div styles={styles}>
<h5>
Layout created: {this.state.layoutCreatedOn}
</h5>
<Sidebar />
{this.props.children}
</div>
);
}
}
As you click around the app, you'll see a few things.
componentDidMount fires only once.
componentWillUnmount never fires.
render fires every time you navigate.
Despite this, my layoutCreatedOn property shows the same time as I navigate the app. The state is initialized when the page loads, and never re-initialized.
You no longer need IndexRedirect, instead just wrap all of your routes in your MainLayout component, such as:
<Router history={this.props.history}>
<Switch>
<MainLayout>
<Route path="/" component={DossiersPage}/>
<Route path="/Dossiers/:dossierId/:title" component={DossierDetailsPage} />
</MainLayout>
</Switch>
</Router>
Here is the correct solution for React Router v4 as stated here
So basically you need to use the render method to render the layout and wrap your component like this:
<Router>
<Switch>
<Route path={ROUTES.LOGIN} render={props =>
<LoginLayout {...props}>
<Login {...props} />
</LoginLayout>
} />
<Route path={ROUTES.REGISTER} render={props =>
<LoginLayout {...props}>
<Register {...props} />
</LoginLayout>
} />
<Route path="*" component={NotFound} />
</Switch>
</Router>
This will not cause re-rendering of the layout when you are changing the routes.
When you have many different components with many different layouts you can go ahead and define them in a route config array like the example from the issue I linked:
const routes = [
{ path: '/',
exact: true,
component: Home
},
{ path: '/about',
component: About,
},
{ path: '/cart',
component: Three,
}
]
<Router>
<Switch>
{routes.map({ path, exact, component: Comp } => (
<Route path={path} exact={exact} render={(props) => (
<LayoutWithSidebarAndHeader {...props}>
<Comp {...props}/>
</LayoutWithSidebarAndHeader>
)}/>
))}
<Route component={Error404}/>
</Switch>
</Router>