react-snap and react-router together make a problem - react-router

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.

Related

TypeError: Cannot read properties of undefined (reading 'pathname')

TypeError: Cannot read properties of undefined (reading 'pathname')
What is the problem?
My code
import {BrowserRouter as Routes, Route, Router} from "react-router-dom";
const App = () => {
return (
<div className={stl.container}>
<Header/>
<Nav/>
<Router>
<Routes>
<Route path='/messages' element={<Messages/>}/>
<Route path='/profile' element={<ProfileContent/>}/>
</Routes>
</Router>
</div>
);
}
Maybe something is wrong with your environment. I had the same issue, reinstalling packages really helped me.
Fix the imports. You're importing BrowserRouter as Routes.
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
Move the Nav component into the Router so it has a routing context provided to it. Any Link components rendered need the routing context.
const App = () => {
return (
<div className={stl.container}>
<Router>
<Header/>
<Nav/>
<Routes>
<Route path='/messages' element={<Messages/>} />
<Route path='/profile' element={<ProfileContent/>} />
</Routes>
</Router>
</div>
);
}
If the code still isn't working then check your installed version of react-router-dom from your project's directory run:
npm list react-router-dom
If it is any v6.x version then you should be good. If there's still issue though then I suggest uninstalling and reinstalling react-router-dom.
npm un -s react-router-dom
npm i -s react-router-dom
then run the list command above to validate/verify the installed version.
The issue is due to you cannot pass an 'undefined' or 'null' value to the attribute, like in my case, I could have added a null-check, but I chose to add a "/" before it, as shown
Remove "/" from the beginning of the path attribute in Route component.
Ex: <Route path='messages' element={}/>.
Also give the same value to the "to" attribute in Link Component
Ex: Messages
Must Use Attribute (to="#") with Link and see that this will run
import is in wrong way,
import like this
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom'
correct sequence is Router then Routes then Route
You are importing Routes Route Router :)

React Router nesting

i'm trying to use nested routing in react router. But my nesting routig not working. If that's make diffrence i'm using Typescript.
//This is working
<Route exact path={path} component={StudentList}></Route>
//This is not working
<Route path={`${path}/:id`} component={StudentComponent}></Route>
I have a module called StudentModule. In module a have two routes like above when i route to
/students/1 nothing render
I created a sample app on CodeSandbox
https://codesandbox.io/s/vibrant-pasteur-n1eq7
To see what's wrong, navigate to students in menu then click student. It's needs to render StudentComponent and writes Student works on the screen.
Pls help me what's wrong in my code ?
At your main router, you declared
<Route exact path="/students" component={StudentModule} />
Because you set path to be exact from one hand, and not declare path as students*, while navigate to students/1, you aren't entering into the Route which holds the sub-router at all.
In component StudentModule, please declare the variable id, I think you have missed it and string literal is understanding the id as general string.
And pass the url like
<Route exact path={`${path}/${id}`} component={StudentComponent}></Route>
Find the updated code below:
import React from "react";
import { useEffect } from "react";
import { Route, Switch, useRouteMatch } from "react-router-dom";
import StudentComponent from "./Student";
import StudentList from "./StudentList";
export default function StudentModule() {
let { path } = useRouteMatch();
let id = 1;
useEffect(() => {
console.log(path);
});
return (
<Switch>
<Route exact path={path} component={StudentList}></Route>
<Route exact path={`${path}/${id}`} component={StudentComponent}></Route>
</Switch>
);
}
try it, hope this will be helpful.

Cannot navigate to path using MemoryRouter while testing

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>
)

react-router Cannot GET /route

