How to access BrowserRouter history outside of BrowserRouter? - javascript

Below is my code snippet simplified. (using react-router-v5)
My question is how to get access to BrowserRouter's history in the logout_Handler(), given that I am "outside" BrowserRouter?
I've seen this answer How to access history object outside <Route /> in React Router v4
, but there is also a comment which applies to my case:
"When I try to use "withRouter" as shown here I get the error
You should not use <Route> or withRouter() outside a <Router>
which is exactly what I get, if I try to use withRouter at the App.js level.
// App.js
function App(props) {
const logout_Handler = (e) => {
localStorage.removeItem("app-tokenObj");
// how to get the history from BrowserRouter to redirect?!
//history.push("/login") ?? NO IDEA ??
}
return (
<BrowserRouter>
<nav>
<h3>My Supa Dupa App</h3>
<Link to="/">Home Page</Link>
<Link to="/admin">Admin Page</Link>
<button type="button" onClick={logout_Handler}>Logout</button>
</nav>
<hr/>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/link1" component={Comp1} />
<Route path="/link2" component={Comp1} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
</Switch>
</BrowserRouter>
)
}

If you are trying to access parts of a routing context in the App component then the router providing the context needs to be higher in the ReactTree. The solution is simple. Move BrowserRouter higher than App in your apps code. Wrapping App in the BrowserRouter would be sufficient enough for this.
Example index.js
ReactDOM.render(
...
<BrowserRouter>
<App />
</BrowserRouter>
...,
root,
);
App
The App component now has a routing context available. While you could use the withRouter Higher Order Component, using the useHistory hook would be simpler.
import { Link, Route, useHistory } from 'react-router-dom';
function App(props) {
const history = useHistory();
const logout_Handler = (e) => {
localStorage.removeItem("app-tokenObj");
history.push("/login");
}
return (
<>
<nav>
<h3>My Supa Dupa App</h3>
<Link to="/">Home Page</Link>
<Link to="/admin">Admin Page</Link>
<button type="button" onClick={logout_Handler}>Logout</button>
</nav>
<hr/>
<Switch>
<Route path="/link1" component={Comp1} />
<Route path="/link2" component={Comp1} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
<Route path="/" component={Home} />
</Switch>
</>
);
}

#for move one folder previous:
history.push("/../login")

Related

Simple authorization check causes redux store Provider to crash

I have an application built in React and Redux that's been working fine until I implemented a simple localStorage check to render either the App or an auth page I've called Gateway:
App.js
import React, { useState } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./redux/store";
import NavBar from "./components/NavBar";
import Settings from "./views/Settings";
import Gateway from "./views/Gateway";
import Profile from "./views/Profile";
import Signup from "./views/Signup";
import NewTestimonialPage from "./views/NewTestimonialPage";
import NotFoundPage from "./views/NotFoundPage";
import "./styles/App.scss";
function App() {
// new additions start here...
const [loggedIn, setLoggedIn] = useState(
localStorage.getItem("loggedIn") === "true"
);
if (!loggedIn) {
return <Gateway />;
}
...and end here
return (
<Router>
<Provider store={store}>
<NavBar />
<div className="navbar-dodger"></div>
<div className="App">
<Switch>
<Route exact path="/" component={Gateway} />
<Route path="/signup" component={Signup} />
<Route path="/settings/:userId" component={Settings} />
<Route path="/profile/:userId" component={Profile} />
<Route
path="/new-testimonial/:userId"
component={NewTestimonialPage}
/>
<Route path="/404" component={NotFoundPage} />
<Route component={NotFoundPage} />
</Switch>
</div>
</Provider>
</Router>
);
}
export default App;
Without the code between comments, everything was 100% fine.
Now, I throw this error:
Could not find "store" in the context of "Connect(EmailCheck)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(EmailCheck) in connect options.
If I render the provider inside that if-block as well, it works. Why? Why do I need a store if I'm not using redux in any way inside Gateway?
If you are using redux inside <Gateway> component then your component must be wrapped in <Provider>
It should look like :
return (
<Router>
<Provider store={store}>
{!loggedIn ?? <Gateway /> : <YourApplication />}
</Provider>
</Router>
);
Create a component for the app like
YourApplication.js:
return (
<>
<NavBar />
<div className="navbar-dodger"></div>
<div className="App">
<Switch>
<Route exact path="/" component={Gateway} />
<Route path="/signup" component={Signup} />
<Route path="/settings/:userId" component={Settings} />
<Route path="/profile/:userId" component={Profile} />
<Route
path="/new-testimonial/:userId"
component={NewTestimonialPage}
/>
<Route path="/404" component={NotFoundPage} />
<Route component={NotFoundPage} />
</Switch>
</div>
<>
);

