React Router: strangely matched Route path props passed to its wrapper - javascript

According to React Router Doc, you can spread routeProps to make them available to your rendered Component, code below works as I expected: users will see Test if entering /test, and 404 for /test2.
If I remove {...rest}, I expected users will always see Test because the Route without a path should always match. But it still behaves the same. Can anyone tell me what's going on here?
function RouteWrapper({ children, ...rest }) {
return (
<Route
{...rest}
render={() => children}
/>
);
}
export default function App() {
return (
<Router>
<Switch>
<RouteWrapper path="/test">Test</RouteWrapper>
<Route>404</Route>
</Switch>
</Router>
);
}
CodesandBox Demo

Interesting.
It appears to be because of how the Switch component is implemented. The following snippet from the code is of particular interest:
let element, match;
React.Children.forEach(this.props.children, child => {
if (match == null && React.isValidElement(child)) {
element = child;
const path = child.props.path || child.props.from;
match = path
? matchPath(location.pathname, { ...child.props, path })
: context.match;
}
});
As you can see, the component simily looks through its children components and finds the path prop. It assumes you've passed a proper path prop to each Route component.
So it doesn't matter that you removed the path from your Route, because the Switch will take the path from your RouteWrapper component.

Related

react.js & django, useParams unable to navigate to the page

I am current building a react app with django, I am trying to navigate from the HomePage to the DataPage with corrsponding id. However, it return Page not found error. I am using react-router-dom v6.
Using the URLconf defined in robot.urls, Django tried these URL patterns, in this order:
admin/
api/
api-auth/
homepage
homepage/data
The current path, homepage/data/54, didn’t match any of these.
Here is my App.js
export default class App extends Component {
constructor(props) {
super(props);
}
renderHomePage() {
return (
<HomePage />
);
}
render() {
return (
<BrowserRouter>
<Routes>
<Route exact path='homepage/' element={this.renderHomePage()} />
<Route path='homepage/data/:id' element={<DataPage />} />
</Routes>
</BrowserRouter>
)
}
}
const appDiv = document.getElementById("app");
render(<App />, appDiv);
And I want to navigate to the DataPage below:
const EmtpyGrid = theme => ({
Grid: { ... }
});
function DataPage(props) {
const { classes } = props;
const { id } = useParams();
return (
<div>
... some material ui components ...
<div/>
)
};
DataPage.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(EmtpyGrid)(DataPage);
I was thinking whether I need configure my url.py in frontend as well, and I need to define a designated value for {id} returned from the materialui component first. Perhaps I need a button or <Link><Link/> for the navigation instead of just simply typing in the url? Still, no luck after many attempts. I am so confused right now.
If you know what is wrong with my code, please feel free to leave a comment. Thanks
After many tries and checking documents, I don't really need to configure my urls.py. I only things that I am missing is to put a parameter in my naviagate() from onRowClick={((rowData, event) => {navigate('data/');})} to onRowClick={((rowData, event) => {let id = event.sample_ID; navigate('data/' + id)})}; I was thinking the problem too complicated.
Thanks you guys for sharing!

Cannot code nested routes with react-router-dom 5.2.0?

I'm trying to access to sub components with nested routing in React using react-router-dom 5.2.0.
Here you can find a CodeSandbox link of the project: https://codesandbox.io/s/nested-routes-8c7wq?file=/src/App.js
First, let me show you the tree structure of my pages.
Home
About
Organisations
MainContent
SecondContent
Routes for main pages (Home, About, Organizations) are in src/routes folder.
So their link look like:
https://localhost:3000/ (for Home)
https://localhost:3000/about (for About)
https://localhost:3000/organizations (for Organizations)
At this point, everything is okay for me. The problem is with the nesting part..
On Organizations page, I have to be able to switch between MainContent and SecondContent.
So links into that page must look like this:
https://8c7wq.csb.app/organizations/maincontent
https://8c7wq.csb.app/organizations/secondcontent
But I cannot print any of those contents...
If you want to build nested paths you should use the path and url from the current route match and build the nested routes and links from there.
src/pages/Orgainizations
import { Switch, Route, useRouteMatch } from "react-router-dom";
const Organizations = (props) => {
const { path, url } = useRouteMatch(); // <-- get current path and url
const [value, setValue] = useState("");
const menuLinks = {
data: [
{
id: 0, // <-- fix React key issue in NavBar
title: "StackOverflow",
to: `${url}/maincontent` // <-- build nested link
},
{
id: 1, // <-- fix React key issue in NavBar
title: "BitBucket",
to: `${url}/secondcontent` // <-- build nested link
}
]
};
return (
<div>
<NavBar changeValue={(value) => setValue(value)} menuLinks={menuLinks} />
<div>
<Switch>
<Route
render={() => <MainContent title={value} />} // *
exact
path={`${path}/maincontent`} // <-- build nested path
strict
/>
<Route
render={() => <SecondContent title={value} />} // *
exact
path={`${path}/secondcontent`} // <-- build nested path
strict
/>
</Switch>
</div>
</div>
);
};
* Notice I converted to rendering route components via the render prop as opposed to the component prop. When you use anonymous functions to render a component it is created each render cycle, i.e. it will remount the component each time. The render prop does not do this. See render methods for more information.
NavBar
Fix the to prop value to render the link correctly, remove the leading "/" from the path and render the direct link.to value.
Change
to={`/${props.link.to}`}
to
to={props.link.to}
Demo

