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
Related
The following React routes code probably works in React Router v5, but gives the following error in React Router v6
Error: [Player] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
Is it possible to update the Routes/Route code so that it works in React Router v6?
function App() {
// Some stuff here...
const { players, offlinePlayers } = usePlayers();
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/players">
{players.map((player) => {
return (
<Route exact key={player.name} path={`/players/${player.name}`}>
<Player player={player} />
</Route>
);
})}
</Route>
</Routes>
</BrowserRouter>
</ThemeProvider>
)
}
The Player component should be rendered by a Route component on the element prop, not as a child of the Route.
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/players">
{players.map((player) => (
<Route
key={player.name}
path={`/players/${player.name}`}
element={<Player player={player} />}
/>
))}
</Route>
</Routes>
</BrowserRouter>
You should map Routes in their parent route.
Like:
<Route path="/players">
{players.map((player) => (
<Route exact key={player.name} path={`/players/${player.name}`}>
<Player player={player} />
</Route>
);
)}
</Route>
But if you want to render dynamic player then dont use the above code for that purpose because its not best approach if you are using dynamic player.name. In your code you are creating each route for every player.
So, use the following code:
<Route path="/players">
<Route exact path={":playerName"} element={<Player/>} />
</Route>
And in Player component, extract playerName from params like:
let { playerName } = useParams();
To hide the navbar on the home component I am doing the following
const NavbarComponent = (props) => {
console.log(props);
if (props.match.path === '/') {
return null;
} else
return (
it works fine, I need to have access to the router so I can send people to locations dependant on the props object , is there a better way to do it such that I have all router logic in the same place?
this is the current state of my router
return (
<div>
<Router>
<Route component={Navbar} />
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/api/:city/electronics" component={Electronics} />
<Route exact path="/api/:city/labour" component={Labour} />
<Route exact path="/api/posts/item/:id" component={ItemDetails} />
<Route exact path="/create/:city/:category" component={CreatePost} />
</Switch>
</Router>
</div>
);
thanks for your time.
I'm not sure I understand why your NavBar component is in it's own Route. Any components contained within the Router have access to the entire Router api, including Link - they do not need to be a Route to do so.
I would suggest wrapping all the Routes that include the NavBar with that component. The Routes will then be displayed as children of the Navbar component.
Here is a simplified example:
// App.js
return (
<div>
<Router>
<Switch>
<Route exact path="/" component={Home} />
<NavBar>
<Route exact path="/electronics" component={Electronics} />
<Route exact path="/labour" component={Labour} />
</NavBar>
</Switch>
</Router>
</div>
);
//NavBar.js
return (
<>
<div>
<Link to="/electronics">Electronics</Link>
<Link to="/labour">Labour</Link>
</div>
<div>{props.children}</div>
</>
);
codesandbox
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")
})
Sometime I was sawing the well known warning, browser.js:49 Warning: [react-router] You cannot change <Router routes>; it will be ignored and I found two trend issues that friends discussed about this issue and the solution is const routes components and putting them inside Router component.
https://github.com/ReactTraining/react-router/issues/2704
https://github.com/reactjs/react-router-redux/issues/179
Just like below:
you will see warning with this code:
class Root extends Component {
render() {
return (
<Router history={browserHistory} createElement={this.createElement}>
<Route component={App}>
<Route path="/" component={MainPage}/>
<Route path="/page2" component={Page2}/>
<Route path="/settings" component={SettingsPage}/>
</Route>
</Router>
)
}
}
but you won't see warning with this code:
const routes = (
<Route component={App}>
<Route path="/" component={MainPage}/>
<Route path="/page2" component={Page2}/>
<Route path="/settings" component={SettingsPage}/>
</Route>
);
class Root extends Component {
render() {
return (
<Router history={browserHistory} createElement={this.createElement}>
{routes}
</Router>
)
}
}
This is OK, awesome solution to vanish [react-router] warning, and for Root Component changing state the routes was static and you won't see any warnings. BUT my issue is: I pass Root Component props to each Route and I can not do the above solution 😞 ,
I must put App Route inside Router so with this method absolutely this is not solution method and I will saw the known warning again, see my router code:
export default class AppRoutes extends Component {
render() {
return (
<Router history={hashHistory}>
<Route path="/" {...this.props} component={App}>
<IndexRoute component={Home} {...this.props}/>
<Route path="/transaction" component={Transaction} {...this.props}/>
<Route path="/users-management" component={UsersManagement} {...this.props}/>
<Route path="/issues" component={Issues} {...this.props}/>
<Route path='/not-found' component={NotFound}/>
<Route path='/settlement-management' component={SettlementManagement} {...this.props}/>
<Route path='/categories-management' component={CategoriesManagement} {...this.props}/>
<Route path='/gifts-management' component={GiftsManagement} {...this.props}/>
<Redirect from='/*' to='/not-found'/>
</Route>
</Router>
);
}
}
And the Root Component render code is:
render(){
return(
<AppRoutes {...this}/>
);
}
I passed this as a props to AppRoutes component and I need to pass inherited this.props to sub Routes and use them. how I could won't see warning and pass props to any Routes?
One of my solution is that, I write all Routes as static and call Root Component props directly inside each component, but how? I don't know how I can call and keep props of Root Component inside the component that need to have props of Root Component as the component is not direct Root Component children?
You can use render route prop instead of component to pass props to your components :
<Route path="/transaction" render={() => <Transaction {...this.props} />} />
Edit : Or this to also pass route props :
<Route path="/transaction" render={(routeProps) => <Transaction parentProps={this.props} {...routeProps} />} />
(I think it's better to pass individual custom parent props to not enter in conflict with routeProps)
I am trying to group some of my routes together with React Router v4 to clean up some of my components. For now I just want to have my non logged in routes group together and my admin routes grouped together but the following doens't work.
main.js
const Main = () => {
return (
<main>
<Switch>
<Route exact path='/' component={Public} />
<Route path='/admin' component={Admin} />
</Switch>
</main>
);
};
export default Main;
public.js
const Public = () => {
return (
<Switch>
<Route exact path='/' component={Greeting} />
<Route path='/signup' component={SignupPage} />
<Route path='/login' component={LoginPage} />
</Switch>
);
};
export default Public;
The Greeting component shows at "localhost:3000/", but the SignupPage component does not show at "localhost:3000/signup" and the Login component doesn't show at "localhost:3000/signup". Looking at the React Dev Tools these two routes return Null.
The reason is very obvious. for your route in main.js, you have specified the Route path of Public component with exact exact path='/' and then in the Public component you are matching for the other Routes. So if the route path is /signup, at first the path is not exact so Public component is not rendered and hence no subRoutes will.
Change your route configuration to the following
main.js
const Main = () => {
return (
<main>
<Switch>
<Route path='/' component={Public} />
<Route path='/admin' component={Admin} />
</Switch>
</main>
);
};
export default Main
public.js
const Public = () => {
return (
<Switch>
<Route exact path='/' component={Greeting} />
<Route path='/signup' component={SignupPage} />
<Route path='/login' component={LoginPage} />
</Switch>
);
};
Also when you are specifying the nested routes these should be relative to the parent Route, for instance if the parent route is /home and then in the child Route you wish to write /dashboard . It should be written like
<Route path="/home/dashboard" component={Dashboard}
or even better
<Route path={`${this.props.match.path}/dashboard`} component={Dashboard}