React Router doesn't show component when i refresh

I have nested routes, the third level routes are failing.
This is my routing structure
yeah.... but the problem is,
App.js Routes to
-Home
-About
-Dashboard
Then Dashboard has children component to
-Profile (/dashboard/user)
-Account (/dashboard/account)
-Wallet (/dashboard/wallet)
-Settings (/dashboard/settings)
Then Settings has other children components
-Edit Profile (/dashboard/settings/editprofile)
-Edit Password and Pin (/dashboard/settings/editpassword)
-Edit Account Number (/dashboard/settings/editnumber)
The top two level routes are working, but the last one fails when i refresh the page, although it renders when i go back to the homepage and click on the links till i get to the last component, but once i refresh my browser it breaks, also it doesn't work when i type it manually.
Here is my App.js Route setup
import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
} from "react-router-dom";
const App = () => {
return (
<div className="App">
{/* setting up the routes */}
<Router>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/dashboard" component={dashboard} />
<Route path="/login" exact component={Login} />
<Route path="/register" exact component={SignUp} />
<Route path="/about" exact component={AboutServices} />
</Switch>
</Router>
</div>
);
};
My DashBoard.js
import { Route, useRouteMatch, NavLink, Switch } from "react-router-dom";
const App = () => {
let { path, url } = useRouteMatch();
return (
<div className="App">
<nav> Navavigation bar <nav>
{/* setting up the routes */}
<div className="MainBody">
<Switch>
<Route path={`${path}/wallet`} exact component={Wallet} />
<Route path={`${path}/profile`} component={Profile} />
<Route path={`${path}/account`} component={Account} />
<Route path={`${path}/settings`} exact component={Settings} />
</Switch>
</div>
</div>
);
};
Settings Page
import {
Switch,
Route,
useRouteMatch,
NavLink,
BrowserRouter,
} from "react-router-dom";
const Settings = (props) => {
let { path, url } = useRouteMatch();
return (
<div className="Settings">
<BrowserRouter>
<nav> Navavigation bar <nav>
<div className="SettingsWrapper">
<Switch>
<Route path={`${path}/editprofile`} component={EditProfile} />
<Route
path={`${path}/changepassword`}
component={ChangePassword}
/>
<Route path={`${path}/changepin`} component={ChangePin} />
<Route
path={`${path}/accountsettings`}
component={BankSettings}
/>
</Switch>
</div>
</div>
</BrowserRouter>
</div>
);
};
export default Settings;
I am 99% sure your issue is because you are using more than 1 router. Remove the BrowserRouter around your Settings UI. I am guessing when the nested routes aren't navigated to via links from the outer router that the match prop isn't initialized as you expect it to be.
const Settings = (props) => {
let { path, url } = useRouteMatch();
return (
<div className="Settings">
<nav>Navigation bar</nav>
<div className="SettingsWrapper">
<Switch>
<Route path={`${path}/editprofile`} component={EditProfile} />
<Route
path={`${path}/changepassword`}
component={ChangePassword}
/>
<Route path={`${path}/changepin`} component={ChangePin} />
<Route
path={`${path}/accountsettings`}
component={BankSettings}
/>
</Switch>
</div>
</div>
);
};
Edit
Ok, when removing the nested router and creating my own codesandbox I found a small, subtle but important "quirk" of nesting routes. Any nested route that is rendering further routes can not specify the exact prop on the route.
const App = () => {
let { path, url } = useRouteMatch();
return (
<div className="App">
<nav>Navigation bar</nav>
{/* setting up the routes */}
<div className="MainBody">
<Switch>
...
<Route
path={`${path}/settings`}
exact // <-- exact match precludes sub-routes!!
component={Settings}
/>
</Switch>
</div>
</div>
);
};
So if for example the path was "/dashboard/settings/editprofile" the path no longer exactly matches the route path and the route is not rendered.
Solution
Simply omit the exact prop for nested routes rendering sub-routes. Remember that route paths are to be considered "prefixes", so without the exact prop specified that path "/dashboard/settings/editprofile" can be matched by "/dashboard/settings".
const Dashboard = () => {
let { path, url } = useRouteMatch();
return (
<div className="App">
<nav>Dashboard Navigation bar </nav>
<NavLink to={`${url}/settings`}>Settings</NavLink>
{/* setting up the routes */}
<div className="MainBody">
<Switch>
...
<Route path={`${path}/settings`} component={Settings} /> // <-- no exact match
</Switch>
</div>
</div>
);
};

