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

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

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!

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

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.

React/Redux/React Router - Only call API when route is first visited or the filter is changed

Is there a way to only run props.fetchTransactions() under the following conditions:
When the route is first navigated to
If props.filter is changed
At the moment, it runs every time the route is navigated to because <TransactionListContainer /> is unmounted and re-mounted by react router. This creates an unnecessary call to the API as it just re-fetches the exact same result set that is already in my Redux store.
The only way I can think of doing this is by lifting the useEffect hook up into the root <App/> so it's outside of the router, but i'm unsure if that's a good way to go because:
It will call the API when <App/> is loaded, even if the user is viewing a different route
<App/> will quickly become cluttered if I need to repeat this for other containers
// App root
const App = () => {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={ () => <TransactionListContainer /> } />
...other routes...
</Switch>
</BrowserRouter>
);
}
// Container
const TransactionListContainer = (props) => {
useEffect(() => {
// This should only happen when the route is first visited or if props.filter has changed
props.fetchTransactions(props.filter.fields, props.filter.pagination);
},[props.filter]);
// The transactions are passed to this container
// by redux: props.transactions. I haven't included
// any code that I believe isn't relevant
// to the question.
}
You can provide a flag variable indicating if the transaction data was cached previously (is available in redux store).
const TransactionListContainer = (props) => {
useEffect(() => {
if(!props.isCached){
props.fetchTransactions(props.filter.fields, props.filter.pagination);
}
},[props.filter, props.isCached]);
}
function mapStateToProps(state) {
const isCached = state.transactions.length !== 0
return { isCached }
}
export default connect(mapStateToProps)(TransactionListContainer)

Context API bringing state up to higher component

I'm trying to get my nav bar to change depending on which page the app is on without having to create individual nav components for each page. I want the nav bar to change once the user goes to a new 'projects' page. The way I would do this is to conditionally render the nav bar elements depending on the url parameter. Once the user clicks to go to the project page, there will be a url parameter such as localhost/project/movie. However, the nav bar component does not have access to the match.params.projectId, only the project page component has access to it since it's within a Route component from react-router. Therefore, I want to make a global state that stores the url parameter so that I could use it within the navbar component to conditionally render the elements of that component.
Highest level App component
const App = () => {
return (
<div className="app">
<Header/>
<Switch>
<Route exact path="/" component={Home}/>
<Route exact path="/project/:projectId" component={ProjectOverview}/>
</Switch>
</div>
);
}
Lower level Project page component
const ProjectOverview = ({ match }) => {
useEffect(() => {
window.scrollTo(0, 650);
}, []);
let project = {};
if(match.params.projectId === 'movie'){
project = {
p: 'Browse your favorite Movies, TV shows, and actors. Search for specific movies, shows or actors by date, rating, region and other categories. Browse the latest and greatest films and to find information about its actors, crew, and reviews. Rate and favorite Movies, TV shows and actors while having access to them through a user account. Login / Authentication.',
img: 'http://www.technologies.com/images/cloud-summary.png',
gif: 'https://giphy.com/media/mUgG6FZuWN9V1/giphy.gif',
list: ['React', 'Redux', 'Hooks', 'TMDB API', 'Sass', 'React Router']
}
} else if(match.params.projectId === 'ecommerce'){
project = {
p: 'Something else',
img: 'http://www.technologies.com/images/cloud-summary.png',
gif: 'https://giphy.com/media/IgASZuWN9V1/giphy.gif',
list: ['React', 'Redux', 'Hooks', 'TMDB API', 'Sass', 'React Router']
}
}
return(
I know the provider has to wrap around the header(navbar) component within the app component but the state value that is needed (match.params.projectId) can only be found within the project page component. I'm wondering how I could get the value for the provider to be whatever the url parameter is within the project page component. I will try to further clarify my question if it seems too confusing for anyone. Thank you
I don't know if it is the best solution, but in an application where I had to change the nav's title depending on the route I did the following:
function App() {
const [title, setTitle] = useState('');
return <Router>
<Navbar title={title} />
<Switch>
<Route path="/product/:id" exact render={props => <ProductPage {...props} setTitle={setTitle} />} />
</Switch>
</Router>
}
And in the ProductComponent:
function ProductionComponet({setTitle, match}){
useEffect(()=>{
setTitle(`product ${match.params.id}!`)
},[])
}

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 />
}

Categories

Resources