With React Testing Library, how to wrap component with a specific route? - react-router

I've looked at several examples, trying to get my React Testing Library test to simulate a specific URL that my React component will then utilize with useParams.
For example, here's one "official" article on the subject: https://react-testing-examples.com/jest-rtl/react-router/
So I tried this code:
function renderDom(component) {
const companyId = 226;
const userId = 123;
// console.log(component, companyId, userId);
return {
...render(
<SessionProvider>
<AppProvider>
<MemoryRouter initialEntries={[`/users/company/${companyId}/user-list/${userId}`]} initialIndex={0}>
<Route path='/users/company/:companyId/user-list/:userId'>
{component}
</Route>
</MemoryRouter>
</AppProvider>
</SessionProvider>
)
};
};
But it didn't work. When I ran the test using this code, my console logs within my component code didn't actually run.
So I'm looking for a proven way to wrap my component such that it simulates a URL.

Related

react-router Redirect vs history.push

I was reading react-router-redux examples and I confused,
what is the difference beetween:
import { Redirect } from 'react-router-dom'
...
<Redirect to='/login' />
and
import { push } from 'react-router-redux'
...
push('/login')
Redirect
Rendering a <Redirect> will navigate to a new location. The new location will override the current location in the history stack, like server-side redirects (HTTP 3xx) do.
whereas History
push function Pushes a new entry onto the history stack
The <Redirect> component will, by default, replace the current location with a new location in the history stack, basically working as a server-side redirect.
But it can also be used with the property push and in this case it will push a new entry into the history stack, working the same way as history.push.
In fact the <Redirect> component uses the history push and replace methods behinds the scene. It is just a more React way of navigating.
So basically you have two ways of navigating:
Use the history.push and history.replace in a component (usually wrapped with the withRouter HOC, so that you can have access to the location object without having to pass it from parent to child.
Use the <Redirect> component with or without the push property, depending
I noticed a difference between the two in my use case with Redux and Typescript.
Here's a simplified version of it:
export const LandingPage = () => {
const { history } = useHistory();
const { viewer } = useSelector(state => state.viewer);
// Redirect method returns another component that handles the redirect
// without mounting any of the route components
if (!viewer?.token) {
return <Redirect to="/signin" />;
}
// History.push method mounts the route component and runs 'onload' functions
// Which can still throw an error if there's no viewer
// if (!viewer?.token) {
// history.push('/signin');
// // return history.push('/signin'); // throws Typescript error
// }
return (
<Switch>
<Route path="/dashboard" component={Dashboard}/>
<Route path="/profile" component={Profile}/>
<Route path="/" component={Home}/>
</Switch>
);
};
When using history.push() method, the JSX in your return statement can still mount and run, whereas returning Redirect can block the rest of your code.

React - Re-direct to a new domain

I have an app built in react and I am trying to implement re-direct where I need to redirect the user from my app base URL to a completely new domain.
Example:
React app base URL - "localhost:3001"
Redirect to - "www.google.com"
I am using react routes in my app.
ReactDOM.render(
(
If I use Redirect as above, it redirects to "http://localhost:3001/#/http:/www.google.com"
How to get rid of base URL while redirecting to a new domain?
React-route only allows redirect to other routes, you can confirm this by reading Github issue here
So the answer is NO, YOU CAN'T.
If I'm not misunderstanding your intention here, you want to redirect whatever which is not being handled by the router.
One aproach that comes to my mind as a solution:
A component-dedicated: Create a component dedicated for this route with a componentDidMount similar to this:
componentDidMount() {
window.location.assign('http://github.com');
}
As an alternative, you can catch it directly in the server and redirect there.
But definitively you need to look for an alternative solutoin due react-route does not allow redirecting to externals url
Expanding on an excellent answer by Facino La Rocca
import React, {Component} from 'react';
class DomainUrlRedirect extends Component{
componentDidMount(){
const{searchParams, pathname, url} = this.props
let uri = new URL(url);
uri.pathname = pathname
for(const paramName in searchParams){
uri.searchParams.append(paramName, searchParams[paramName])
}
window.location.assign(uri.href)
}
render(){
return(<span data-test-component="DomainUrlRedirect"></span>)
}
}
export default DomainUrlRedirect
You can leverage URL utility supported by modern browsers to construct your URL properly.
Also, don't forget that the component will still try to render. Render it as part of some other component on condition so that object that uses it does not disappear.
render(){
const{ redirectNow } = this.props;
return(<div>
<p>Important content</p>
{redirectNow &&
<DomainUrlRedirect {...url_params}/>
}
</div>)
}

Prefetch resources for async routes

Is there a way to prefetch or preload async routes? I'm exploring how to do this right now with RR2/3. The basic idea is I code split on every route but I'd like to be able to cache bundles for connected pages in a service worker before visiting that page. So what I'd like to do is have a custom <Link> that every time it's rendered, it tries to cache the resources of the page it's linked to. This would make page transitions considerably faster. What I don't know is if there's a way to emulate navigating to a route so that the resources will be fetched. Is there an API for this or some sort of tricky way to do this someone can think of?
This is what I came up. It's a component that wraps the React Router Link component and in componentDidMount (so only runs on the client not the server) check if in production (no need to run this during development) and if this is a browser that doesn't support Service Workers (this check is specific to my use case). Then manually match against the location and call any async getComponent functions.
import React from 'react'
import Link from 'react-router/lib/Link'
class GatsbyLink extends React.Component {
componentDidMount () {
// Only enable prefetching of Link resources in production and for browsers that
// don't support service workers *cough* Safari/IE *cough*.
if (process.env.NODE_ENV === 'production' && !('serviceWorker' in navigator)) {
const routes = require('my-routes')
const { createMemoryHistory } = require('history')
const matchRoutes = require('react-router/lib/matchRoutes')
const getComponents = require('react-router/lib/getComponents')
const createLocation = createMemoryHistory().createLocation
if (typeof routes !== 'undefined') {
matchRoutes([routes], createLocation(this.props.to), (error, nextState) => {
getComponents(nextState, () => console.log('loaded bundle(s) for ' + this.props.to))
})
}
}
}
render () {
return <Link {...this.props} />
}
}
module.exports = GatsbyLink
You could just do a require.ensure... section in a timeout when the Link is mounted. That should require the code split and load it up async. The timeout will ensure it get's loaded in a separate file.
I would recommend using RR4 for code splitting as I found in RR3 the async required routes get re-included and re-rendered if a child route changes. In my case, I had the componentWillMount of my routes being fired for any child route changes. e.g. Navigating from /agent/step-1 to /agent/step-2 will cause the Component for /agent to be unmounted and re-mounted.

React List w/ JSON call? (FIDDLE)

I"m trying to pull images into a React List module using a JSON and can't figure out what I'm doing wrong.
This FIDDLE is supposed to grab two images from my server.
Code:
var Playlist = React.createClass({
render() {
var playlistImages = [];
$.getJSON('http://k1r.com/json/playlist_tn.json', function(data){
playlistImages = data;
});
return (
<List list={playlistImages.images} />
)
}
});
UPDATED FIDDLE
I'm not sure you can use modules directly in JSFiddle, but apart from that the main issue is that you are fetching some asynchronous data directly in your render method and React isn't going to wait on that to finish before rendering your List.
The suggested approach (via the docs: https://facebook.github.io/react/tips/initial-ajax.html) is to make your data request inside of the componentDidMount or componentWillMount lifecycle methods then use setState() to trigger a re-render when the data has been received, which should then correctly render your List.

Why my page will not change when use history.push using react-router

I have a single page application webpage...
Below is some of my code...
var createBrowserHistory = require('history/lib/createBrowserHistory');
var history = createBrowserHistory();
history.push('account/person');
When I click a button, I can see the browser's URL change to http://IP/account/person, but nothing happened, still on same page.
But if I replace url directly to http://IP/account/person by typing, the page will map to correct page.
Below is my react router config
<Router history={new createBrowserHistory()}>
<Route path='/account' component={Gift}></Route>
<Route path='/account/person' component={Person}></Route>
</Router>
Why this happened?
I think you need to import a different object to handle the programmatic routing:
https://github.com/reactjs/react-router/blob/master/docs/guides/NavigatingOutsideOfComponents.md
import { browserHistory } from 'react-router'
browserHistory.push('/account/person')