Using react routes for routing in a react application

I am trying to setup the routes for a simple react application and react-router doesn't seem to match the given routes in the switch defaults the 404 page
Here is the code for the routes:
import {
BrowserRouter as Router,
Route,
Switch,
HashRouter
} from "react-router-dom";
window.React = React;
render(
<Router>
<Switch>
<HashRouter>
<Route exact path="/" component={App} />
<Route path="list-days" component={App} />
<Route path="add-day" component={AddDayForm} />
<Route component={Whoops404} />
</HashRouter>
</Switch>
</Router>,
document.getElementById("root")
);
And here is the code for the links to those routes:
import { Link } from "react-router-dom";
import { IoIosHome as Home } from "react-icons/io";
import { FaCalendarPlus } from "react-icons/fa";
import { FaTable } from "react-icons/fa";
export const Menu = () => (
<nav className="Menu">
<Link to="/" activeclassname="selected">
<Home />
</Link>
<Link to="/add-day" activeclassname="selected">
<FaCalendarPlus />
</Link>
<Link to="/list-days" activeclassname="selected">
<FaTable />
</Link>
</nav>
When you click anything but the home link the 404 page is displayed.
You use 'links' as HTML structure and have this not inherited in import from "react-router-dom";
Per seeing the documentation of react this all is provided with this import statement.
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
HashRouter
} from "react-router-dom";
Add a / to your Routes' path.
<Route path="/list-days" component={App} />
The problem is, you have defined your Route like
<Route path="list-days" component={App} />
and you are accessing those links as
<Link to="/list-days" activeclassname="selected"></Link>
You can see the difference that your are adding a front slash when accessing through Link. Just put a front slash on Route like this:
<Route path="/list-days" component={App} />
will solve the issue.
Thanks.
Probably because you are using hash router.
try this:
return (
<HashRouter>
<Switch>
<Route exact path="#/" component={App} />
<Route path="#/list-days" component={App} />
<Route path="#/add-day" component={AddDayForm} />
<Route component={Whoops404} />
<Switch>
</HashRouter>
)
or why not just switch to browserRouter so you wouldn't need those hashes on your url?
Try to remove HashRouter and edit your route to be like this
<Router>
<Switch>
<Route exact path="/" component={App} />
<Route exact path="/list-days" component={App} />
<Route exact path="/add-day" component={AddDayForm} />
<Route exact path="*" component={Whoops404} />
</Switch>
</Router>

404 page in REACT

