I can't figure out how to use any relatively recent (react 13+) version of React-router . The example on the current README suggests integrating it by rendering Router directly (with routes defined via child Route elements). Another official overview doc seems to advise using Router.run. The react-router examples use the former. None work for me: I get different errors depending on use:
When attempting to use react-router by rendering the Router element directly I get "Uncaught Error: Invariant Violation: A needs a valid Location”
When attempting to use react-router by running Router.run with routes, I get “Uncaught TypeError: Cannot read property 'toUpperCase' of null”
here is a simplified version of how to use it, this is using webpack for requires but this is irrelevant, if you have access to React and Router it will work.
var React = require('react');
var Router = require('react-router');
var DefaultRoute = Router.DefaultRoute;
var Route = Router.Route;
var RouteHandler = Router.RouteHandler;
var Link = Router.Link;
var Comp0 = require('./comp0.jsx');
var Comp1 = require('./comp1.jsx');
var Comp2 = require('./comp2.jsx');
var App = React.createClass({
render: function () {
return (
<div>
<div >
<li><Link to="comp0">comp0</Link></li>
<li><Link to="comp1">comp1</Link></li>
<li><Link to="comp2">comp2</Link></li>
</div>
<div>
<RouteHandler {...this.props}/>
</div>
</div>
);
}
});
var routes = (
<Route name="app" path="/" handler={App}>
<Route name="comp1" handler={Comp1}/>
<Route name="comp2" handler={Comp2}/>
<DefaultRoute name="comp0" handler={Comp0}/>
</Route>
);
Router.run(routes, function (Handler) {
React.render(<Handler />, document.body);
});
I'm using react-router version 0.13.3 with react version 0.13.3 (yes both are same). I use the Router.run() way so that the UI can be re-rendered on URL change. I'll show my code that works well for me, it's in ES6 (although shouldn't be too hard to derive ES5 from it):
import React from 'react';
import Router, { Route, DefaultRoute, NotFoundRoute, Redirect, Link } from 'react-router';
// Components
import App from './components/App.js';
import Home from './components/Home.js';
import SampleComponent from './components/SampleComponent.js';
const AppRoutes = (
<Route path="/" handler={App}>
<DefaultRoute name="home" handler={Home} />
<Route name="sample" handler={SampleComponent} />
</Route>
);
Router.run(AppRoutes, Router.HashLocation, (Root) => {
React.render(<Root />, document.body);
});
Make sure the components you specify in AppRoutes is accessible (by importing or requiring them). Also in this case I've used DefaultRoute for instance in AppRoutes - so if you're using similar configurations then make sure you have them available from the react-router export. Hope that's clear.
If you're still facing issues then share your code.
I have an existing example code that you can have reference with - https://github.com/iroy2000/react-reflux-boilerplate-with-webpack
For those who are interested, it is called "React Reflux Workflow Boilerplate", and it is a workflow framework that make life easier for developers by providing a development and production ready build process framework out of the box.
Related
I'm designing a MFE app called header. Implementation is something like below,
header.js
const headerApp = () => {
const navigate = useNavigate();
const logoClickHandler = () => {
navigate('/some-route'); // router v6
}
return(
...
<Logo onClick={logoClickHandler} />
)
}
App.js
I want to keep/use it like below
const App = () = {
return(
<div>
<HeaderApp /> // This component just uses useNavigation or <NavLink to='/some-route' />
</div
)
}
Problem is Header app doesn't have its own routing mechanism in it. It is just a separate app and to be more specific standalone component and just provides navigations among different MFE apps using useNavigate() OR <NavLink /> router feature.
Since, I'm using useNaviage() OR <NavLink />, react is asking me to wrap the component inside <Routes> (as shown below) which is unnecessary for my header app.
React Error
useNavigate() may be used only in the context of a <Router> component.
don't want to end up like below,
const App = () = {
return(
<div>
<Routes>
<Route path='/' element={ <HeaderApp /> } />
</Routes>
</div
)
}
NOTE : Routing is handled in separate app called container. Header only provides links for navigations.
React Router uses React Context, which is a way of passing information down through the React tree. Because of this, you only need to make sure you have at least one <Router> as a parent of whatever component is rendering <headerApp /> for this to work.
If this is not acceptable to you - you want your application to be used in non-React router contexts, for example - you may want to refactor your header application such that it either provides its own React Router instance or accepts the required methods and attributes through props.
It is not possible to use <NavLink /> or useNavigate() without one of the parents of <headerApp /> using Router />.
I have two apps within single-spa, one in React and other in Vue.
The React app uses history library for navigation. Below given are my React app files:
history.js
import { createBrowserHistory } from 'history'
export const history = createBrowserHistory({
basename: '/myapp,
forceRefresh: true
})
App.js
import React, {Component} from 'react';
import { Router, Switch, Route } from 'react-router-dom';
import history from ‘../history.js’;
class App extends Component {
constructor(props){
super(props);
}
render(){
return (
<Router history={history}>
<Switch>
<Route exact path="/user" component={User} />
<Route exact path="/" component={Home} />
</Switch>
<Router />
)
}
}
I face an issue when:
I’m at the path https://localhost:3000/myapp/user of React app and I switch to the Vue app (using navbar menu).
Now when I switch back to React app, the url shows https://localhost:3000/myapp which ideally should load my Home component.
But now the history still has the old location (https://localhost:3000/myapp/user) which then loads the old User component.
Is there a way to update history when the url changes?
I didn't find how to change history, but I want you to fix some grammar errors. Meybe fixing errors can help you.
history.js
export const history = createBrowserHistory({
basename: '/myapp, // basename: '/myapp',
forceRefresh: true
})
App.js
<Switch>
<Route exact path="/user" component={User} /> // <Route path="/user" component={User} />
<Route exact path="/" component={Home} />
</Switch>
I think this boils down to getting to routing mechanisms to work together. This is an interesting problem and I have two ideas that could work:
Idea 1: refresh the page
If you're okay with a full reload, refreshing the browser whenever you switch between Vue and React would probably reset the state in both routers so that they both read the initial state they loaded on and continue.
I would do this by assigning window.location.href = '/myapp' inside any event handler that would cause the transition to React. It should reload the browser and navigate to that page. This should work if your react app is loading at that path.
Idea 2: Use createMemoryHistory and sync it with Vue's router
Instead of feeding react-router a "browser router", you can instead pass it a "memory router". As the name implies, the memory router holds state in memory (instead of using the History API).
I'm thinking you can wire up the events from Vue's router to react-router's memory router and get them in sync like that. I think you can call history.push inside of Vue's global after hooks.
Something like this:
// 🔴 I have not tested this, take this as inspiration
const router = new VueRouter({/* ... */});
const history = createMemoryHistory({/* ... */});
router.afterEach(({fullPath}) => {
history.replace(fullPath, {fromVueRouter: true});
});
history.listen(location => {
if (location.state?.fromVueRouter) return;
router.replace(location.pathname);
});
I'm using react-testing-library within a project of mine and am trying to write tests that validate in-app routing.
e.g. testing that a button on the AccessDenied page brings you back to the Home page.
I've been able to write these sorts of tests successfully for my App component because it defines all of the app routes. But if AccessDenied is one of those routes, how do I need to set up my tests to validate a button clicked there will route my back to Home?
Here is a contrived example:
App.tsx
<>
<Router>
<Route exact path="/" component={Home} />
<Route exact path="/access-denied" component={AccessDenied} />
</Router>
<Footer />
</>
AccessDenied.tsx
<div>
<div>Access Denied</div>
<p>You don't have permission to view the requested page</p>
<Link to="/">
<button>Go Home</button> <--- this is what i want tested
</Link>
</div>
As I said earlier the reason my tests work inside App.test.tsx is because my App component defines the routes inside itself, whereas my AccessDenied is just one of those routes. However, is it possible to leverage the router defined in my App.tsx in my AccessDenied.test.tsx tests? Perhaps I'm approaching this problem incorrectly? That's where I'm struggling. For reference, here is my working App.test.tsx tests.
App.test.tsx
describe('App', () => {
it('should allow you to navigate to login', async () => {
const history = createMemoryHistory()
const { findByTestId, getByTestId } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<AuthContext.Provider
value={{
authState: AUTH_STATES.UNAUTHENTICATED,
}}
>
<Router history={history}>
<App />
</Router>
</AuthContext.Provider>
</MockedProvider>,
)
fireEvent.click(getByTestId('sidebar-login-button'))
expect(await findByTestId('login-page-login-button')).toBeInTheDocument()
fireEvent.click(getByTestId('login-page-register-button'))
expect(await findByTestId('register-page-register-button')).toBeInTheDocument()
})
})
Any thoughts or suggestions are appreciated!
If you think about the responsibility of the AccessDenied component, it isn't really to send the user home. That's the overall behaviour you want, but the component's role in that is simply to send the user to "/". At the component unit level, therefore, the test could look something like this:
import React, { FC } from "react";
import { Link, Router } from "react-router-dom";
import { fireEvent, render, screen } from "#testing-library/react";
import { createMemoryHistory } from "history";
const AccessDenied: FC = () => (
<div>
<div>Access Denied</div>
<p>You don't have permission to view the requested page</p>
<Link to="/">
<button>Go Home</button>
</Link>
</div>
);
describe("AccessDenied", () => {
it("sends the user back home", () => {
const history = createMemoryHistory({ initialEntries: ["/access-denied"] });
render(
<Router history={history}>
<AccessDenied />
</Router>
);
fireEvent.click(screen.getByText("Go Home"));
expect(history.location.pathname).toBe("/");
});
});
Note that "/" is the default path, so if you don't provide initialEntries the test passes even if the click doesn't do anything...
At that point you might be thinking "but what if the home route changes?" If you moved the home page to "/home", for example, this test would continue to pass but the application would no longer actually work. This is a common problem with relying too much on very low-level tests and is where higher-level tests come into play, including:
Integration: render the whole App and use fireEvent to simulate navigation. This is challenging in your current setup, because the Router is at the App level; I'd move the Router to index.tsx and have a Switch in App.tsx instead, so you can render App within a MemoryRouter or use the createMemoryHistory method I show above (I've done this in this starter kit for example).
End-to-end: use a browser driver (e.g. Cypress or the various Selenium-based options) to automate actual user interactions with the app.
I haven't got as far as showing tests for routing, but do cover these different levels of test for a simple React app on my blog.
In React Router v6, you need to update the Router usage slightly (see "Cannot read properties of undefined (reading 'pathname')" when testing pages in the v6 React Router for details):
render(
<Router location={history.location} navigator={history}>
<AccessDenied />
</Router>
);
I'm trying to run a project based on session timeout in reactjs.. But unfortunately an error occurs.. This is the error,
'react-router' does not contain an export named 'browserHistory'.
I think you're using React Router 4. It moves around some of those modules. You'll either need to migrate your code to use v4, or downgrade React Router in your package.json.
React router provides a migration guide here:
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/migrating.md
I think you are using react-router latest version. In new version history managed by internally. you can just use like and history is avaliable in your component props like props.history
import React from 'react';
import { Route } from 'react-router';
import { HashRouter, Switch } from 'react-router-dom';
let RootApp = () => {
return (
<div>
<HashRouter>
<Switch>
<PrivateRoute path='/admin' component={AdminRoute} />
<Route path='/login' component={LoginComponet}></Route>
<Route path='/**' component={LoginComponet}></Route>
</Switch>
</HashRouter>
</div>
);
}
Trying to do the most simple example possible with React Router.
Every example I've looked at is very different and many include 20 page tutorials. I just want a simple root path to work:
...
<script src="https://unpkg.com/react#15.3.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom#15.3.2/dist/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
<script src="https://npmcdn.com/react-router/umd/react-router.min.js"></script>
...
var insert_point = document.querySelector('#container');
var App = React.createClass({
render: function() {
return (
<div>
<h1>Simple SPA</h1>
<ul>
<li>Home</li>
<li>Stuff</li>
<li>Contact</li>
</ul>
<div>
</div>
</div>
)
}
});
ReactDOM.render(
<ReactRouter.Router>
<ReactRouter.Route path="/" component={App}>
</ReactRouter.Route>
</ReactRouter.Router>,
insert_point
);
Since you're in the browser, you'll need react-router-dom too, a separate package in v4. react-router only contains the core, but for the DOM bindings you need the former package:
<script src="https://unpkg.com/react-router-dom/umd/react-router-dom.min.js"></script>
Then, get what you need from the global ReactRouterDOM. I find it easier just to use destructuring assignment to get what you need:
var { BrowserRouter, Route } = ReactRouterDOM;
Then you can do:
<BrowserRouter>
<Route exact path="/" component={App} />
</BrowserRouter>
The problem was that you weren't using the correct router -- for the web use BrowserRouter. Then, use Route and make sure to include the exact prop to only match the root route, in this case you can make Route self closing.
I suspect the problem is in how are you importing react router. When working with webpack I am using react-router-dom package and BrowserRouter
import { BrowserRouter as Router , Route, Switch} from 'react-router-dom'
Which are correct packages for V4. So at least I think you should use cdn that provides react-router-dom
also Try to use BrowserRouter instead of Router.
Aside from this, you can close your Route component immediately
<ReactRouter.Route path="/" component={App}/>
Also I do not understand your intentions with insertion_point as they are in your code.