Difference between MemoryRouter and Router concerning testing with react-testing-library - react-router-dom

I work on a small App to get familiar with testing and such in React, using jest and React-Testing-Library.
In my <App /> Component, I have a Routes with two Route. One of those looks like this:
<Route path='/' element={<RequireAuth><Dashboard /></RequireAuth>} />
If the user is not logged in, <RequireAuth /> redirects the user to a Login /> Component via <Navigate to="/login" />.
This is just for context. All of this works. But for quite some time, I couldnt make the test pass to check that the Navigation happens. I figured, that my problem was the Route method I used in React-Testing-Librariesrender() method. The test passes if I use <MemoryRouter /> but doesnt if I wrap <App /> in <Router location={history.location} navigator={history} />..
In the following stackblitz, I have implemented both versions. The one that s not working, is currently commented out. Could someone tell me, why it only works with MemoryRouter? You find the test in src/routing.test.tsx. You can run the tests with npm run test.
https://stackblitz.com/edit/github-nfj4lw?file=src/routes.test.tsx

The Router is a low level function that is not environment specific. As the docs state, you probably never want to render your components with it.
Replacing it with higher level environment aware functions like BrowserRouter or (MemoryRouter) makes your test pass:
// routes.test.tsx
import { BrowserRouter } from 'react-router-dom';
it('Testing if Route Login gets rendered by default', () => {
render(
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
);
expect(
screen.getByText('Welcome to nutri. Please Login.')
).toBeInTheDocument();
});
If you want something more, you can add further assertions by querying the location/history stack.
Note: using MemoryRouter in tests is preferred to BrowserRouter since it allows you to keep your routing history independent of any external source. React-router uses it in many of its tests.

Related

React Router v6 and ownParams doesnt work like v5