I created the component NotFound and it works fine when I go to a page that doesn't exist. But the same page it's appearing in all my pages, not only the one that doesn't exist. This is the component:
import React from 'react'
const NotFound = () =>
<div>
<h3>404 page not found</h3>
<p>We are sorry but the page you are looking for does not exist.</p>
</div>
export default NotFound
And this is how I used it in the main page:
class MainSite extends Component {
render () {
return (
<div>
{/* Render nav */}
<Route path='/dashboard' component={Nav} />
<Route path='/retrospectives' component={Nav} />
<Route path='/users' component={Nav} />
<Route path='/projects' component={Nav} />
{/* Dashboard page */}
<ProtectedRoute exact path='/dashboard' component={DashboardPage} />
{/* Retrospectives page */}
<ProtectedRoute exact path='/retrospectives' component={RetrospectivesPage} />
{/* Users page */}
<ProtectedRoute exact path='/users' component={UsersPage} />
{/* Projects page */}
<ProtectedRoute exact path='/projects' component={ProjectsPage} />
{/* Retrospective related pages */}
<Route exact path='/retrospectives/:retrospectiveId' component={Retrospective} />
<Route exact path='/join-retrospective' component={JoinRetrospective} />
<ProtectedRoute exact path='/create-retrospective/:retrospectiveId' component={Retrospective} />
{/* OnBoarding pages */}
<ProtectedRoute exact path='/beta-code' component={BetaCodeAccess} />
<Route exact path='/auth-handler' component={AuthHandler} />
<Route exact path='/join-organization' component={JoinOrganization} />
</div>
)
}
}
export default MainSite
As you can see I use <Route path="*" component={NotFound} /> to create the 404 pages, but that component is appearing in every existing page as well. How can I fix this?
Try this one:
import { Switch, Route } from 'react-router-dom';
<Switch>
<Route path='/dashboard' component={Nav} />
<Route path='/retrospectives' component={Nav} />
<Route path='/users' component={Nav} />
<Route path='/projects' component={Nav} />
<Route path="" component={NotFound} />
</Switch>
All below example works fine:
<Route path="" component={NotFound} /> // empty ""
<Route path="*" component={NotFound} /> // star *
<Route component={NotFound} /> // without path
Or if you want to return a simple 404 message without any component:
<Route component={() => (<div>404 Not found </div>)} />
For those who are looking for an answer using react-router-dom v6, many things had changed. Switch for example doesn't exists anymore, you have to use element instead of component, ... Check this little example to get you an idea:
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
import './index.css'
import App from './App'
const Test = () => (
<h1>404</h1>
)
ReactDOM.render(
<React.StrictMode>
<Router>
<Routes>
<Route path='/' element={<App />} />
<Route path='*' element={<Test />}/>
</Routes>
</Router>
</React.StrictMode>,
document.getElementById('root')
)
With this you are defining your home route and all the other routes will show 404. Check the official guide for more info.
Try This:
import React from "react";
import { Redirect, Route, Switch, BrowserRouter } from 'react-router-dom';
import HomePage from './pages/HomePage.jsx';
import NotFoundPage from './NotFoundPage.jsx';
class App extends React.Component {
render(){
return(
<BrowserRouter>
<Switch>
<Route exact path='/' component={HomePage} />
<Route path="*" component={NotFoundPage} />
</Switch>
</BrowserRouter>
)
}
}
export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Simply import Switch from react-router-dom and wrap all your Routes in the Switch Component. Also Important here is to note to keep your 404Page component at the very bottom(just before your switch ending tag) This way it will match each component with its route first. If it matches, it will render the component or else check the next one. Ultimately if none matching routes will be founded, it will render 404Page
react router is a headache for new coders. Use this code format. This is class component but you can make a functional component and use it.
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import HomePage from './pages/HomePage.jsx';
import NotFoundPage from './NotFoundPage.jsx';
import Footer from './Footer';
class App extends React.Component {
render(){
return(
<Router>
<Routes>
<Route exact path='/' element={HomePage} />
<Route path="*" element={NotFoundPage} />
</Routes>
<Routes>
<Route path="/" element={Footer}/>
</Routes>
</Router>
)
}
}
export default App;

React-Router only one child

