how to use an index route with useRoutes in React Router 6 - react-router

<Route path="/exercises" element={<Layout />}>
<Route index element={<List />} />
<Route path=":id" element={<Exercise />} />
</Route>
How would I rewrite this as JS objects for useRoutes() including the index route?
{
path: "/exercises",
element: <Layout />,
children : [
{index? , element: <List />},
{path: ":id", element: <Exercise /> }
]
}
I'm not sure what to do with index...

The useRoutes hook takes an array of RouteObject objects
/**
* A route object represents a logical route, with (optionally) its child
* routes organized in a tree-like structure.
*/
export interface RouteObject {
caseSensitive?: boolean;
children?: RouteObject[];
element?: React.ReactNode;
index?: boolean;
path?: string;
}
You should be able to specify what is an index route using the index property.
{
path: "/exercises",
element: <Layout />,
children: [
{ index: true, element: <List /> },
{ path: ":id", element: <Exercise /> }
],
}

This seems to work:
{
path: "/exercises",
element: <Layout />,
children : [
{path: "" , element: <List />},
{path: ":id", element: <Exercise /> }
]
}
When the path is /exercises Layout is Rendered and List is rendered in the Outlet in Layout. when the path is /exercises/4 (for example) the Exercise component is rendered in Outlet in Layout.

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>

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 - 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.

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 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.