Dynamically render a react component with react routing, based on value given in url

Basically what I want to do is create a react portfolio project that contains and showcases all of my react projects. But I don't know how to render a project based on a url value.
What I mean is,
<Route path='/projects/:projectName' component={Project}></Route>
I want to render a component based on the :projectName vakue.
Or maybe create a Project component that just renders the given project based on the url value.
Is that even possible? I know I can use match to get the :projectName value, but how could I use it to render a component?
There are few approaches
1. As mentioned above to let project component decide what should be rendered based on match.params
const routes = {
'my-route1': <MyComponent1 />,
'my-route2': <MyComponent2 />
}
const Project = props => {
const { projectName } = props.match.params
return routes[projectName] || <DefaultComponent />
}
You may define your own routes components who will decide which component to Render based on state. It is helpful when you need to create master pages or templates and do not want any dependencies on match inside other components.
const PrivateRoute = ({ component: Component, ...rest }) => {
const func = props => (!!rest.isUserAllowedToNavigate()
? <Component {...props} />
: (
<Redirect to={
{
pathname: '/login',
search: props.location.pathname !== '/' && queryStringComposer({
redirect_from: props.location.pathname || getQueryStringParam('redirect_from')
})
}
}
/>
)
)
return (<Route {...rest} render={func} />)
}
/* Connecting to redux */
const PrivateRouteConnected = connect(mapStateToProps, mapDispatchToProps)(PrivateRoute)
/* Using as normal routes */
<PrivateRouteConnected exact path="/dashboard" component={Dashboard} />
your Project component can handle the logic to render a different component based on the URL param. For example:
const Project = props => {
const { projectName } = props.match.params
if (projectName === project1) {
return <ProjectOne addProps={addProps} />
}
if (projectName === project2) {
return <ProjectTwo />
}
return <DefaultProject />
}

How to access react router match object from mapstatetoprops

I was wondering how I would access the react-router match object for its params from mapStateToProps or any such selector. I wanted to build something out of the params and pass that down as props to the presentational component within the selector. I have a component that accepts a built prop and I'm hoping to pass it some value that's derived from the react router params. I'd preferably would like to not have to pass params down as a prop.
mapStateToProps takes second parameter ownProps. Once your component is initialized by react-router you gets match prop among your others.
<Route path="/details/:id" component={DetailsPage}
then
function DetailsPageComp(props) {
...
}
function mapStateToProps(state, { match: { params: {id} } }) {
return {
obj: someSelectorFunction(state, id);
};
}
export default connect(mapStateToProps)(DetailsPageComp);
Sure if you use <Route render={} version you need to pass match down:
<Route render={({ match }) => <DetailsPageComp match={match} />} path="..." />

React router v4 - Rendering two components on same route

I have these routes
<Route exact path={`/admin/caters/:id`} component={Cater} />
<Route exact path={'/admin/caters/create'} component={CreateCater} />
When I navigate to the first route I get a cater with a given ID. And the Cater component is rendered
When I navigate to the second route, the CreateCater component is rendered on the page, but I noticed that some redux actions that are used in the Cater component are being run. So both component are somehow being rendered - but I can't figure out why.
Here are the components:
Cater:
class Cater extends Component {
async componentDidMount() {
console.log('Cater component did mount')
const { match: { params: { id }}} = this.props
this.props.get(id)
}
render() {
const { cater } = this.props
if(!cater) {
return null
}
else {
return (
<div>
... component data ...
</div>
)
}
}
}
const mapStateToProps = (state, props) => {
const { match: { params: { id }}} = props
return {
cater: caterSelectors.get(state, id)
}
}
const mapDispatchToProps = (dispatch, props) => {
return {
get: (id) => dispatch(caterActions.get(id))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Cater)
CreateCater:
export default class CreateCaterPage extends Component {
render() {
return (
<React.Fragment>
<Breadcrumbs />
<CaterForm />
</React.Fragment>
)
}
}
When I go to /admin/caters/create' I can see the console.log in the componenDidMount() lifecycle method inside the Cater component.
I cant figure out what I am doing wrong :(
/create matches /:id, so it makes sense that this route matches. I recommend forcing :id to look for numeric only:
<Route exact path={`/admin/caters/:id(\\d+)`} component={Cater} />
<Route exact path={'/admin/caters/create'} component={CreateCater} />
Likewise, you can follow #jabsatz's recommendation, use a switch, and have it match the first route that matches. In this case, you would need to ensure that the /admin/caters/create route is the first <Route /> element matched.
The problem is that :id is matching with create (so, it thinks "see cater with id create"). The way to solve this is to put the wildcard matching route last, and wrapping all the <Routes/> with a <Switch/>, so it only renders the first hit.
Check out the docs if you have any more questions: https://reacttraining.com/react-router/core/api/Switch

Categories

Resources