I've implemented an App component which contains a Route using React and React router:
import {
BrowserRouter as Router,
Route
} from 'react-router-dom';
import createHeader from './components/header/header';
import createLandingPage from './components/landing-page/landing-page';
import createFooter from './components/footer/footer';
export default React => () => {
const Header = createHeader(React);
const LandingPage = createLandingPage(React);
const Footer = createFooter(React);
return (
<section id="sectionId">
<Header />
<Router>
<Route exact path="/" component={LandingPage} />
</Router>
<Footer />
</section>
);
};
The app itself renders and works properly, as I can go to my browser and see everything as expected.
The problem arises when I try to implement proper testing, which runs in Node.js without a browser:
import test from 'tape';
import dom from 'cheerio';
import React from 'react';
import reactDom from 'react-dom/server';
import { MemoryRouter } from 'react-router';
import createApp from './app';
const render = reactDom.renderToStaticMarkup;
test('App init & render', assert => {
const message = 'It sohuld initialize and render the app.';
const App = createApp(React);
const $ = dom.load(render(
<MemoryRouter>
<App />
</MemoryRouter>
));
const output = $('#jptv').length;
const actual = output > 0;
const expected = true;
assert.equal(actual, expected, message);
assert.end();
});
I used MemoryRouter to wrap my app as indicated in React docs to avoid errors, but with or without it, I have the same error when running the test in the console:
Invariant Violation: Browser history needs a DOM
My guess is that I cannot run a component using BrowserRouter out of the browser, yet I don't understand what would be the proper way to test it, as I don't want to force myself to manipulate the code as to fit the test environment shortcomings.
I've searched the web and look into similar issues, but I cannot find an answer, and the docs offer little if no useful information to deal with this.
Any help will be most appreciated.
Thanks!
Related
Im new to this website so bear with me.
I'm developing an appointment based website in which I have a login page, a profile page, and a register page. Im trying to implement a programatic redirect to a users profile, I opted to use useNavigation from react-router-dom. I attempted to use the navigate function like the documentation says but it crashes my website after the render. I stepped through my code with my debugger and the correct page does render, however, somewhere after the render, my site goes blank and returns an error im confused by. I've managed to simplify/obfuscate my code aswell in the examples below.
This is the applicable code to my problem. When the page is rendered it will check for a JWT in local storage, it then verifies the JWT in my server and returns data, i expected that if the jwt is verified then the page will redirect to my profile page route. which it does do, but then goes blank.
The profilePage route is currently unprotected for simplicity so i can figure out why useNavigate isn't working.
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
app.js
import Appbar from './appbar.jsx';
import { Route, Routes } from 'react-router-dom';
import Login from "./components/login/login.jsx";
import ProfilePage from "./components/profilePage/profilePage.jsx";
export default function App() {
return (
<div className="App">
<Appbar/>
<>
<Routes>
<Route path="/" element={<Home /> }/>
<Route path="/login"/> element={<Login />}/>
<Route path="/profilePage" element={<ProfilePage />}/>
</Routes>
</>
</div>
);
}
login.jsx
import { useNavigate } from 'react-router-dom';
import { useEffect } from 'react';
export default function Login(props) {
const navigate = useNavigate()
/*
EDIT: I added this here because this was in my original code that got me my original error
*/
useEffect( async () => {
// My express route verifying the JWT
const response = await fetch('/users/userAuth', {
method: 'POST',
headers: {
'x-access-token': localStorage.getItem('token'),
},
})
const data = await response.json()
if (data.isLoggedIn === true)
{
navigate("/profilePage")
}
else {
console.log("not logged in")
}
}, [])
profilePage.jsx
export default function ProfilePage() {
// Keeping it simple until i can get useNavigate to work
return (
<div>
<h1>Profile Page</h1>
</div>
)
}
This is the error I get:
React Error from useNavigate
EDIT: Thanks for the patience regarding the edits. In an effort to obfuscate I removed the crucial parts of my code. Although im still getting the same error
in your code I think you forgot to define navigate
const navigate = useNavigate();
I think that the problem is on the async function passed in React.useEffect. You should create the async function inside React.useEffect's setup and then call it.
Have a look here on how to use async functions with React.useEffect
This question already has answers here:
Why useEffect running twice and how to handle it well in React?
(2 answers)
React Hooks render twice
(5 answers)
Closed 5 months ago.
Why is this duplicating in the console? I noticed this while working on another projects and noticed the amount of HTML elements I was adding using jQuery was twice as much as expected (building a notification framework). I tried recreating the problem in a new project and the behavior persisted
DupeMountTest.js:
import React, {Component, useEffect} from "react";
const DupeMountTest = () => {
useEffect(() => {
console.log("useEffect")
}, [])
return (
<div>
<p>Test</p>
</div>
)
}
export default DupeMountTest
App.js:
import {
BrowserRouter as Router,
Route, Routes,
} from "react-router-dom";
import DupeMountTest from "./DupeMountTest";
function App() {
return (
<Router>
<div className="App">
<Routes>
<Route path={"/"} exact element={<DupeMountTest/>}/>
</Routes>
</div>
</Router>
);
}
export default App
index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Console:
I also attempted this using a class component but "Mounted" also logged twice.
DupeMountTest using class component:
import React, {Component, useEffect} from "react";
class DupeMountTest extends Component {
componentDidMount() {
console.log("Mounted")
}
render() {
return (
<div>
<p>Test</p>
</div>
)
}
}
export default DupeMountTest
Answer provided by evolutionxbox inside the comments section. Problem solved by removing <React.StrictMode> in index.js:
Updated index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// <React.StrictMode>
//
// </React.StrictMode>
<App />
);
React 18 introduces a new development-only check to Strict Mode. This
new check will automatically unmount and remount every component,
whenever a component mounts for the first time, restoring the previous
state on the second mount.
https://reactjs.org/blog/2022/03/29/react-v18.html#new-strict-mode-behaviors
So, your useEffect is running twice on each mount.
This was put in place to lay the groundwork for future features, so it's not exactly a bad thing.
In the future, we’d like to add a feature that allows React to add and
remove sections of the UI while preserving state. For example, when a
user tabs away from a screen and back, React should be able to
immediately show the previous screen. To do this, React would unmount
and remount trees using the same component state as before.
This feature will give React apps better performance out-of-the-box,
but requires components to be resilient to effects being mounted and
destroyed multiple times. Most effects will work without any changes,
but some effects assume they are only mounted or destroyed once.
It's also discussed in this post (thanks #evolutionxbox):
React Hooks render twice
I really liked a comment there that basically summed up the answer to what you're wondering about:
"it's a feature, not a bug."
I am working on project that will use Reactjs on top off rails application.
So i set up my project following this link
Set up Rails with React and Es6
Everything is working fine but i change my bundle.js to this
require('./main');
Then inside main.js i have this
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route } from 'react-router';
import { browserHistory } from 'react-router';
/*Import Component*/
import NotFound from './components/NotFound';
/*
*
* Routes
*
* */
var routes = (
<Router history={browserHistory}>
<Route path="/" component={NotFound}/>
</Router>
);
ReactDOM.render(routes , document.querySelector('#main'));
So inside my component file (/components/NotFound.js)
I have this
import React from 'react';
var NotFound = React.createClass({
render : function () {
return (
<div>
<h1>Not found</h1>
</div>
)
}
});
export default NotFound;
It can be displayed smoothly but the problem is
when i change some code inside my component file (in this case /components/NotFound.js) then refresh the page.
It took around 3 Seconds for just 1 line of code that i added to my component file.
So this is so painful because it took a huge amount of time just for edit a few line of code.
So anyone know how can i reduce the build time to be less than this?
Thanks a lot!
I am currently to adapt a simple web app i made (using React and Redux) into a native desktop app.
I am using GitHub's Electron and Webpack to do this. Everything is fine if I use hashHistory from React-Router.. but I want to use browserHistory so my app will still look nice (URL-wise) when running as a webapp. If i do this though, I get the following error:
No route matches path ".../index.html"
Which makes sense to me. I am loading index.html as the main file for Electron:
mainWindow.loadURL('file://' + __dirname + '/index.html');
I'm just wondering if it is at all possible to use browserHistory with React-Router and Electron. If anyone knows it would be greatly appreciated!
Not exactly. But there is even better solution .
You should Separate from your react app the bootstrap file.
Bootstrap file which load your app and pass to it some additional params from outside.
In your situation you will create two bootstrap files, one for electron - with memoryHistory (I think it is better for electron) and second one for browsers with browser history.
Example of bootstrap file for electron index-electron.jsx:
import React from "react";
import ReactDOM from "react-dom";
import { createMemoryHistory } from "react-router";
import App from "./App.jsx";
const initialState = window.__INITIAL_STATE__;
const config = window.__CONFIG__;
const history = createMemoryHistory("begin-path");
ReactDOM.render(
<App
config={config}
history={history}
initialState={initialState}
/>, document.getElementById("root"));
Example of bootstrap file for browsers index-browser.jsx:
import React from "react";
import ReactDOM from "react-dom";
import { browserHistory } from 'react-router';
import App from "./App.jsx";
const initialState = window.__INITIAL_STATE__;
const config = window.__CONFIG__;
ReactDOM.render(
<App
config={config}
history={history}
initialState={initialState}
/>, document.getElementById("root"));
In my examples difference is small (only history) but you can make more changes. As you see I also provide additional begin params from outside (initialState, config);
And how your App should :
import React from 'react';
import { Router, Route } from 'react-router';
class App extends React.Component {
static propsTypes = {
history: React.PropTypes.object,
config: React.PropTypes.object,
initialState: React.PropTypes.object
};
render() {
return (
<Router history={this.props.history}>
<Route ...>
...
</Route>
</Router>
);
}
}
export default App;
Above code is only conception. It is from my project, where I removed obsolete things. Therefore without some modification it may not work
Now for electron you use index-electron.jsx, and for browsers index-browser.jsx. Most of your code is reusable cross both envs. And it is very flexible.
I'm using Preact as my View framework (can NOT use React due to the Facebook IP problems). I needed to use React Router for the location routing because it has more flexibility than the Preact Router that the same team built.
I managed to get React Router to accept Preact in place of React, however, I can't get it to match locations. I'm not sure if it's a compatibility problem, or a configuration problem. I've tried using just one pair of routes ( App and Account ) and it still doesn't work with that simplified setup.
Q: Does anyone see if I'm doing something wrong here?
The error I get is: Location "/account/12345/" did not match any routes
main.js
import { h, render } from 'preact';
import { Router, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import createStore from './createStore';
import createRoutes from './createRoutes';
process.env.DEBUG && console.log('Hello, developer!');
const history = browserHistory;
const store = createStore( history );
const routes = createRoutes( store );
render((
<Provider store={ store } key="redux-provider">
<Router history={ history } createElement={ h } routes={ routes } />
</Provider>
), document.body );
createRoutes.js
import { h } from 'preact';
import { IndexRoute, Route } from 'react-router';
// App Component
import App from './components/app';
// Sub Components
import Account from './components/account';
import Conversation from './components/conversation';
import Dashboard from './components/dashboard';
// Error Components
import BadAccount from './components/bad-account';
import NotFound from './components/not-found';
// Routes
export default ()=> (
<Route path="/" component={App}>
{/* Get URL parameter */}
<Route path="account/:accountID" component={Account}>
{/* Index Route */}
<IndexRoute component={Dashboard} />
{/* Sub Routes ( Alphabetical Please ) */}
<Route path="chat" component={Conversation} />
{/* Catch-All Route */}
<Route path="*" component={NotFound} />
</Route>
{/* Handle Invalid URIs */}
<Route path="*" component={BadAccount} />
</Route>
);
createStore.js
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import { routerMiddleware } from 'react-router-redux';
import messages from './resources/messages/reducer';
import conversation from './resources/conversation/reducer';
import layout from './resources/layout/reducer';
import profile from './resources/profile/reducer';
import contract from './resources/contract/reducer';
/*const { devToolsExtension } = window;*/
export default history => {
// Sync dispatched route actions to the history
const reduxRouterMiddleware = routerMiddleware( history );
// Create single reducer from all modules
const rootReducer = combineReducers({
messages,
conversation,
layout,
profile,
contract
});
// List redux middleware to inject
const middleware = [
thunk,
reduxRouterMiddleware
];
// Compose the createStore function
const createComposedStore = compose(
applyMiddleware( ...middleware )/*, // Figure this out...
( process.env.DEBUG && devToolsExtension ) ? devToolsExtension() : f => f*/
)( createStore );
// Create the store
const store = createComposedStore( rootReducer );
// Hook up Redux Routing middleware
// reduxRouterMiddleware.listenForReplays(store);
// Return store
return store;
};
(OP already solved his issue, but this ranks high in Google and is not very helpful for newcomers, so I thought I'd provide some background info)
Preact and preact-compat
preact is a minimal version of React that weighs just 3Kb. It implements a subset of React's API, with some small differences here and there. It also comes with a helper library, preact-compat, which provides compatibility with React by filling in missing parts and patching up API differences.
React-Router
react-router is a router library designed to work with React. But you can make it work with Preact as well, using preact-compat.
Setting up preact-compat
npm i --save preact-compat
Make sure you set up aliases for react and react-dom in your webpack / browserify configuration, or write some code to set up these aliases manually.
example webpack config
{
// ...
resolve: {
alias: {
'react': 'preact-compat',
'react-dom': 'preact-compat'
}
}
// ...
}
Then you can use React Components as-is. They won't know they are being rendered by Preact i.s.o. React. Have a look at this preact-compat-example.
Issues with compatibility
Keep in mind that when you are using Preact Compat, you are taking a risk. Jason is a very smart guy, but his library is only a fraction of the size of the one provided by Facebook so there's bound to be some differences. Components that use lesser known features of React might not work correctly. If you encounter such issues, report them to the preact-compat issue tracker (with a minimal reproduction in the form of a GitHub repo) to help him improve it.
There have been a few of such issues in the past that prevented React-Router from working correctly with Preact, but they have been fixed since and you should now be able to use the two together nicely.
Fiddle of Preact + React-Router
Have a look at this JS Fiddle for a working example.
Updated answer is there is a preact-router package now: https://www.npmjs.com/package/preact-router
import Router from 'preact-router';
import { h } from 'preact';
const Main = () => (
<Router>
<Home path="/" />
<About path="/about" />
<Search path="/search/:query" />
</Router>
);
render(<Main />, document.body);
Found the issue, was a pair of problems with Preact's compatibility with React:
Contexts not handled correctly:
https://github.com/developit/preact/issues/156
props.children not handled correctly:
https://github.com/developit/preact-compat/issues/47#issuecomment-220128365
Here is extend solution for preact-router with hash support.
Works with reload and direct access.
https://www.webpackbin.com/bins/-KvgdLnM5ZoFXJ7d3hWi
import {Router, Link} from 'preact-router';
import {h, render} from 'preact';
import {createHashHistory} from 'history';
[cut]...[/cut]
const App = () => (
<div>
<Header />
<Router history={createHashHistory()}>
<Page1 default path="/" />
<Page2 path="/page2" />
</Router>
</div>
);
render(<App />, document.body);