I can't figure this out even when looking on SO answers. I have a layout that looks like this:
const Dashboard = (props) => (
<div className={styles.views}>
<Route
to="/dashboard/reports/create"
render={() => <ReportsForm {...props} />}
exact
/>
<Route
to="/dashboard/reports"
render={() => <Reports {...props} />}
exact
/>
</div>
);
const routes = [
{ path: '/', component: Home, exact: true },
{ path: '/dashboard', component: Dashboard },
{ path: '/about', component: About, exact: true },
{ path: undefined, component: Error404 },
];
const Routes = () => {
return (
<Switch>
{routes.map((config, i) => {
const key = `path-${config.path}`;
return <Route key={key} {...config} />;
})}
</Switch>
);
};
const App = compose(
withRouter,
connect(mapStateToProps),
)(() => {
return (
<Router history={history}>
<IntlProvider>
<Routes />
</IntlProvider>
</Router>
);
})
I have a dashboard component responsible for rendering multiple tabs, so going to /dashboard/reports/create should only render the ReportsForm component, and going to /dashboard/reports should only render the Reports component. Currently both are rendered in both cases.
What am I doing wrong?
EDIT
When I try to print out the match prop in the Dashboard it gives me this – maybe this will be helpful:
{
"path": "/dashboard",
"url": "/dashboard",
"isExact": false,
"params": {}
}
Apart from typo that you pointed out for declaring to instead of path
You can wrap Dashboard component Routes in a Switch
const Dashboard = (props) => (
<div className={styles.views}>
<Switch>
<Route
path="/dashboard/reports/create"
render={() => <ReportsForm {...props} />}
exact
/>
<Route
path="/dashboard/reports"
render={() => <Reports {...props} />}
exact
/>
</Switch>
</div>
);
If that dont work you can even wrap the entire thing in Route with initial path as follows:
const Dashboard = props => (
<div className={styles.views}>
<Route path="/dashboard/reports"> // <------------------
<Switch>
<Route path="/dashboard/reports/create" render={() => <ReportsForm {...props} />} exact />
<Route path="/dashboard/reports" render={() => <Reports {...props} />} exact />
</Switch>
</Route>
</div>
);
Here's the working example solution that I just created: https://stackblitz.com/edit/react-uih91e-router-nested?file=index.js
Related
I have multiple components with different paths (routes) and would like to export those to a single Main router component.
For example:
routeComponent1.js
export default function childRoutes() {
return (
<div>
<Route path="/foo" component={foo} />
<Route path="/bar" component={bar} />
</div>
);
}
routeComponent2.js
export default function childRoutes2() {
return (
<div>
<Route path="/foo2" component={foo2} />
<Route path="/bar2" component={bar2} />
</div>
);
}
I would like to use it in
root.js
import routeComponent1 from 'routeComponent1.js';
import routeComponent2 from 'routeComponent2.js';
class Root extends Component {
constructor(props) {
super(props);
}
render() {
return <Router>{routeComponent1}</Router>;
}
}
It is giving an error - Invariant Violation: <Route> elements are for router configuration only and should not be rendered.
Expecting the
<Router>
<div>
<Route path="/foo" component={foo} />
<Route path="/bar" component={bar} />
</div>
</Router>
routeComponent1 and routeComponent2 are React components. React components are Capitalized and rendered as JSX.
Example:
import RouteComponent1 from 'routeComponent1.js';
import RouteComponent2 from 'routeComponent2.js';
class Root extends Component {
render() {
return (
<Router>
<RouteComponent1 />
<RouteComponent2 />
</Router>
);
}
}
Assuming you're using the latest version of react router (v6), you can use nested routes as described here: https://reactrouter.com/en/main/start/overview#nested-routes
So this is what your App.jsx might look like:
import {
BrowserRouter as Router, Routes, Route
} from "react-router-dom";
import {AuthRoute, NonAuthRoute, UserParent} from "../components";
import {
Dasboard, UserList, CreateUser, SignIn, Page404
} from "./pages"
// Define your paths here ...
export const paths = {
SIGN_IN: "/sign-in",
DASHBOARD: "/dashboard",
LIST_USERS: "/users/list"
CREATE_USERS: "/users/create"
}
// All your routes which require authentication (requires login)
const authRoutes = [
{
path: paths.DASHBOARD,
element: <Dashboard />
},
]
// Authenticated routes but need a particular parent component
const userAuthRoutes = [
{
path: paths.LIST_USERS,
element: <UserList />
},
{
path: paths.CREATE_USERS,
element: <CreateUser />
}
]
// Paths that dont require authentication
const nonAuthRoutes = [
{
path: paths.SIGN_IN,
element: <SignIn />
},
]
const App = () => {
return (
<Router>
<Routes>
{/* AUTHENTICATED ROUTES */}
<Route element={<AuthRoute />}>
{authRoutes.map((route, index) =>
<Route key={index} {...route} />;
)}
{/* User Management */}
<Route element={<UserParent />}>
{userAuthRoutes.map((route, index) =>
<Route key={index} {...route} />
)}
</Route>
</Route>
{/* NON-AUTH ROUTES */}
<Route element={<NonAuthRoute />}>
{nonAuthRoutes.map((route, index) =>
<Route key={index} {...route} />;
)}
</Route>
<Route path="*" element={<Page404 />} />
</Routes>
</Router>
)
}
I'm trying to make react-router-dom work with a simple url: /user/{name} but for some reason cannot get it to load the page with the url slug for the name.
This is the return of my App function component:
<>
<MainNavBar navigation={navigation} />
<Routes>
<Route index={true} element={<Home />} exact />
<Route path="user" element={<User />} exact>
<Route
path=":name"
render={
({ match: { params: { name } } }) => {
console.log(name);
console.log("test2");
return (<UserPage
userName={name}
/>);
}}
/>
</Route>
<Route path="*" element={<PageNotFound />} />
</Routes>
</>
This is the User component; a placeholder for my debugging atm.
const User = () => (
<div>
<header className="App-header">
<Outlet />
</header>
</div>
);
When I go to http://localhost:3000/user/test it loads the User component but not the children (the Outlet/UserPage elements)
I've tried lots of combinations but seem to be doing something wrong, so any help would be very appreciated. Thanks!
In react-router-dom v6 the Route components no longer have render or component props, they render their components on the element prop. Use the useParams hook to access the route match params. If UserPage is a component that can't use React hooks, then use a wrapper function component to access the route match param and pass it as a prop.
const UserPageWrapper = () => {
const { name } = useParams();
useEffect(() => {
console.log({ name }); // <-- log param in effect
}, [name]);
return <UserPage userName={name} />;
};
...
<>
<MainNavBar navigation={navigation} />
<Routes>
<Route index element={<Home />} />
<Route path="user" element={<User />}>
<Route path=":name" element={<UserPageWrapper />} />
</Route>
<Route path="*" element={<PageNotFound />} />
</Routes>
</>
Hello I want it to render InstrumentPage with no props when #/ is access but when #/ipb is access pass ipb as the props entity. But it's not working.
render() {
return (<React.Fragment>
<HashRouter>
<Switch>
<Route path='/' render={(props) => <InstrumentPage {...props} />}/>
{Object.keys(AppConfig).forEach((property) =>
<Route path={property} render={(props) => <InstrumentPage {...props} entity={property} />} />)
}
</Switch>
</HashRouter>
</React.Fragment>);
}
AppConfig
const AppConfig = {
pb: {
header: 'PBHeader',
body: 'PBBody',
},
ipb: {
header: 'IPBHeader',
body: 'IPBBody',
},
};
export default AppConfig;
You need to use map and return the Route, forEach won't return anything but undefined.
<Switch>
<Route exact path='/' render={(props) => <InstrumentPage {...props} />}/>
{Object.keys(AppConfig).map((property) =>
<Route
path={`/${property}`}
render={ (props) => <InstrumentPage {...props} entity={property} /> }
/>
)
}
</Switch>
Note: When you write path={property} which means path={ipb} or path={pb} which is incorrect path. Correct path should be path="/ipb" (Notice the slash), for this you need to do /${property} [contains back-tick] (using Template string).
Also add exact prop for your first Route otherwise it will going to match for all the Routes.
I'd like to test a redirection from the / path to a locale path (e.g. /en). So here's what the component looks like:
// GuessLocale is imported from a helper
const App = () => (
<Router>
<Switch>
<Route exact path='/' render={() => (
<Redirect to={`/${guessLocale()}`} />
)} />
<Route exact path='/:lang' component={Home} />
</Switch>
</Router>
)
And this is the current testing function:
it('redirects to a localed path', () => {
const wrapper = mount(
<MemoryRouter initialEntries={['/']}>
<App />
</MemoryRouter>
)
expect(wrapper.find('Redirect')).toHaveLength(1)
})
Obviously, the test fails as the Redirect component is inside a child as a function as the render prop to the Route
In the test, I wrap the App in a memory router but in the App component, a browser router is already present so I might need to refactor that.
But even with the routes splitted in a Routes component, I don't know how to test inside the render prop.
You can test this by checking the component that should be rendered after the redirection, in this case the Home component like this:
it('redirects to a localed path', () => {
let wrapper = mount(
<MemoryRouter initialEntries={['/']}>
<Switch>
<Route exact path='/' render={() => (
<Redirect to={`/en`} />
)} />
<Route path='/en' component={Home} />
<Route render={() => "not found"} />
</Switch>
</MemoryRouter>
)
expect(wrapper.find(Home)).toHaveLength(1)
})
I had to remove <Router> to get this working since we're not using it for the browser. Another way of doing this is to check the <Route> pathname property within the location prop. see here:
it('redirects to a localed path', () => {
let wrapper = mount(
<MemoryRouter initialEntries={['/']}>
<Switch>
<Route exact path='/' render={() => (
<Redirect to={`/en`} />
)} />
<Route path='/en' component={Home} />
<Route render={() => "not found"} />
</Switch>
</MemoryRouter>
)
expect(wrapper.find("Route").prop('location').pathname).to.equal("/en")
})
Private Route definition:
PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
this.state.isAuthenticated
? <Component {...props} />
: <Redirect to="/" />
)}/>
)
Passing in props in router to Home:
<this.PrivateRoute path="/home" exact component={Home} portalId={this.state.portalId} />
React developer tools shows:
Console logs:
Where is my props.portalId? I am setting the this.props.portalId after a fetch in another component so this may be the problem because of the asynchronous call? How would I fix this in that case?
Edit: Console.log is being called in Home's componentDidMount():
class Home extends Component {
componentDidMount() {
console.log(this.props.portalId);
}
To see props, you should pass props to the component rendered by <Route />, instead you pass props to the <Route />.
You can use render attribute instead of component like this:
<Route to="/anywhere" render={() => <YourComponent {...yourProps} />} />
I use once component prop:
<Route to="/anywhere" component={() => <YourComponent {...yourProps} />} />
It is worked, but recomended to use render.
It discussed sometimes on github , you can see.
Edit to working example:
PrivateRoute = ({ component: Component, ...rest }) => (
<Route render={props => (
this.state.isAuthenticated
? <Component {...props} {...rest} />
: <Redirect to="/" />
)}/>
)
I emulate your example in my env, i placed rest object in <Component /> definition, its worked for me, try it and see what happens.
It works when I do this:
PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={() => (
this.state.isAuthenticated
? <Component {...this.props}/>
: <Redirect to="/" />
)}/>
)
<this.PrivateRoute path="/home" exact component={() => <Home portalId={this.state.portalId} />} />