I have a test case where I need to start on the Signup page (/signup), and on successful signup, the application should navigate to the Home page (/). I am currently using the MemoryRouter to set the initial route to /signup and everything works well. See below:
import React from 'react';
import userEvent from '#testing-library/user-event';
import { MemoryRouter } from 'react-router';
import { Route, Routes } from 'react-router-dom';
import { render, screen } from '../../test/test-utils';
import { SignUpPage } from './SignUpPage';
const MockHomePage = () => <div>MockHomePage</div>;
describe('<SignUp />', () => {
test('navigates to Home page on successful signup', async () => {
render(
<MemoryRouter initialEntries={['/signup']}>
<Routes>
<Route path="/" element={<MockHomePage />} />
<Route path="/signup" element={<SignUpPage />} />
</Routes>
</MemoryRouter>
);
// Enter valid user info and submit form
userEvent.type(screen.getByLabelText('Full Name'), 'John Smith');
userEvent.type(screen.getByLabelText('Email'), 'johnsmith#gmail.com');
userEvent.type(screen.getByLabelText('Password'), 'let-me-in');
userEvent.type(screen.getByLabelText('Confirm Password'), 'let-me-in');
userEvent.click(screen.getByText('Sign up'));
// Expect to see the Home page
expect(await screen.findByText('MockHomePage')).toBeInTheDocument();
});
});
However, I'd like to use BrowserRouter in my tests instead of MemoryRouter (I understand that it is the recommended way since it is closer to what the real app would use). Unfortunately, BrowserRouter does not have the initialEntries prop (I am using React Router v6 beta.7). Is there a way to force BrowserRouter to start at /signup?
I tried the location API like this:
window.location.href = 'http://localhost:3000/signup';
However I get a jsdom error saying that this API is not implemented:
Error: Not implemented: navigation (except hash changes)
at module.exports (/Users/naresh/projects/accelerated-news/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
Is there any other way?
Using react-router-dom v6.
I believe you need to use window.history.pushState() so it would look something like this:
test('navigates to Home page on successful signup', async () => {
window.history.pushState({}, '', '/signup')
render(
<BrowserRouter>
<Routes>
<Route path="/" element={<MockHomePage />} />
<Route path="/signup" element={<SignUpPage />} />
</Routes>
</BrowserRouter>
);
// etc..
});
Related
This worked in "react-router-dom": "^5.3.0"
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Api from "./Api";
const api = new Api();
const App = () => {
return (
...
<Router basename="/my-app">
<Switch>
<Route
path="/complete"
render={(props) => <ConfirmationPage {...props} api={api} />}
/>
...
</Switch>
</Router>
After upgrading to "react-router-dom": "^6.4.3"
We've tried:
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Api from "./Api";
const api = new Api();
const App = () => {
return (
...
<Router basename="/my-app">
<Routes>
<Route
path="/complete"
element={(props) => <ConfirmationPage {...props} api={api} />}
/>
...
</Routes>
</Router>
But that doesn't work. We've read through https://reactrouter.com/en/6.4.3/upgrading/v5
but do not see how to handle passing in props.
In react-router-dom#6 the route components are passed as JSX to the element prop, and passing props to the routed component works just like it does anywhere else in React.
Example:
<Router basename="/my-app">
<Routes>
<Route
path="/complete"
element={(
<ConfirmationPage
api={api} // <-- props passed to component
/>
)}
/>
...
</Routes>
</Router>
There are no longer any route props, so if the routed components need access to what was previously provided they will need to use the React hooks, i.e. useLocation, useNavigate, useParams, etc.
Additional documentation:
Why does <Route> have an element prop instead of render or component?
Advantages of <Route element>
import { React } from "react";
import { Router as MyRouter, Switch, Route } from "react-router-dom";
import NavBar from "./Components/NavBar";
import Article from "./Components/Article";
import Articles from "./Components/Articles";
var createBrowserHistory = require("history").createBrowserHistory;
const history = createBrowserHistory();
const Router = () => {
return (
<div>
<MyRouter history={history}>
<NavBar />
<Switch>
<Route exact path="/" component={Articles} />
<Route exact path="/home/:title/:id" component={Article} />
</Switch>
</MyRouter>
</div>
);
};
I have a component which display a list of articles, and if you click on one of the articles, you will be redirected to the specifics of that article. Also, article is fetching the content from the server only using the id parameter. I implemented that by using
const history = useHistory();
const onclick = (title, id) =>{
history.push(`/home/${title}/${id}`);
}
Initially, I am having no trouble at all with this structure. However, when I added a random test article, and click on it, the url changes, but the component is never rendered(I tried to log "hello world" in the Article component but it never got printed out). Strangely, if I manually put in the url localhost:3000/home/fijdaifjaid/100 with some random title and the correct id(the id that router fails to render the component for), the component is rendered with no issue, but if I manually put in the correct title it does not work.(My title is "# 1 priority")
Any suggestion on what is the problem here? Also I should note this is not a universal problem, I have several other test ids that are working fine.
My dependencies:
"react": "^17.0.2",
"react-router-dom": "^5.2.0",
Try this
import { React } from "react";
import { Router as MyRouter, Switch, Route } from "react-router-dom";
import NavBar from "./Components/NavBar";
import Article from "./Components/Article";
import Articles from "./Components/Articles";
const Router = () => {
return (
<div>
<MyRouter >
<NavBar />
<Switch>
<Route exact path="/" component={Articles} />
<Route exact path="/home/:title/:id" component={Article} />
</Switch>
</MyRouter>
</div>
);
};
Since you are using Router as a wrapper of your application, what if you don't pass the history object on the Router since you will be using the react hook useHistory().
Instead of router use browser router :
import { React } from "react";
import { BrowserRouter as MyRouter, Switch, Route } from "react-router-dom";
import NavBar from "./Components/NavBar";
import Article from "./Components/Article";
import Articles from "./Components/Articles";
// var createBrowserHistory = require("history").createBrowserHistory;
// const history = createBrowserHistory();
const Router = () => {
return (
<div>
<MyRouter basename={process.env.PUBLIC_URL}>
<NavBar />
<Switch>
<Route exact path="/" component={Articles} />
<Route exact path="/home/:title/:id" component={Article} />
</Switch>
</MyRouter>
</div>
);
};
Before making the build don't forget to add homepage in your package.json :
"homepage": "https://xxxxx.github.io",
I'm coding a MediumBlog like application and I'm using React & redux and Django Rest Framework for my Backend. I already made the first page, which is the first page you see when you go to https://medium.com/
I'm able to navigate to different categories without refreshing the page, which is the Main Purpose of React. But now, when you want to read a post in the medium blog, the page is refreshing. I have no idea how to do it with react. I know how to build a Single Page App, but here, it seems that a new page is being loaded when you click on a post. My question is :
.Is this loading a new page instead of a new route ( a new HTML file or something )
.How can I handle this " multipage " structure with react
Here is my Single Page App that can load content from different categories
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './css/App.css';
import { Provider } from 'react-redux';
import { store } from '../store.js';
import AppHeader from './header/AppHeader';
import HeaderCategories from './header/HeaderCategories'
import PostListStream from './posts/PostListStream';
import PostList from './posts/PostList';
import { BrowserRouter } from "react-router-dom";
import { Route, Switch } from 'react-router-dom';
class App extends Component {
render(){
return(
<Provider store={store}>
<div className="App">
<AppHeader />
<HeaderCategories />
<Switch>
<Route exact
key="sciences"
path="/sciences"
render={() => <PostListStream field="sciences" />}
/>
<Route exact
key="littérature"
path="/littérature"
render={() => <PostListStream field="littérature" />}
/>
<Route exact
key="sciences-sociales"
path="/sciences-sociales"
render={() => <PostListStream field="sciences-sociales" />}
/>
</Switch>
<PostList />
</div>
</Provider>
)
}
}
ReactDOM.render(<BrowserRouter>
<App />
</BrowserRouter>, document.getElementById('app'));
Here is where my are :
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './css/App.css';
import { Provider } from 'react-redux';
import { store } from '../store.js';
import AppHeader from './header/AppHeader';
import HeaderCategories from './header/HeaderCategories'
import PostListStream from './posts/PostListStream';
import PostList from './posts/PostList';
import { BrowserRouter } from "react-router-dom";
import { Route, Switch } from 'react-router-dom';
class App extends Component {
render(){
return(
<Provider store={store}>
<div className="App">
<AppHeader />
<HeaderCategories />
<Switch>
<Route exact
key="sciences"
path="/sciences"
render={() => <PostListStream field="sciences" />}
/>
<Route exact
key="littérature"
path="/littérature"
render={() => <PostListStream field="littérature" />}
/>
<Route exact
key="sciences-sociales"
path="/sciences-sociales"
render={() => <PostListStream field="sciences-sociales" />}
/>
</Switch>
<PostList />
</div>
</Provider>
)
}
}
ReactDOM.render(<BrowserRouter>
<App />
</BrowserRouter>, document.getElementById('app'));
Thank you so much
You can create dynamic routes to your posts using react-router-dom in a similar way to what you have done with your other routes. react-router doesn't really refresh the page, although it looks like it. It simply renders another component when the route changes.
To create dynamic routes for your posts you can do something like this:
<Route
path="/post/:postId"
render={() => <PostComponent />}
/>
or like this if you want it to be a little more readable:
<Route
path="/post/:postId"
component={PostComponent}
/>
:postId here is a dynamic id, whatever is placed after /post/ in your URL will be considered the postId by react-router.
Inside your PostComponent you can do something like this to fetch your postId variable:
import { withRouter } from "react-router-dom";
const PostComponent = withRouter(props => (
const postId = props.match.params.postId;
return ();
))
You can then use your postId to fetch your post from the backend and do whatever you need to do with it. As soon as you go to your post URL now, it will show the PostComponent without hard refreshing the page.
React-Router appears to be working in my app except for the fact that I am getting a blank page instead of my component, even though it is directed to the proper path.
I'm scanning the documentation but I can't resolve the issue on my own after looking it over and searching Google/this site.
I had tried...
Making it so that the router.js file just contained the routes only to get the same results. Specifying exact path as well when doing so.
Reinstalling react-router-dom into the component in case there was an error when it downloaded.
Removing the provider in case that was the issue
Placing the code in the router file directly in the App.js file between the provider component tags
These are the files involved.
Router.js
import React from 'react';
import {Route, Switch, Redirect} from 'react-router-dom';
import LandingPage from '../scenes/LandingPage';
import CityPage from '../scenes/CityPage';
const Router = () => {
return (
<Switch>
<Redirect from='/' to='/landing' />
<Route path='/landing' component={LandingPage} />
<Route path='/citypage' component={CityPage} />
</Switch>
);
}
export default Router;
App.js
import React from "react";
import { BrowserRouter } from "react-router-dom";
import Router from "./services/Router";
import ChosenCityContextProvider from "./services/context/ChosenCityContext";
const App = () => {
return (
<BrowserRouter>
<ChosenCityContextProvider>
<Router />
</ChosenCityContextProvider>
</BrowserRouter>
);
};
export default App;
No error messages accompany the rendering of the site. Aside from the blank page, everything else appears to be working. In the React Dev tools, it states that the Router.Consumer has an object which is revealed to empty when expanded.
What is wrong with my code?
https://codesandbox.io/s/youthful-maxwell-rch1k?fontsize=14
Above is sandbox of code. I have the same issue here
I'm not certain why exactly this fixes the issue, but I've run into this on a work project so knew it worked.
If you add exact into the redirect element it forces the correct behavior.
import React from 'react';
import {Route, Switch, Redirect} from 'react-router-dom';
import LandingPage from '../scenes/LandingPage';
import CityPage from '../scenes/CityPage';
const Router = () => {
return (
<Switch>
<Redirect exact from='/' to='/landing' />
<Route path='/landing' component={LandingPage} />
<Route path='/citypage' component={CityPage} />
</Switch>
);
}
export default Router;
I tried this and it worked. I'm not sure why before it didn't. If anyone has an explanation please let me know because I am trying to learn what I did wrong initially.
<Route render={() => <Redirect from='/' to='/landing' />} />
I added the above, so my router file looked like this.
import React from 'react';
import {Route, Switch, Redirect} from 'react-router-dom';
import LandingPage from '../scenes/LandingPage';
import CityPage from '../scenes/CityPage';
const Router = () => {
return (
<Switch>
<Route path='/landing' component={LandingPage} />
<Route path='/citypage' component={CityPage} />
<Route render={() => <Redirect from='/' to='/landing' />} />
</Switch>
);
}
export default Router;
#DLowther has also showed me another solution
import React from "react";
import { Route, Switch, Redirect } from "react-router-dom";
import Page from "./Page";
import Home from "./Home";
const Router = () => {
return (
<Switch>
<Redirect exact from="/" to="/home" />
<Route path="/home" component={Home} />
<Route path="/page" component={Page} />
</Switch>
);
};
export default Router;
I would like to credit this individual for answering my question
Im building a small application where i have used ReactJs for the front end and node express with jwt and passport in back end. Im using react router v4 and i have configured the applications routes like this.
import React,{Component} from 'react';
import Login from './login/login';
import Signup from './signup/signup';
import Account from './account/account';
import Navigation from './navigation/navigation';
import Newsfeeds from './newsfeed/feedcontainer';
import Test from './test';
import NotMatch from './nomatch/Notmatch';
import injectTapEventPlugin from 'react-tap-event-plugin';
import createBrowserHistory from 'history/createBrowserHistory';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
const history = createBrowserHistory({});
injectTapEventPlugin();
let hasToken = () => {
const token = localStorage.getItem('jwtToken');
return (token == undefined);
}
class App extends Component{
render(){
return(
<MuiThemeProvider>
<Router history={history}>
<Switch>
<Route path="/login" exact component={Login}/>
<Route path="/signup" exact component={Signup}/>
<Route path="/test" exact component={Test}/>
<Navigation>
<Route path="/account" exact render={() => (hasToken() ? (<Account />) : (<Redirect to="/login"/>))}/>
<Route path="/home" exact render={() => (hasToken() ? (<Newsfeeds />) : (<Redirect to="/login"/>))}/>
</Navigation>
<Route component={NotMatch}/>
</Switch>
</Router>
</MuiThemeProvider>
);
}
}
export default App;
The code for logout
<Link to="/login"><MenuItem primaryText="Logout" onTouchTap={e=>{localStorage.clear();}}></MenuItem></Link>
When ever i try to logout(clear my localstorage and redirect to login route) it gets stuck in loop and the app breaks. How to avoid this?
How to restrict/authenticate routes. When already logged switch to home page when user tries to move to /login or /signup routes.