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")
})
Related
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>
</>
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" />}
/>
);
I created this react component which has some nested routes. The problem is that the nested page component is either not rendering at all despite the URL changes or just returns a blank page.
I tried the suggestions from other posts like adding/removing exact in the parent route, but it's still not working.
Below are my codes:
// the parent component
<div className="App">
<Router>
<Navbar />
<Switch>
<Route exact path="/" render={() => <MyPage accessToken={accessToken} />}/>
<Route path="/editing/:playlistId" render={(props) =>
<EditingPlaylist {...props} accessToken={accessToken} />} />
</Switch>
</Router>
</div>
//the child component EditingPlaylist
render() {
const { pathname } = this.props.location;
return (
<div className="editing">
<Switch>
<Route exact path={pathname} render={() =>
<Searchbar accessToken={this.props.accessToken} />} />
<Route path={`${pathname}/test`} component={<p>Test</p>} />
<Route path={`${pathname}/album/:albumId`} render={
(props) => <AlbumPage {...props} accessToken={this.state.accessToken} />} />
<Route path={`${pathname}/artist/:artistId`} render={
(props) => <ArtistProfile {...props} accessToken={this.state.accessToken} />} />
</Switch>
</div>)
}
export default withRouter(EditingPlaylist);
Use url and path:
const { url, path } = this.props.match
to defined nested routes.
So, in nested routes:
<Switch>
<Route
exact
path={path} // HERE
render={() => <Searchbar accessToken={this.props.accessToken} />}
/>
...
</Switch>
There is a difference between url and path:
url: It is what is visible in the browser. e.g. /editing/123. This should be used in when redirecting via Link or history.push
path: It is what matched by the Route. e.g. /editing/:playlistId. This should be used when defining (nested) paths using Route.
From docs:
path - (string) The path pattern used to match. Useful for building nested <Route>s
url - (string) The matched portion of the URL. Useful for building nested <Link>s
I want to test if the routes exist in the component. App should render the specific component on the specific path.
App.js
return (
<Router>
<Header jsTrendingTopics={this.props.data} />
<Switch>
<Route exact path="/" render={() => <Dashboard jsTrendingTopics={this.props.data} />} />
<Route exact
path="/language/:languagename"
render={(props) => this.handleTopic(props)
}
/>
</Switch>
</Router>
)
}
I expect the test to return the component that should render on that specific route.
I think MemoryRouter with initialEntries={['/]} is what you are looking for.
import Dashboard from ...
jest.mock('path to Dashboard component');
Dashboard.mockImplementationOnce(() => <div>Dashboard</div>);
render(
<MemoryRouter initialEntries={['/']}>
<App/>
</MemoryRouter>
);
expect(screen.getByText("Dashboard").toBeInTheDocument()
You can read this document for more examples
https://javascript.plainenglish.io/testing-react-router-with-jest-bc13d367bad
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