Hello in react router dom v5 i can get params inside redux. Sample code is below:
1- passing parameter
<Route path="/saveproduct/:productId" component={AddOrUpdateProduct} />
2- get params inside redux. I can get the productId inside ownProps
function mapStateToProps(state, ownProps) {...
But when i call route in v6 i cant get the productId inside ownProps
First, in react-router-dom v6 you should pass component to Route like an element. See docs.
<Route path="/saveproduct/:productId" element={ <AddOrUpdateProduct /> } />
Second,
react-router-dom v6 Route components rendered via the element prop don't receive route props
See question. Redux is not needed here. Just use params react hook
const { productId } = useParams();
Im new at react and it take my one day. But finally i found the solution. I am not sure this is the best practice but it make sense for me now. Check link for solution.
https://youtu.be/qdCHEUaFhBk
Also thanks #kupp

Why cant my React App handle links to one of its routes?

Edit: For anyone coming here from Google, here is a TLDR: The reason for this "issue" is that React uses client side rendering. A quick solution is to use the HashRouter component, an SEO friendly solution is to use server side rendering(SSR). I switched to using NextJS, an SSR React framework, and my concern is resolved. Pages refresh like normal, and favorites work as intended. Thanks to all in the comments.
Original Question:
I've tried to find the answer to this but maybe i'm not googling well enough. So I have a react app with multiple routes, one of the routes is /reset, I have routing set up in the App.tsx like so:
import { BrowserRouter as Router, Link, Route, Switch} from 'react-router-dom';
class App extends Component<IAppProps, IAppState> {
constructor(props: IAppProps) {
super(props);
this.state = {
loggedin: "false",
user: {
username: "",
email: ""
}
};
}
<Router>
<nav className="topnav">
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About Us</Link></li>
<li><Link to="/faq">FAQ</Link></li>
<li><Link to="/login">Login</Link></li>
</ul>
</nav>
<Switch>
<Route exact path="/">
<Home
text="This text was passed in as a prop"
/>
</Route>
<Route path="/login">
<Login
changeHandler={this.updateLoginInput}
/>
</Route>
<Route path="/register">
<Register
formSubmit={null}
/>
</Route>
<Route path="/forgotpassword">
<ForgotPassword
stateSetter={this.handleChange}
/>
</Route>
<Route path="/reset">
<Home/>
</Route>
</Route>
</Switch>
</Router>
Sorry for the bad indenting, the editor doesn't like tabs.
The links work fine but, when ever i manually type in the address bar localhost:3000/reset it just keeps spinning and eventually times out. Same with every other route in fact. I need this to work because im sending a password reset email that will contain a link to use, and currently it just keeps spinning when the link is clicked. What if someone were to favorite a route on my page like mysite.com/page1? It would keep spinning and then eventually time out.
I've tried to add a route on the server side with app.get() and a response.redirect() and redirect to the reset route, but Chrome blocked it as an unsafe redirect.
Does this functionality not work on localhost or is there something else i'm doing wrong?

Handle Post request to React JS app

I have written a whole application in ReactJS Client Side. I am able to accept /API on my react app URL using <BrowserRouter>
I am looking for a way to make a post request to my React App. Eg. When a user makes a post request to http://myreactapp.com , it should recognize the Post request and get the Post Body received from this call and I want to assign it one of the state before the React App loads.
I tried searching many things on BrowserRouter but I am not able to find 1.
Can someone help me direct towards the way to implement handling POST with body params.
I am open to only suggestions too and and not the whole code since I am kind of stuck thinking what to do next.
render((
<BrowserRouter>
<div>
<Route exact path="/"
render={() => (<App />)} />
<Route path="/find/:find"
render={(props) => (<App {...props} />)} />
</div>
</BrowserRouter>
), document.getElementById('root')
);
I tried with BorwserRouter but it seems it only accepts Get parameters and not Post body.
I have also heard of using NodeJS or express as the backend but can someone help me with modifying my current React code to convert to Express only for Post request and remaining everything remains same,
TIA!

react + routing + security

I am building a web application with React and react-router and I would like to protect some routes of my React application with an existing external access management infrastructure (OpenAM).
I would like to protect the http://web.example.com:8080/myapp/#/user-profile url which means that only logged users can have access to this route.
I have some other routes which are public and any user can open them, for example http://web.example.com:8080/myapp/#/welcome.
The Access Management protects urls and if a user wants to open a protected url then a login form is shown by Access Management and after a successful login the original requested url will be displayed.
The problem here is that react-router adds the routing info after the '#' character and the above mentioned two different urls are same from the access manager point of view because they refer to the same web resource (/myapp). The different between these two urls appear after the '#' character.
I need to have real urls without '#' chars like this:
http://web.example.com:8080/myapp/user-profile
http://web.example.com:8080/myapp/welcome
Is there any way to use real url mappings with React?
Do you guys have any idea or workaround how to use real url routes with react?
Thanks.
UPDATE:
This is my code. The urls in the web browser look nice but I get a "about did not match any routes" error. Requested url: http://web.example.com:8080/myapp/about
import {Router, Route, IndexRoute, useRouterHistory} from 'react-router';
import {createHistory} from 'history';
const browserHistory = useRouterHistory(createHistory) ({
basename: '/myapp/'
});
ReactDOM.render(
<Router history={browserHistory}>
<Route path="/" component={MainLayout}>
<IndexRoute component={Home} />
<Route path="about" component={About} />
</Route>
</Router>
)
You could use browserHistory, as suggested in the comments, to get rid of the #-sign. If you want the root of your react-application to be /myapp/ instead of / you can try:
import {Router, Route, useRouterHistory} from 'react-router';
import {createHistory} from 'history';
const browserHistory = useRouterHistory(createHistory)({basename: '/myapp/'});
ReactDOM.render(
<Router history={browserHistory}>
...
</Router>
)

Multiple entry-points in webpack with react-router

I am facing tremendous issues trying to implement multiple entry points along with react router. The aim is to achieve using webpack to do something that is suggested by https://github.com/petehunt/webpack-howto.
So far I have currently set it up like this:
webpack.config.js
entry: {
entry1: __dirname + './entry1.jsx',
entry2: __dirname + './entry2.jsx'
}
routes.jsx
export default (
<Route path='/' name='app' handler={App}>
<DefaultRoute handler={entry1} />
<Route name='entry2' handler={entry2}/>
</Route>
);
Whenever this code is run to instantiate a instance of Router on the client
Router.run(routes, Router.HistoryLocation, (Handler, state) => {
React.render(<Handler />, document.getElementById('app'));
});
Node.js will try to run this code on the server which obviously fails as DOM is not available there. It will produce an error.
Is there any proper way to implement multiple-entry points with react-router?
I have done something similiar to this in a sandbox webpack-react-boilerplate repository I've been playing around with using react-router.
I believe the answer you may be looking for lies here where you need to use React.renderToString and/or React.renderToStaticMarkup in your server entry file as you rightly suggest, there is obviously no DOM on the server. You can see how the client entry point here differs to the server one.

Resources