Working with the example in the README at
https://github.com/ReactTraining/react-router/tree/master/packages/react-router-redux
I have created this layout in my index:
// attach the redux dev tools extension for Chrome
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// apply router logic as middleware
const history = createHistory();
const router_mw = routerMiddleware(history);
// second arg to createStore is the inital redux store state
const store = createStore(
reducers,
initialState,
composeEnhancers(
applyMiddleware(
ReduxPromise,
ReduxThunk,
router_mw
)
));
// --> add routes for logs, opsec, etc. later
ReactDOM.render(
<Provider store={ store }>
<ConnectedRouter history={ history }> // <- error here
<div>
<Route exact path="/" component={ LoginScreen } />
<Route path="/comp1" component={ comp1 } />
<Route path="/comp2" component={ comp2 } />
</div>
</ConnectedRouter>
</Provider>
, document.getElementById('root')
);
Any attempt to compile it complains about { history } in the ConnectedRouter entry point:
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in. Check your code at index.js:52.
What am I missing? This is a react-router-redux retrofit - going back to react-router (and changing ConnectedRouter to BrowserRouter) and everything works as expected.
I used react-router-dom#4.2.2 and had the same problem until update react-router-redux to ^5.0.0-alpha.9.
If you are using react-router-dom#4 you should use react-router-redux#5, it is said in react-router-redux repo:
The next version of react-router-redux will be 5.0.0 and will be compatible with react-router 4.x. It is currently being actively developed over there. Feel free to help out!
Related
I have a component ViewRestaurant.js that is part of a React-Router (using 5.2). I'm trying to access a dynamic URL parameter (/view-restaurant/:id, the id)
Snippet from App.js (the main data loader):
return (
<Router>
<div>
<Route exact path="/" component={() => <Main
restaurants={this.state.restaurants}
account = {this.state.account} /> } />
<Route path="/view-restaurant/:id" component={() => <ViewRestaurant
restaurants = {this.state.restaurants}
account = {this.state.account} /> } />
</div>
</Router>
);
This is how I'm trying to put the Link, snippet from Main.js which displays a list of restaurants:
<Link
to={{
pathname: '/view-restaurant/' + key,
state: { id: key }
}}>
View
</Link>
But then, when I'm trying to access what I've passed, meaning the :id, which is the same as key in Main.js, it doesn't work.
Snippet from ViewRestaurant.js:
render() {
const restaurants = this.props.restaurants
console.log(restaurants[0].name) // this works.
console.log(this.state.id) // this throws the error.
// return removed for readability
}
The error: TypeError: this.state is null
According to the documentation, we should be able to create a state and pass it via link. I'm 100% sure I'm misinterpreting the documentation, but I just don't know how to solve this error.
I figured it out eventually.
Just don't use classic components with the render function that returns stuff.
The way in React Router v5 (v5.2 in my case) is to pass props via functions and get parameters via a special function.
For this specific issue I had, only had to use useParams() from the new React-Router documentation and completely refactor my code to use functions and hooks instead.
I highly recommend to ditch clasic classes that use the render() function and move to the improved way of coding using the new React-Router.
Youtube video that teaches you how to pass props
Documentation on useParams()
How I solved it:
function ViewRestaurant({ restaurants }) {
const { id } = useParams();
}
and in Main.js (note that the key is obtained via a simple js map)
<Link to={{ pathname: '/view-restaurant/' + key}}>View</Link>
and finally, App.js
<Route path="/view-restaurant/:id">
<ViewRestaurant
restaurants={this.state.restaurants} />
</Route>
In order to handle authentication / conditional routing in my App, I decided to bring in a HOC component that, based on a switch statement, checks whether a component should be rendered or not.
Of course I could get the same by defining the conditions in the components themselves, but now it allows me to have a single file to handle this.
However, using the useHistory() hook seems to return history as undefined. Likely since my app Routes are not written in a conventional way (AllowAccess is the HOC component here):
<Router>
<Switch>
<Route exact path='/success' component={AllowAccess(SuccessComponent)}></Route>
<Route render={() => <Redirect to="/" />} />
</Switch>
</Router>
Are there ways so I can acces the history prop from the useHistory hook and use them in both the HOC as 'normal' component?
well as i see you code i dont really find why it not help you, but i use here PrivateRoute file that handle the access to the route.
but why you want to access the useHistory from the HOC
const PrivateRoute = ({ component: Component,isAuthenticated, ...rest }) => {
return (
<Route {...rest} render={props =>
!isAuthenticated ? (
<Redirect to='/'/>
) : (
<Component {...props} />
)
}
/>
);
};
implementation
`<PrivateRoute exa`ct path="/add" component={AddEmployee} isAuthenticated={isAdmin}/>
I have followed the examples closely but I cannot get the MemoryRouter (is this how you are supposed to test route components?) to work with a test using jest and enzyme.
I would like to navigate to one of the routes, and have that reflected in my snapshot. The code below attempts to navigate using MemoryRouter to "/A" so I assume I would see <div>A</div>
import React from 'react';
import Enzyme, {mount} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import {BrowserRouter as Router, MemoryRouter, Route, Switch} from 'react-router-dom';
Enzyme.configure({adapter: new Adapter()});
describe('Routing test', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(
<MemoryRouter initialEntries={["/A"]}>
<div className={"Test"}>This is my Test Component and should not have any test specific code in it
<Router>
<Switch>
<Route path={"/A"}>
<div className={"A"}>A</div>
</Route>
<Route path={"/B"}>
<div>B</div>
</Route>
</Switch>
</Router>
</div>
</MemoryRouter>
);
});
afterEach(() => {
wrapper.unmount();
});
it('matches snapshot', () => {
expect(wrapper.find(".Test")).toHaveLength(1); //this ok
expect(wrapper.find(".A")).toHaveLength(1); //but this is not ok :( It should find A
});
});
Instead of seeing <div>Test<div>A</div></div> I just see <div>Test</div>
NOTE: My example is simplified into one class. My real world situation is that <div>Test...</div> is a seperate component.
I can't find any proof of this but I always was under impression than you should use only one <Router> somewhere at the top of the tree and shouldn't nest them.
So I've looked in the source code myself, and if I got it right, this is true. Because:
react-router uses Context API to pass props down the hierarchy.
From React docs:
[...] it will read the current context value from the closest matching Provider above it in the tree.
<Router> is a Provider but not a Consumer, so it can't peek up props from a parent <Router>
When people advocate for tests they also mention that writing tests leads to a more testable code and a more testable code is cleaner. I wouldn't argue about this, I just wan't to note, that if you can write a testable code, then you also can write a non-testable one. And this looks like the case.
So although you specifically say that
should not have any test specific code in it
I would ague that, while you probably shouldn't use createMemoryHistory as #aquinq suggested, or put anything else specifically and only for testing purposes, you can and probably should modify your code to be more testable.
You can:
Move <Router> higher. You can even wrap the <App> with it - it's the simplest and a recommended way, although may not apply to your case. But still I don't see why can't you put <div className={"Test"}> inside the <Router> and not vice versa.
In your tests you are not supposed to test third-party libraries, you supposed to test your own code, so you can extract this
<Switch>
<Route path={"/A"}>
<div className={"A"}>A</div>
</Route>
<Route path={"/B"}>
<div>B</div>
</Route>
</Switch>
part into a separate component and test it separately.
Or if we combine these two: put <div className={"Test"}> inside the <Router>, extract <div className={"Test"}> into a separate component, write
wrapper = mount(
<MemoryRouter initialEntries={["/A"]}>
<TestDiv/>
</MemoryRouter>
)
Also createMemoryHistory can be a useful feature on it's own. And some time in the future you'll find yourself using it. In that case #aquinq's answer will do.
But if you can't/don't want to modify your code at all. Then you can cheat a little and try this approach: How to test a component with the <Router> tag inside of it?
OK I figured it out.
Its very ugly but you need to create a __mocks__ directory (In the first level of your project). __mocks__ seems to be poorly documented but it seems to be a jest thing, and everything in here will be run when testing, and here you can add mock stubs for certain external libraries.
import React from 'react';
const reactRouterDom = require("react-router-dom")
reactRouterDom.BrowserRouter = ({children}) => <div>{children}</div>
module.exports = reactRouterDom
My test file is the same as in my question (i think) :
import React from 'react';
import Enzyme, {mount} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import {BrowserRouter as Router, MemoryRouter, Route, Switch} from 'react-router-dom';
Enzyme.configure({adapter: new Adapter()});
describe('Routing test', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(
<MemoryRouter initialEntries={['/A']}>
<div className={"Test"}>This is my Test Component and should not have any test specific code in it
<Router>
<Switch>
<Route path={"/A"}>
<div className={"A"}>A</div>
</Route>
<Route path={"/B"}>
<div>B</div>
</Route>
</Switch>
</Router>
</div>
</MemoryRouter>
);
});
afterEach(() => {
wrapper.unmount();
});
it('matches snapshot', () => {
expect(wrapper.find(".Test")).toHaveLength(1); //this ok
expect(wrapper.find(".A")).toHaveLength(1); //but this is not ok :( It should find A
});
});
This works and my test is green! :)
UPDATE :
I think I got a bit confused because I was treating the Router like any other react component, when it actually is a top level component like redux Provider. Router should not be inside the App but outside the App like so (in an index.js file for example).
ReactDOM.render(
<Provider store={store}>
<Router>
<App/>,
</Router>
</Provider>,
document.getElementById('root')
);
Now when writing tests against App, I provide my own router such as MemoryRouter.
According to documentation, if you use a regular Router in your test, you should pass a history prop to it
While you may be tempted to stub out the router context yourself, we recommend you wrap your unit test in one of the Router components: the base Router with a history prop, or a <StaticRouter>, <MemoryRouter>, or <BrowserRouter>
Hope this will work. If not, maybe using a second MemoryRouter instead of Router will simply do the job.
Typically Router will be outside of the app logic, and if you're using other <Route> tags, then you could use something like <Switch>, like this:
<Router>
<Switch>
<Route exact path="/">
<HomePage />
</Route>
<Route path="/blog">
<BlogPost />
</Route>
</Switch>
</Router>
MemoryRouter actually is a Router, so it may be best to replace the "real" Router here. You could split this into a separate component for easier testing.
According to the source GitHub:
The most common use-case for using the low-level <Router> is to
synchronize a custom history with a state management lib like Redux or Mobx. Note that this is not required to use state management libs alongside React Router, it's only for deep integration.
import React from "react";
import ReactDOM from "react-dom";
import { Router } from "react-router";
import { createBrowserHistory } from "history";
const history = createBrowserHistory();
ReactDOM.render(
<Router history={history}>
<App />
</Router>,
node
);
From personal experience:
I have used an outer component (we called it "Root") that includes the <Provider> and <Router> components at the top level, then the <App> includes just the <Switch> and <Route> components.
Root.jsx returns:
<Provider store={rootStore}>
<Router history={rootHistory}>
<App />
</Router>
</Provider>
and App.jsx returns:
<Switch>
<Route exact path="/" component={HomePage}>
<Route exact path="/admin" component={AdminPage}>
</Switch>
This allows the App.test.jsx to use:
mount(
<Provider store={fakeStore}>
<MemoryRouter initialEntries={['/']}>
<App myProp={dummyProp} />
</MemoryRouter>
</Provider>
)
I need to start a react application and I need pre-rendering and routing, so I installed react-snap and react-router. (The react-router to do the routing and react-snap for pre-rendering obviously).
Everything looks fine in the local with 'npm start' but as I make a production build and serve it, the routing links make the page redirect to a new url, so all i see always, is the homepage.
My render looks like this:
render() {
return (
<Router>
<React.Fragment>
<MainNav/>
<Route exact path="/" component={Home}/>
<Route path="/greeting/:name/:surname" render={(props) => <Greetings text="Hello, " {...props} />} />
<Route path="/About" component={About}/>
</React.Fragment>
</Router>
);
}
and this is my index.js as suggested by react-snap
import React from 'react';
import { hydrate, render } from "react-dom";
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
const rootElement = document.getElementById("root");
if (rootElement.hasChildNodes()) {
hydrate(<App />, rootElement);
} else {
render(<App />, rootElement);
}
serviceWorker.unregister();
any ideas?
I've been able to fix a similar issue by adding the following snippet to my package.json
"reactSnap": {
"fixWebpackChunksIssue": false
}
Check the following link for more information and other options
https://github.com/stereobooster/react-snap/issues/264
I've been battling this for a couple weeks now. The main thing I've noticed is that if I call registerServiceWorker() in index.js, the app will function normally after building. If I comment out this line the app only routes to '/' no matter what.
It appears you are unregistering the service worker in your index.js, this might cause an issue.
A catch 22 I've run into and haven't been able to solve is that if I use the registerServiceWorker() call in index.js, react-snap doesn't properly prerender all the routes, and if I do comment out the registerServiceWorker() line, react-snap prerenders all routes perfectly, but the app doesnt navigate.
It's also worth noting that my project was created using 'create-react-app' and hasnt been ejected.
i am a react newbie, and every routing example i have found routes to components defined as const, but when using a component class with react router v4 the following error is thrown:
"TypeError: Cannot read property 'apply' of undefined
at new About (wuwemek.js:34:70)
..."
jsbin example
in the following example, routing to {Home} works fine, but routing to {About} throws the above error. relevant code below - please let me know if you need to see more:
var { BrowserRouter, Route, Link } = ReactRouterDOM;
const Home = () => <p>home</p>
class About extends React.Component {
render() {
return (<div>about</div>)
}
}
<Link to="/">home</Link>
<Link to="/about">about</Link>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
There is nothing wrong with your code.
Your jsbin's setting is incorrect. You should set it as JSX (React), not ES6/ Babel.