I keep on getting the error:
A 'Router' may have only one child element
when using react-router.
I can't seem to figure out why this is not working, since it's exactly like the code they show in their example: Quick Start
Here is my code:
import React from 'react';
import Editorstore from './Editorstore';
import App from './components/editor/App';
import BaseLayer from './components/baselayer';
import {BrowserRouter as Router, Route} from 'react-router-dom';
import {render} from 'react-dom';
const root = document.createElement('div');
root.id = 'app';
document.body.appendChild(root);
const store = new Editorstore();
const stylelist = ['https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css', 'https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.2/semantic.min.css', 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css', 'https://api.tiles.mapbox.com/mapbox-gl-js/v0.33.1/mapbox-gl.css'];
stylelist.map((link) => {
const a = document.createElement('link');
a.rel = 'stylesheet';
a.href = link;
document.body.appendChild(a);
return null;
});
render((
<Router>
<Route exact path="/" component={BaseLayer} />
<Route path="/editor" component={App} store={store} />
</Router>
), document.querySelector('#app'));
You have to wrap your Route's in a <div>(or a <Switch>).
render((
<Router>
<Route exact path="/" component={BaseLayer} />
<Route path="/editor" component={App} store={store} />
</Router>
), document.querySelector('#app'));
should be
render((
<Router>
<div>
<Route exact path="/" component={BaseLayer} />
<Route path="/editor" component={App} store={store} />
</div>
</Router>
), document.querySelector('#app'));
jsfiddle / webpackbin
This is an API change in react-router 4.x. Recommended approach is to wrap Routes in a Switch: https://github.com/ReactTraining/react-router/issues/4131#issuecomment-274171357
Quoting:
Convert
<Router>
<Route ...>
<Route ...>
</Router>
to
<Router>
<Switch>
<Route ...>
<Route ...>
</Switch>
</Router>
You will, of course, need to add Switch to your imports:
import { Switch, Router, Route } from 'react-router'
I Always use Fragment in react web and native ( >= react 16 )
import React, { Component, Fragment } from 'react'
import { NativeRouter as Routes, Route, Link } from 'react-router-native'
import Navigation from './components/navigation'
import HomeScreen from './screens/home'
import { RecipesScreen } from './screens/recipe'
class Main extends Component {
render() {
return (
<Fragment>
<Navigation />
<Routes>
<Fragment>
<Route exact path="/" component={HomeScreen} />
<Route path="/recipes" component={RecipesScreen} />
</Fragment>
</Routes>
</Fragment>
)
}
}
export default Main
I put all my <Route /> tags inside the <Switch> </Switch> tag like this.
<BrowserRouter>
<Switch>
<Route path='/' component={App} exact={true} />
<Route path='/form-example' component={FormExample} />
</Switch>
</BrowserRouter>
This solves the problem.
If you are nesting other components inside the Router you should do like.
<Router>
<div>
<otherComponent/>
<div>
<Route/>
<Route/>
<Route/>
<Route/>
</div>
</div>
</Router>
If you are using Reach Routers make sure the Code looks like this:
<Router>
<Login path="/" />
<Login path="/login" />
</Router>
Including these Components in a Div in the case of React Routers will make this work but In Reach Routers, Remove that Div Element.
you can also wrap all your route in a parent route which defaults to index page
<Router history={history}>
<Route path="/" component={IndexPage}>
<Route path="to/page" component={MyPage}/>
<Route path="to/page/:pathParam" component={MyPage}/>
</Route>
</Router>
I am using react-router-dom package with version 5.0.1 and it works perfectly fine with your code.
import { BrowserRouter as Router , Router, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
...
class App extends React.Component {
render() {
return (
<Router>
<ul>
<li><Link path='/'>Home</Link></li>
<li><Link path='/about'>About</Link></li>
</ul>
<Route path='/' exact component={Home} />
<Route path='/about' component={About} />
</Router>
);
}
}
export default App;
Not sure if my router might be too simple, or there was a change to this rule but was following along a tutorial that mentioned this limitation (A 'Router' may have only one child element) and it allowed me to add 3 routes without giving any errors. This is the working code:
function render() {
ReactDOM.render(
<BrowserRouter>
<Route exact path="/" component={App} />
<Route path="/add" component={AddAuthorForm} />
<Route path="/test" component={test} />
</BrowserRouter>
,
document.getElementById('root')
);
}
And this are my dependencies:
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.1.2",
"react-scripts": "3.4.1",
This problem occurs when you don't have parent tag before <Route>inside <Router> so to resolve this problem keep the <Route>enclosed in a parent tag such as <div> , <p> etc.
Example -
<Router>
<p>
<Route path="/login" component={Login} />
<Route path="/register" component={Register} />
</p>
</Router>

Categories