i'm trying to run snippet code as below:
class App extends Component {
render() {
return (
<Router>
<div className="App">
<Navbar></Navbar>
<Routes>
<Route path="/" element={<Home></Home>} />
<Route path="/about" element={<About></About>} />
<Route path="/contact" element={<Contact></Contact>} />
<Route path="/challenges/*" element={<Challenges></Challenges>} />
<Route path="*" element={<NotFound />} />
</Routes>
</div>
</Router>
);
}
}
let a = 0;
const Challenges = (props) => {
console.log(++a);
console.log(window.location.pathname);
const path = props.location.pathname;
const slug = path.split("/").slice(path.split("/").length - 1)[0];
const challenge = challenges.find((challenge) => challenge.slug === slug);
return (
<div>
<h1>30 Days Of React Challenge</h1>
<ul>
{challenges.map(({ name, slug }) => (
<li key={name}>
<NavLink to={`/challenges/${slug}`}>{name}</NavLink>
</li>
))}
</ul>
<Routes>
<Route
exact
path="/challenges"
element={<h1>Choose any of the challenges</h1>}
/>
<Route path={path} element={<Challenge challenge={challenge} />} />
</Routes>
</div>
);
};
i want to get the path at Challenges route component but it throw an error:
Cannot read properties of undefined (reading 'pathname')
i try to log variable "a" and "window.location" to test and it log two times like this:
1
/challenges
2
/challenges
My question is why i can't take value of props.location.pathname and why its run two times and the second time it throw an error why not at fisrt time.
Thank for helping me! Hope you have a good day.
Issue(s)
react-router-dom v6 Route components rendered via the element prop don't receive route props.
Route children components must use react hooks to access the route context, i.e. useParams, useLocation, useNavigate, etc... and therefore must be function components.
The console.log calls are in the function body so these are unintentional side-effects. This is likely why they are called twice, assuming the app is being rendered into a React.StrictMode component.
Solution
Challenges should use the uselocation hook to access the pathname. Move the console logs into an useEffect hook so they are called once per render to the DOM.
const Challenges = (props) => {
const { pathname } = useLocation();
useEffect(() => {
console.log(++a);
console.log(pathname);
});
const path = pathname;
const slug = path.split("/").slice(path.split("/").length - 1)[0];
const challenge = challenges.find((challenge) => challenge.slug === slug);
return (
<div>
<h1>30 Days Of React Challenge</h1>
<ul>
{challenges.map(({ name, slug }) => (
<li key={name}>
<NavLink to={`/challenges/${slug}`}>{name}</NavLink>
</li>
))}
</ul>
<Routes>
<Route
path="/challenges"
element={<h1>Choose any of the challenges</h1>}
/>
<Route path={path} element={<Challenge challenge={challenge} />} />
</Routes>
</div>
);
};
v6 api-reference
Related
In My app I have my routes defined, as per below:
<BrowserRouter>
<Header />
<div className="App">
<Switch>
<Route exact path="/">
<Redirect to="/home" />
</Route>
<Route exact path={["/home", "/"]} component={Home} />
<Route path="/account/:id" render={(props: RouteComponentProps<any>) => <Account {...props} />} />
<Route component={NotFound} />
</Switch>
</div>
</BrowserRouter>
What I want to know is, this can be tricky, If I wanted my route to have a prefix from my context i.e variable how would I do this, but the twist is the variable comes from an api response?
so what if i wanted the route /contextVariable/home but contextVariable is from an api response and is stored in a context value, I know how I would bring that variable into the component but how would the routes handle it i.e from not being /undefined/home as in the response would need to finish before being inserted into the route?
Any idea's?
I had once made a project that had similar requirement. In that, instead of declaring dynamic routes, I fetched a routes array from the state which was an object array with component, path, and few other parameters. By default I added the initial landing page and not found page:
const [routes, setRoutes] = React.useState([
{
component: HomeComponent,
path: '/',
},
{
component: NoMatchPage,
path: '*',
}
])
And then I had the request in a useEffect block which would update this state like so:
React.useEffect(()=>{
// call api()
const oldRoutes = routes;
const noMatchPage = oldRoutes.pop();
const newRoutes = [...oldRoutes,
responseFromApi.map(
routeItem =>
({
component: ComponentName,
path: routeItem.path
})
), noMatchPage]
setRoutes(newRoutes)
},[])
Edit 1 : Cause I'm forgetful
Sorry, I forgot the main part, here's how the Route rendering would be:
<Switch>
{
routes.map(routeItem =>
<Route path={routeItem.path} component={routeItem.component} />
)
}
</Switch>
Also if you want to avoid the extra code in useEffect, you could simply do this:
React.useEffect(()=>{
// call api()
setRoutes(responseFromApi.map(
routeItem =>
({
component: ComponentName,
path: routeItem.path
})
))
},[])
and then
<Switch>
<Route exact path={["/home", "/"]} component={Home} />
{
routes.map(routeItem =>
<Route path={routeItem.path} component={routeItem.component} />
)
}
<Route component={NotFound} />
</Switch>
Edit 2 : Cause I'm ignorant
In the case where the user enters the URL directly and Switch is unable to identify the Route and therefore loads the NotFoundPage, you could do the following:
Set a condition when you start loading your paths, inside your useEffect block:
const [loading, setLoading] = React.useState(false);
React.useEffect(() =>
{
setLoading(true);
// load paths
setLoading(false);
}, [])
While the fetch is in progress, show a Loader to the user:
return
(
<>
{
loading ?
<LoaderComponent /> :
<Switch>
// same as before
</Switch>
}
</>
)
Preferable show something for the user to read, so they don't get irritated, cause patience is a thing of the past. Hope this helps!
If you want to do this with a React Context then this is the pattern I'd suggest. Create a React Context that holds the API logic to fetch a "base path" and expose that out to consumers. Consumers will take the provided "base path" value and prepend it to all link targets and route paths.
Example:
BasePathProvider
import { createContext, useContext } from "react";
const BasePath = createContext({
basepath: ""
});
const BasePathProvider = ({ children }) => {
... logic to fetch basepath ...
return (
<BasePath.Provider value={{ basepath }}>
{children}
</BasePath.Provider>
);
};
const useBasePath = () => useContext(BasePath);
Header
const Header = () => {
const { basepath } = useBasePath();
return (
...
<Link to={`${basepath}/`}>Home</Link>
<Link to={`${basepath}/account/${/* some id value */}`}>
Account
</Link>
...
);
};
App
function App() {
return (
<div className="App">
<Header />
<BasePath.Consumer>
{({ basepath }) => (
<Switch>
<Redirect from={`${basepath}/`} exact to={`${basepath}/home`} />
<Route path={`${basepath}/home`} component={Home} />
<Route path={`${basepath}/account/:id`} component={Account} />
<Route component={NotFound} />
</Switch>
)}
</BasePath.Consumer>
</div>
);
}
index.js
import { BrowserRouter as Router } from "react-router-dom";
import BasePathProvider from "../path/to/BasePathProvider";
...
<Router>
<BasePathProvider>
<App />
</BasePathProvider>
</Router>
Note: You might also want/need to implement a "loading" state to conditionally render the BasePathProvider component's children until the basepath value has been fetched.
Register and login components need to be added to the container class. I followed a react course on Udemy. They are using an older version of react-router-dom. For this i used v6 react router dom and made changes, but this one I don't know what to do. This code is new to me, please assist me
function App() {
return (
<Router>
<Fragment>
<Navbar />
<Routes>
<Route exact path='/' element={<Landing />} />
<section className='container'>
<Route exact path='/register' element={Register} />
<Route exact path='/login' element={Login} />
</section>
</Routes>
</Fragment>
</Router>
);
}
error in console
[section] is not a <Route> component. All component children of <Routes> must be a <Route>
As the error is informing you, only Route or React.Fragment are valid children of the Routes component.
If you want to render several routed components into a specific layout, i.e. common UI/layout, then create a layout route for them to be nested into.
Make sure to also render Register and Login as JSX!
Example:
import { Outlet } from 'react-router-dom';
const SectionLayout = () => (
<section className='container'>
<Outlet /> // <-- nested routes render content here
</section>
);
export default SectionLayout;
...
import SectionLayout from '../path/to/SectionLayout';
...
<Routes>
<Route path='/' element={<Landing />} />
<Route element={<SectionLayout />}>
<Route path='/register' element={<Register />} />
<Route path='/login' element={<Login />} />
</Route>
</Routes>
For more information see:
Layout Routes
I think the error is quite descriptive in itself. That the children of <Routes /> can only be <Route /> and <section /> doesn't satisfy that.
If you need both Register and Login components to have a wrapper of section with .container class.
We can achieve it through different approaches, here are a few of them.
For eg.:
/**
* 1. Putting them inside the components itself
*/
const Register = () => {
return (
<section className="container">
// your other codes here
</section>
)
}
const Login = () => {
return (
<section className="container">
// your other codes here
</section>
)
}
/**
* 2. As a reusable Layout wrapper or Higher Order Component or
* Useful when you have many shared contents and styling
*/
const Container = (props) => {
return (
<section className="container">
// shared contents
{props.children}
// other shared contents
</section>
);
}
const Register = () => {
return (
<Container>
// your other codes here
</Container>
)
}
const Login = () => {
return (
<Container>
// your other codes here
</Container>
)
}
Hope that helps.
Currently using react-router-dom 6.1.1 and I'm working with a private route.
Inside this private route I usually had other routes (so that I can keep my Sidebar on them).
My code looks like this
// App.tsx
const RequireAuth: React.FC<PrivateRouteProps> = ({ children, redirectTo }) => {
const isAuthenticated = Auth.isLogedIn()
return isAuthenticated ? children : <Navigate to={redirectTo} />
}
const Page = () => {
return (
<div className={css.host}>
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/"
element={
<RequireAuth redirectTo="/login">
<Home />
</RequireAuth>
}
/>
</Routes>
</BrowserRouter>
</div>
)
}
// Home/index.tsx
const Home = () => {
return (
<div className={css.host}>
<Sidebar sections={sidebarOptions(t)} />
<Routes>
{routes.map(({ breadCrumbtitle, link, component }, index) => (
<Route path={link} key={index}>
{component ? component : <p>[{breadCrumbtitle}] To be done</p>}
</Route>
))}
</Routes>
</div>
)
}
So... This setup worked with v5 but it seems to be something that doesn't really work with v6.
What can I do if I still want to keep the Sidebar for all the routes once I'm logged in?
I ended up finding the solution to my issue.
Doing what Drew Reese suggested only worked to a certain point since I was being led to a route that, for react router, didn't exist.
For it to work I add to do
// App.tsx
const RequireAuth: React.FC<PrivateRouteProps> = ({ children, redirectTo }) => {
const isAuthenticated = Auth.isLogedIn()
return isAuthenticated ? children : <Navigate to={redirectTo} />
}
const Page = () => {
return (
<div className={css.host}>
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path=""
element={
<RequireAuth redirectTo="/login">
<Home />
</RequireAuth>
}
>
{routes.map(({ breadCrumbtitle, link, component }, index) => {
return <Route path={link} key={index} element={component}></Route>
})}
</Route>
</Routes>
</BrowserRouter>
</div>
)
}
// Home/index.tsx
const Home = () => {
return (
<div className={css.host}>
<Sidebar sections={sidebarOptions(t)} />
<div className={css.contentContainer}>
<Outlet />
</div>
</div>
)
}
Using the Outlet seemed to be essential, don't know if it's something new on react router v6 but seemed to do the trick!
As far as I can tell the only issue is with the routes mapping, the Route components have invalid children, i.e. you are rendering another Route or React.Fragment as a child.
Move this up to the element prop of the mapped Route components.
const Home = () => {
return (
<div className={css.host}>
<Sidebar sections={sidebarOptions(t)} />
<Routes>
{routes.map(({ breadCrumbtitle, link, component }, index) => (
<Route
path={link}
key={index}
element={component || <p>[{breadCrumbtitle}] To be done</p>}
/>
))}
</Routes>
</div>
);
};
I have a simple menu and I have a page with a random context only for tests.
Here my index.js
const Route = ReactRouterDOM.Route;
const Link = ReactRouterDOM.Link;
const HashRouter = ReactRouterDOM.HashRouter;
const Routes = ReactRouterDOM.Routes;
// create context
const UserContext = React.createContext(null);
function Spa() {
return (
<HashRouter>
<div>
<h1>Routing - Hello World</h1>
<Link to="/">Home</Link> --
<Link to="/about/">About</Link> --
<Link to="/products">Products</Link>
<hr />
<UserContext.Provider value={{ users: ["peter"] }}>
<Routes>
<Route path="/" exact element={ <Home />} />
<Route path="/about/" element={ <About />} />
<Route path="/products/" element={ <Products />} />
</Routes>
</UserContext.Provider>
</div>
</HashRouter>
);
}
ReactDOM.render(
<Spa/>,
document.getElementById('root')
);
and this is my products.js
const ctx = React.useContext(UserContext);
ctx.users.push(Math.random().toString(36).substr(2, 5));
return (
<div>
<h3>Products Component</h3>
<p>List of the the product we make</p>
{JSON.stringify(ctx.users)}
</div>
);
}
my problem is that when I do click on the products menu the fist time the random works, but if I do again doesn't works, I have to change of link, for example login and return again to products for random works.
Someone knows what could be the problem?
I am using
React-dom-16.8
History-5
React-Router-dom-6
Issues
Linking to a route with the same path and state/etc is effectively a non-op. It won't trigger a navigation and it won't trigger the routed component to rerender.
ctx.users.push(Math.random().toString(36).substr(2, 5)); is an unintentional side-effect and also won't trigger a rerender. You only see the mutation after navigating away and back to "/products".
Solution
users should reside in some react state so when it's updated it can trigger React components to rerender. Pass the users state and the state updater (or some callback to update state) as the UserContext value.
Pass some "random" state value in the link such that it will trigger the navigation action. Check for state in an useEffect to help trigger the effect to update the users state.
Suggestions
Spa
const Spa = () => {
const [users, setUsers] = useState(["peter"]);
return (
<Router>
<div>
<h1>Routing - Hello World</h1>
<Link to="/">Home</Link> --
<Link to="/about/">About</Link> --
<Link
to="/products"
state={{ value: Math.random() }} // <-- random state to trigger navigation
>
Products
</Link>
<hr />
<UserContext.Provider value={{ users, setUsers }}>
<Routes>
<Route path="/" exact element={<Home />} />
<Route path="/about/" element={<About />} />
<Route path="/products/" element={<Products />} />
</Routes>
</UserContext.Provider>
</div>
</Router>
);
};
Products
const Products = () => {
const { state } = useLocation();
const { users, setUsers } = useContext(UserContext);
// "Listen" for updates to route state to trigger effect
useEffect(() => {
setUsers((users) => [...users, Math.random().toString(36).substr(2, 5)]);
}, [state, setUsers]);
return (
<div>
<h3>Products Component</h3>
<p>List of the the product we make</p>
{JSON.stringify(users)}
</div>
);
};
Use BrowserRouter instead of HashRouter
I can't find the way how to send object from one React Route Component to another.
For example I have container router like this:
const App = () => (
<Router>
<div>
<Route exact path="/" component={Home} />
<Route path="/sections/:id"
component={Section} />
</div>
</Router>
)
with Home component like this:
const Home = () => {
const Sections = tilesData.map( (section, index) => (
<div>
<img src={section.img} height="200" /><br/>
<Link to={`/sections/'${section.id}`} >Details for {section.author}</Link>
<hr/>
</div>
))
return(
<div>
{Sections}
</div>
)
}
and I don't understand =\ how to pass selected object when next route clicked with <Link>. Here is example component:
const Section = (props) => {
return(
<div>
Section {props.title}
<img src={props.img} />
</div>
)
}
Here is code example: https://codesandbox.io/s/Mv037AE3
In react-router v4, we usually do the following to pass in a ProductPage component:
<Route path='/' component={ProductPage} />
When you want to use props, you can prepare a function that returns your component with the desired props. Here I'm preparing to pass in a toggleSidebarOn prop:
const MyProductPage = (props) => {
return (
<ProductPage
toggleSidebarOn={this.toggleSidebarOn.bind(this)}
{...props}
/>
);
}
Then you use the render prop of the <Route /> instead of its component prop. Do not use both and do not use the component prop as this leads to undesired remounting and might break your app (e.g. for me CSS transitions for the sidebar animation stopped working properly).
Using the render prop for the MyProductPage we can then pass in our toggleSidebarOn prop to its target route:
<Router>
<div>
<Switch>
<Route exact path="/products" render={MyProductPage} />
<Route exact path="/perspectives" component={PerspectivePage}/>
<Route component={NotFound}/>
</Switch>
</div>
</Router>
Hope this helps!