React Router v6 not working with URL Slugs - javascript

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

Related

Uncaught Error: [Elements] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>

I was trying to integrate the strip API key but I am not able to come up with the solution in the new react-router-dom version
here is the full error
The above error occurred in the component:
at Routes (http://localhost:3000/static/js/bundle.js:81119:5)
at Router (http://localhost:3000/static/js/bundle.js:81052:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:79861:5)
at App (http://localhost:3000/main.79cc3231add2da1b35a8.hot-update.js:89:63)
at Provider (http://localhost:3000/static/js/bundle.js:74963:5)
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
here is my Appjs Protected route code:
import { Elements } from "#stripe/react-stripe-js";
import { loadStripe } from "#stripe/stripe-js";
useEffect(() => {
store.dispatch(loadUser());
getStripeApiKey();
}, []);
useEffect(() => {
const stripePromise= loadStripe(stripeApiKey);
}, [stripeApiKey]);
<Route element={<ProtectedRoute />}>
<Route path="/account" element={<Profile />} />
<Route path="/me/update" element={<ProfileEdit />} />
<Route path="/password/update" element={<UpdatePassword />} />
<Route path="/login/shipping" element={<Shipping />} />
<Route path="/order/confirm" element={<ConfirmOrder />} />
{stripeApiKey && (
<Elements stripe={stripePromise}>
<Route path="/order/payment" element={<Payment />} />
</Elements>
)}
</Route>
My protected route code
const ProtectedRoute = () => {
const { loading, isAuthenticated, user } = useSelector((state) => state.user);
const location = useLocation();
if (isAuthenticated == false) {
return <Navigate to="/account" state={{ from: location }} />;
}
return <Outlet />;
};
export default ProtectedRoute;
Issue
This issue is that you are rendering something other than a Route or React.Fragment inside the Routes component.
{stripeApiKey && (
<Elements stripe={stripePromise}>
<Route path="/order/payment" element={<Payment />} />
</Elements>
)}
The Elements component is neither and fails the invariant check.
Solution
Refactor this into a layout route component similar to the ProtectedRoute component.
Example:
import { Outlet, Navigate } from 'react-router-dom';
const StripeLayout = ({ stripeApiKey }) => {
return stripeApiKey
? <Outlet />
: <Navigate to="/" replace />
};
...
<Route element={<ProtectedRoute />}>
<Route path="/account" element={<Profile />} />
<Route path="/me/update" element={<ProfileEdit />} />
<Route path="/password/update" element={<UpdatePassword />} />
<Route path="/login/shipping" element={<Shipping />} />
<Route path="/order/confirm" element={<ConfirmOrder />} />
<Route element={<StripeLayout {...{ stripeApiKey }} />}>
<Route
path="/order/payment"
element={(
<Elements stripe={stripePromise}>
<Payment />
</Elements>
)}
/>
</Route>
</Route>
You are getting that error because of this line of code
{stripeApiKey && (
<Elements stripe={stripePromise}>
<Route path="/order/payment" element={<Payment />} />
</Elements>
)}
Only Route or React.Fragment are allowed to be children of the Routes component. But in your case if stripeApiKey turns out to be true , you are rendering Elements component which contradicts the new react router v6 rules
Like you asked in comments you want to wrap your payments method
<Route path="/order/payment" element={<Elements stripe={stripePromise}><Payment /></Elements} />

Uncaught Error: [RouteWrapper] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>

I'm using React Router v6 and am creating private routes for my application.
In file Route.js, I've the code
export default function RouteWrapper({
element: Element,
isPrivate,
...rest
}) {
const { signed, loading } = useContext(AuthContext);
if (loading) {
return <div></div>;
}
if (!signed && isPrivate) {
return <Navigate to="/" />;
}
if (signed && !isPrivate) {
return <Navigate to="/dashboard" />;
}
return <Route {...rest} render={(props) => <Element {...props} />} />;
}
And in file index.js I've written as:
return (
<Routes>
<Route path="/" element={SignIn} />
<Route path="/register" element={SignUp} />
<Route path="/dashboard" element={Dashboard} isPrivate />
<Route path="/profile" element={Profile} isPrivate />
<Route path="/customers" element={Customers} isPrivate />
<Route path="/new" element={New} isPrivate />
<Route path="/new/:id" element={New} isPrivate />
</Routes>
);
}
Is there something I'm missing?
Issue
RouteWrapper isn't a Route component, and fails an invariant check by react-router-dom.
RouteWrapper is directly rendering a Route component, which if the first invariant wasn't failed would trigger another invariant violation. Route components can only be rendered directly by the Routes component or another Route component in the case of building nested routing.
In short, in react-router-dom#6 custom route components are no longer supported. You should instead use wrapper components/layout routes to handle this use case.
Solution
Convert RouteWrapper to a wrapper component that renders an Outlet component for nested routed components to be rendered into.
Example:
import { Navigate, Outlet } from 'react-router-dom';
export default function RouteWrapper({ isPrivate }) {
const { signed, loading } = useContext(AuthContext);
if (loading) {
return <div></div>;
}
if (!signed && isPrivate) {
return <Navigate to="/" />;
}
if (signed && !isPrivate) {
return <Navigate to="/dashboard" />;
}
return <Outlet />; // <-- nested routes render here
}
Wrap the routes you want to protect with the RouteWrapper.
return (
<Routes>
<Route element={<RouteWrapper />}>
<Route path="/" element={<SignIn />} />
<Route path="/register" element={<SignUp />} />
</Route>
<Route element={<RouteWrapper isPrivate />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/customers" element={<Customers />} />
<Route path="/new" element={<New />} />
<Route path="/new/:id" element={<New />} />
</Route>
</Routes>
);
See Layout Routes for further details.
You should convert
<Routes>
<Route exact path="/" element={Dashboard} />
</Routes>
to
<Routes>
<Route exact path="/" element={<Dashboard/>} />
</Routes>
Also if you want to keep your UI in sync with the URL use like this.
<BrowserRouter>
<Routes>
<Route exact path="/" element={<Dashboard/>} />
</Routes>
</BrowserRouter>
Best.

Protected Routes and Nested Routes are not working together in react routing

My nested privateRoute is not working.
When I changed the nested routes in normal routing like /route everything works fine but as I switch to nested routes like /route/nested-route PrivateRoute component loses its functionality.
This is the main routing in my app component.
function App() {
return (
<Suspense fallback={<Loader />}>
<Switch>
<Route path="/" exact={true} component={Home} />
<Route path="/login">
<ProvideAuth>
<Login />
</ProvideAuth>
</Route>
<Route path="/signup">
<SignUpProvider>
<SignUp />
</SignUpProvider>
</Route>
<PrivateRoute path="/home" component={UserHome} />
<PrivateRoute path="/profile" component={UserProfile} />
<Route path="*" component={Error} />
</Switch>
</Suspense>
);
}
PrivateRoute Component works fine in this main routing.
This is code of nested and protected route.
const UserHome = () => {
const { url } = useRouteMatch();
return (
<Wrapper>
<Header />
<Suspense fallback={<Loader />}>
<Switch>
<PrivateRoute exact path={`${url}/feed`} component={Feed} />
<PrivateRoute path={`${url}/search`} />
<PrivateRoute path={`${url}/addUser`} />
<PrivateRoute path={`${url}/notification`} />
<PrivateRoute path={`${url}/profile`} component={UserProfile} />
</Switch>
</Suspense>
<BottomNavbar />
</Wrapper>
);
};
PrivateRoute is not working in this nested routing. The redirect route is not firing when the user logs out.
Code for PrivateRoute Component.
const PrivateRoute = ({ children, ...rest }) => {
const cookie = document.cookie;
return (
<Route
{...rest}
render={() => {
return cookie ? children : <Redirect to="/login" />;
}}
/>
);
};
I think the issue here is that the cookie value is computed once, initially, when the PrivateRoute component is mounted and rendered. It's a stale value from the enclosure.
Move the cookie into the returned render function so it's accessed when the path is matched.
const PrivateRoute = ({ children, ...rest }) => (
<Route
{...rest}
render={() => document.cookie ? children : <Redirect to="/login" />}
/>
);

React-router: testing inside the `render` prop with enzyme

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")
})

Can't pass a function to a component via <Route />

setErm is a function and is undefined in Erm component. Although the App component receives it. If I pass anything like something='foo' the ERM component get's it but not setErm={props.setErm}
const App = props => {
console.log("props in App", props);
return (
<Router>
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route
path="/erm"
render={props => <Erm {...props} setErm={props.setErm} />}
/>
<Route exact path="/:weekId" component={Week} />
<Route exact path="/:weekId/:dayId" component={Day} />
</Switch>
</div>
</Router>
);
};
The problem in your code is that in your Erm component props here refer to the Route component props not App props so you can change your code to use the props of App to something like this:
const App = props => {
console.log('props in App', props);
const appProps = props; // add this line to refer to App props
return (
<Router>
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route
path="/erm"
render={props => <Erm {...props} setErm={appProps.setErm} />}
/>
<Route exact path="/:weekId" component={Week} />
<Route exact path="/:weekId/:dayId" component={Day} />
</Switch>
</div>
</Router>
);
};
Or you can use destructuring:
const App = ({ setErm }) => {
console.log('props in App', props);
return (
<Router>
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route
path="/erm"
render={props => <Erm {...props} setErm={setErm} />}
/>
<Route exact path="/:weekId" component={Week} />
<Route exact path="/:weekId/:dayId" component={Day} />
</Switch>
</div>
</Router>
);
};
As you can see you now able to use the top props of App component

Categories

Resources