Ok, guys, here's the problem... I've been writing code for my web application using another fluently working app as an example for the beginning. Here is the source code:
(./app.jsx)
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import App from './components/app';
import Signin from './components/auth/signin';
import reducers from './reducers';
const createStoreWithMiddleware = applyMiddleware()(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<Router history={browserHistory}>
<Route path='/' component={App}>
<Route path='signin' component={Signin} />
</Route>
</Router>
</Provider>,
document.getElementById('app')
);
(./components/app.js)
import React, { Component } from 'react';
import Header from './header';
export default class App extends Component {
render() {
return (
<div>
<Header />
{this.props.children}
</div>
);
}
}
(./components/auth/signing.js)
import React, { Component } from 'react';
import { reduxForm } from 'redux-form';
class Signin extends Component {
handleFormSubmit({ email, password }) {
console.log(email, password);
}
render() {
const { handleSubmit, fields: { email, password }} = this.props;
return (
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<fieldset className="form-group">
<label>Email</label>
<input {...email} className="form-control"/ >
</fieldset>
<fieldset className="form-group">
<label>Email</label>
<input {...password} className="form-control"/ >
</fieldset>
<button action="submit" className="btn btn-primary">Sign in</button>
</form>
);
}
}
export default reduxForm({
form: 'signin',
fields: ['email', 'password']
})(Signin);
(you can see my whole repository here: https://github.com/LiJuons/react-dribbble )
The thing is that when I go to localhost:3000 - everything's Ok, but when I enter localhost:3000/signin - I get error message that says "Cannot GET /signin" THOUGH the application I'm taking code from works properly and shows form!
The problem is in routes, because if I set signin.js route's path to '/' in my project, form is shown on home directory without any problem.
Package.json files are the same in both projects (same number of packages, same versions and dependencies), only start script differs, so...
THE ONLY DIFFERENCE between the working project and mine is that in working one 'npm start' script is defined as:
"start": "node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js"
where in mine it's:
"start": "node server.js"
P.S. I checked every line to be sure that code of both projects would be as homogeneous as possible.
Any suggestions how to fix this issue?
Thank you
Your server is probably not configured to support HTML5 history.
Take a look at https://medium.com/#baphemot/understanding-react-deployment-5a717d4378fd
React-router not working when typing URL manually
This is a common surprise and is related to the fact that you are using browserHistory in your application, which requires some additional configuration of the server itself. Basically, when you type the URL by hand, by default the server will look for a file with that path, stored on its disk — if not found, it will show a 404 error. What you want to do is internally redirect the request to the index of your application.
You can see the documentation for react-router v3 about this setting (don’t worry, it’s still valid for react-router 4!). Common configuration are:
express:
const express = require('express')
const path = require('path')
const port = process.env.PORT || 8080
const app = express()
// this assumes that all your app files
// `public` directory relative to where your server.js is
app.use(express.static(__dirname + '/public'))
app.get('*', function (request, response){
response.sendFile(path.resolve(__dirname, 'public', 'index.html'))
})
app.listen(port)
console.log("Server started on port " + port);

react-router gives warning and then doesn't work

For some reason I get a complaint in chrome from
import React from 'react';
import {Route, DefaultRoute} from 'react-router';
import App from "components/app"
import FindView from "components/find";
import RememberView from "components/remember";
import MetaView from "components/meta";
import ExploreView from "components/explore";
export default (
<Route name='pkm' path='/' handler={App}>
<DefaultRoute handler={RememberView} />
<Route name="remember" handler={RememberView} />
<Route name="find" handler={FindView} />
<Route name="meta" handler={MetaView} />
<Route name="explore" handler={ExploreView} />
</Route>
);
at the DefaultRoute line that looks like
React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components.
I don't see why this is. All the router examples I have seen for ES6 usage look similar to this. What am I missing?
And when I attempt to run it I get that it doesn't know what Router is?
import React from 'react';
import ReactDOM from 'react-dom'
import Router from 'react-router';
import routes from './routes';
import App from 'components/app'
Router.run(routes, Router.HistoryLocation, (App, state) => {
ReactDOM.render(<App {...state}/>, document.getElementById('content'));
});
module.js:8Uncaught TypeError: Cannot read property 'run' of undefined.
So I am obviously not getting something.
Which version of react-router are you using? In the latest version, Router module does not have a run function defined.
In the latest version, the configuration is as follows
ReactDOM.render(
<Router routes={routes} />
, document. getElementById('content'))
For more details refer the documentation
Yes. I was on a more recent version. I reworked the code in line with the github for react-router documentation and resolved the problem.