I'm using react-router-dom version 6.0.2 here and the "Render" props isn't working, every time I got to the url mentioned in the Path of my Route tag it keeps throwing me this error - "Matched leaf route at location "/addRecipe" does not have an element. This means it will render an with a null value by default resulting in an "empty" page.". Can someone please help me with this issue
import './App.css';
import Home from './components/Home';
import AddRecipe from './components/AddRecipe';
import items from './data';
import React, { useState } from 'react';
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom';
const App = () => {
const [itemsList, setItemsList] = useState(items)
const addRecipe = (recipeToAdd) => {
setItemsList(itemsList.concat([recipeToAdd]));
}
const removeItem = (itemToRemove) => {
setItemsList(itemsList.filter(a => a!== itemToRemove))
}
return (
<Router>
<Routes>
<Route path="/addRecipe" render={ ({history}) => {
return (<AddRecipe onAddRecipe={(newRecipe) => {
addRecipe(newRecipe);
history.push('/');
} } />);
} } />
</Routes>
</Router>
);
}
export default App;
In react-router-dom version 6, you should use element prop for this.
I suggest your read their document on upgrading from version 5 where they explain the changes.
For your problem, you should write something like this:
<Route
path="/addRecipe"
element={
<AddRecipe
onAddRecipe={(newRecipe) => {
addRecipe(newRecipe);
history.push('/');
}
/>
}
/>
The Route component API changed significantly from version 5 to version 6, instead of component and render props there is a singular element prop that is passed a JSX literal instead of a reference to a React component (via component) or a function (via render).
There is also no longer route props (history, location, and match) and they are accessible only via the React hooks. On top of this RRDv6 also no longer surfaces the history object directly, instead abstracting it behind a navigate function, accessible via the useNavigate hook. If the AddRecipe component is a function component it should just access navigate directly from the hook. If it unable to do so then the solution is to create a wrapper component that can, and then render the AddRecipe component with the corrected onAddRecipe callback.
Example:
const AddRecipeWrapper = ({ addRecipe }) => {
const navigate = useNavigate();
return (
<AddRecipe
onAddRecipe={(newRecipe) => {
addRecipe(newRecipe);
navigate('/');
}}
/>
);
};
...
const App = () => {
const [itemsList, setItemsList] = useState(items);
const addRecipe = (recipeToAdd) => {
setItemsList(itemsList.concat([recipeToAdd]));
};
const removeItem = (itemToRemove) => {
setItemsList(itemsList.filter(a => a !== itemToRemove))
};
return (
<Router>
<Routes>
<Route
path="/addRecipe"
element={<AddRecipeWrapper addRecipe={addRecipe} />}
/>
</Routes>
</Router>
);
};
so I am using React's context API in my Gatsby app(which is written in React basically) to deal with user authentication. I have two components that use that context: dashboard and navBar. When I try to log in and log out, my navBar will behave differently according to my userContext, but my dashboard won't respond. Is it something related to the structure, like navBar is the direct "children" to layout, but dashboard is not? I assume not though, after all, that's why I use contextAPI then just pass a normal prop.
Here are the codes:
//layout.js
import React, { useContext, useState, createContext } from "react"
import Navbar from "../components/navBar"
import {monitorAuth} from "../firebase/firebaseService"
export const UserStateContext = createContext(null)
export const SetUserContext = createContext()
const Layout = ({ children }) => {
const [user, setUser] = useState()
console.log(user)
monitorAuth(setUser)// everytime a layout component renders, it will grab a user if it is logged inthen setUser, then I will use it in the context
return (
<>
<UserStateContext.Provider value={user}>
<SetUserContext.Provider value={setUser}>
<div>
<SEO />
<Navbar />
<main>{children}</main>
</div>
</SetUserContext.Provider >
</UserStateContext.Provider>
</>
)
}
export default Layout
import React, { useState, useContext } from "react"
import AppBar from "#material-ui/core/AppBar"
import { signOut } from "../firebase/firebaseService"
import {UserStateContext} from "./layout"
export default function NavBar() {
const user = useContext(UserStateContext)
console.log(user) // when I log in/ log out, it will console.log the right user status, user/null
const renderMenu = () => {
return (
<>
{user? (
<>
<Button onClick={signOut}>Sign Out</Button>
<Button>My profile</Button>
</>)
:<Button>Sign In</Button> }
</>
)
}
return (
<AppBar position="static" className={classes.root}>
...
{renderMenu()}
...
</AppBar>
)
}
//dashboard.js
import React, { useContext } from 'react'
import Layout from '../components/layout'
import LoggedIn from '../components/dashboard/loggedIn'
import NotLoggedIn from '../components/dashboard/notLoggedIn'
import {UserStateContext} from "../components/layout"
const Dashboard = props => {
console.log("within dashboard")
const user = useContext(UserStateContext)
console.log(user)
const renderDashboard = () =>{
return (
<>
{user? <LoggedIn /> : <NotLoggedIn />}
</>
)
}
return(
<Layout>
{renderDashboard()}
</Layout>
)
}
export default Dashboard
One more clue, I console.log user in all three components and when I refresh the page:
within dashboard
dashboard.js:17 null
layout.js:15 undefined
navBar.jsx:54 undefined
layout.js:15 [user...]
navBar.jsx:54 [user...]
layout.js:15 [user...]
That means, at first, user is not set yet, so all three components log the user as undefined, but later, layout detect the user and then updates it, so navbarknows too, but dashboard doesn't. Is it something about re-render? Thanks!
The reason it's not working is because your <Dashboard> component is not a child of the context provider. If you use React devtools, you'll see the component tree looks like
<Dashboard>
<Layout>
<UserStateContext.Provider>
<SetUserContext.Provider>
...
</SetUserContext.Provider>
</UserStateContext.Provider>
</Layout>
</Dashboard>
When the context value changes, it looks for components in its subtree that useContext. However, Dashboard is not a child, it's the parent!
If you want to follow this pattern, a solution may be to create a parent component of Dashboard and put the context there.
I am new to react and i am creating a simple Contact Manager App, i have contacts and addContact components, these are separated in different routes.
When i add a contact,it gets added to the local storage and the contacts component is getting the list of contacts from the local storage.
The problem that when i add a contact and i redirect to the contacts page, the new contact doesn't show unless i manually refresh the page.
i tried this :
this.props.history.push('/');
this too
window.location.href=window.location.hostname;
and i created a function to refresh the page if the previous link is the addContact link, it doesn't work either
this is the App component code:
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Navbar from './components/navbar';
import Contacts from './components/contacts';
import About from './components/pages/about';
import AddContact from './components/pages/addContact'
import NotFound from './components/pages/notFound'
function getContactsLS() {
let contacts;
if (localStorage.getItem('contacts') === null) {
contacts = [];
}
else {
contacts = JSON.parse(localStorage.getItem('contacts'));
}
contacts = Array.from(contacts);
return contacts;
}
class App extends React.Component {
contactsLS = getContactsLS();
render() {
return (
<Router>
<Navbar />
<div className="container">
<Switch>
<Route exact path="/add-contact" component={AddContact} />
<Route exact path="/" component={() => <Contacts contacts={this.contactsLS} />} />
<Route exact path="/about" component={About} />
<Route component={NotFound}/>
</Switch>
</div>
</Router>
)
}
}
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>
this is addContact function :
addContactEvent = () =>{
let contacts;
if(localStorage.getItem('contacts')===null)
contacts=[];
else{
contacts=JSON.parse(localStorage.getItem('contacts'));
}
contacts=Array.from(contacts);
console.log(contacts);
this.setState({key:contacts.length});
const contact = {
key : contacts.length,
name: this.state.name,
email:this.state.email,
phone:this.state.phone
}
contacts.push(contact);
localStorage.setItem('contacts', JSON.stringify(contacts));
//redirection
//this.props.history.push('/');
window.location.href=window.location.hostname;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
It feels like you would want the contacts as part of the state. In that case the code would be:
state = { contactLS: getContactsLS()}
But as we are calling values from localStorage a more appropriate place might be
componentDidMount() method.From react docs
Avoid introducing any side-effects or subscriptions in the
constructor. For those use cases, use componentDidMount() instead.
Then the code would be
componentDidMount(){
const contacts = getContactsLS()
this.setState({ contacsLS: contacts })
}
Don't forget to change to this.state.contactsLS
<Route exact path="/" component={() => <Contacts contacts={this.state.contactsLS} />} />
====
PS
Another problem i can see in the existing code is the misuse of component constructor.
This is not obvious but the line
contactsLS = getContactsLS();
is equivalent to
constrcutor(){
this.contactLS = getContactLS();
}
from react documentation we read
Typically, in React constructors are only used for two purposes:
- Initializing local state by assigning an object to this.state.
- Binding
event handler methods to an instance.
You could use <Redirect> component from react-router-dom package. To get it working you initially setState where redirect is set to false.
this.state = {
redirect: false
}
Once the addContact opeartion is done, update the state
this.setState({
redirect: true
})
Inside of your AddContact component, the state will decide if it has to show the actual component or to redirect to the attached path.
Something like this:
import {Redirect } from 'react-router-dom';
class AddContact extends React.Component {
//Add the initial state
// addContact Operation
render(){
if(this.state.redirect)
return <Redirect to="/" />
return (
//The markup for AddContact
)
}
}
I am using the last version react-router module, named react-router-dom, that has become the default when developing web applications with React. I want to know how to make a redirection after a POST request. I have been making this code, but after the request, nothing happens. I review on the web, but all the data is about previous versions of the react router, and no with the last update.
Code:
import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { Redirect } from 'react-router'
import SignUpForm from '../../register/components/SignUpForm';
import styles from './PagesStyles.css';
import axios from 'axios';
import Footer from '../../shared/components/Footer';
class SignUpPage extends React.Component {
constructor(props) {
super(props);
this.state = {
errors: {},
client: {
userclient: '',
clientname: '',
clientbusinessname: '',
password: '',
confirmPassword: ''
}
};
this.processForm = this.processForm.bind(this);
this.changeClient = this.changeClient.bind(this);
}
changeClient(event) {
const field = event.target.name;
const client = this.state.client;
client[field] = event.target.value;
this.setState({
client
});
}
async processForm(event) {
event.preventDefault();
const userclient = this.state.client.userclient;
const clientname = this.state.client.clientname;
const clientbusinessname = this.state.client.clientbusinessname;
const password = this.state.client.password;
const confirmPassword = this.state.client.confirmPassword;
const formData = { userclient, clientname, clientbusinessname, password, confirmPassword };
axios.post('/signup', formData, { headers: {'Accept': 'application/json'} })
.then((response) => {
this.setState({
errors: {}
});
<Redirect to="/"/> // Here, nothings happens
}).catch((error) => {
const errors = error.response.data.errors ? error.response.data.errors : {};
errors.summary = error.response.data.message;
this.setState({
errors
});
});
}
render() {
return (
<div className={styles.section}>
<div className={styles.container}>
<img src={require('./images/lisa_principal_bg.png')} className={styles.fullImageBackground} />
<SignUpForm
onSubmit={this.processForm}
onChange={this.changeClient}
errors={this.state.errors}
client={this.state.client}
/>
<Footer />
</div>
</div>
);
}
}
export default SignUpPage;
You have to use setState to set a property that will render the <Redirect> inside your render() method.
E.g.
class MyComponent extends React.Component {
state = {
redirect: false
}
handleSubmit () {
axios.post(/**/)
.then(() => this.setState({ redirect: true }));
}
render () {
const { redirect } = this.state;
if (redirect) {
return <Redirect to='/somewhere'/>;
}
return <RenderYourForm/>;
}
You can also see an example in the official documentation: https://reacttraining.com/react-router/web/example/auth-workflow
That said, I would suggest you to put the API call inside a service or something. Then you could just use the history object to route programatically. This is how the integration with redux works.
But I guess you have your reasons to do it this way.
Here a small example as response to the title as all mentioned examples are complicated in my opinion as well as the official one.
You should know how to transpile es2015 as well as make your server able to handle the redirect. Here is a snippet for express. More info related to this can be found here.
Make sure to put this below all other routes.
const app = express();
app.use(express.static('distApp'));
/**
* Enable routing with React.
*/
app.get('*', (req, res) => {
res.sendFile(path.resolve('distApp', 'index.html'));
});
This is the .jsx file. Notice how the longest path comes first and get's more general. For the most general routes use the exact attribute.
// Relative imports
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';
// Absolute imports
import YourReactComp from './YourReactComp.jsx';
const root = document.getElementById('root');
const MainPage= () => (
<div>Main Page</div>
);
const EditPage= () => (
<div>Edit Page</div>
);
const NoMatch = () => (
<p>No Match</p>
);
const RoutedApp = () => (
<BrowserRouter >
<Switch>
<Route path="/items/:id" component={EditPage} />
<Route exact path="/items" component={MainPage} />
<Route path="/yourReactComp" component={YourReactComp} />
<Route exact path="/" render={() => (<Redirect to="/items" />)} />
<Route path="*" component={NoMatch} />
</Switch>
</BrowserRouter>
);
ReactDOM.render(<RoutedApp />, root);
React Router v5 now allows you to simply redirect using history.push() thanks to the useHistory() hook:
import { useHistory } from "react-router-dom"
function HomeButton() {
let history = useHistory()
function handleClick() {
history.push("/home")
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
)
}
Simply call it inside any function you like.
this.props.history.push('/main');
Try something like this.
import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { Redirect } from 'react-router'
import SignUpForm from '../../register/components/SignUpForm';
import styles from './PagesStyles.css';
import axios from 'axios';
import Footer from '../../shared/components/Footer';
class SignUpPage extends React.Component {
constructor(props) {
super(props);
this.state = {
errors: {},
callbackResponse: null,
client: {
userclient: '',
clientname: '',
clientbusinessname: '',
password: '',
confirmPassword: ''
}
};
this.processForm = this.processForm.bind(this);
this.changeClient = this.changeClient.bind(this);
}
changeClient(event) {
const field = event.target.name;
const client = this.state.client;
client[field] = event.target.value;
this.setState({
client
});
}
processForm(event) {
event.preventDefault();
const userclient = this.state.client.userclient;
const clientname = this.state.client.clientname;
const clientbusinessname = this.state.client.clientbusinessname;
const password = this.state.client.password;
const confirmPassword = this.state.client.confirmPassword;
const formData = { userclient, clientname, clientbusinessname, password, confirmPassword };
axios.post('/signup', formData, { headers: {'Accept': 'application/json'} })
.then((response) => {
this.setState({
callbackResponse: {response.data},
});
}).catch((error) => {
const errors = error.response.data.errors ? error.response.data.errors : {};
errors.summary = error.response.data.message;
this.setState({
errors
});
});
}
const renderMe = ()=>{
return(
this.state.callbackResponse
? <SignUpForm
onSubmit={this.processForm}
onChange={this.changeClient}
errors={this.state.errors}
client={this.state.client}
/>
: <Redirect to="/"/>
)}
render() {
return (
<div className={styles.section}>
<div className={styles.container}>
<img src={require('./images/lisa_principal_bg.png')} className={styles.fullImageBackground} />
{renderMe()}
<Footer />
</div>
</div>
);
}
}
export default SignUpPage;
Update for react-router-dom v6, there is a useNavigate hook for condtional redirection and Link component
import { useEffect } from 'react';
import { useNavigate, Link } from 'react-router-dom';
export default function Example(): JSX.Element {
const navigate = useNavigate();
useEffect(() => {
...
if(true) { // conditional redirection
navigate('/not-found', { replace: true });
}
}, []);
return (
<>
...
<Link to="/home"> Home </Link> // relative link navigation to /home
...
</>
);
}
useNavigate
Relative Link Component
Alternatively, you can use withRouter. You can get access to the history object's properties and the closest <Route>'s match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.
import React from "react"
import PropTypes from "prop-types"
import { withRouter } from "react-router"
// A simple component that shows the pathname of the current location
class ShowTheLocation extends React.Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
}
render() {
const { match, location, history } = this.props
return <div>You are now at {location.pathname}</div>
}
}
// Create a new component that is "connected" (to borrow redux
// terminology) to the router.
const ShowTheLocationWithRouter = withRouter(ShowTheLocation)
Or just:
import { withRouter } from 'react-router-dom'
const Button = withRouter(({ history }) => (
<button
type='button'
onClick={() => { history.push('/new-location') }}
>
Click Me!
</button>
))
The problem I run into is I have an existing IIS machine. I then deploy a static React app to it. When you use router, the URL that displays is actually virtual, not real. If you hit F5 it goes to IIS, not index.js, and your return will be 404 file not found. How I resolved it was simple. I have a public folder in my react app. In that public folder I created the same folder name as the virtual routing. In this folder, I have an index.html with the following code:
<script>
{
sessionStorage.setItem("redirect", "/ansible/");
location.href = "/";
}
</script>
Now what this does is for this session, I'm adding the "routing" path I want it to go. Then inside my App.js I do this (Note ... is other code but too much to put here for a demo):
import React, { Component } from "react";
import { Route, Link } from "react-router-dom";
import { BrowserRouter as Router } from "react-router-dom";
import { Redirect } from 'react-router';
import Ansible from "./Development/Ansible";
import Code from "./Development/Code";
import Wood from "./WoodWorking";
import "./App.css";
class App extends Component {
render() {
const redirect = sessionStorage.getItem("redirect");
if(redirect) {
sessionStorage.removeItem("redirect");
}
return (
<Router>
{redirect ?<Redirect to={redirect}/> : ""}
<div className="App">
...
<Link to="/">
<li>Home</li>
</Link>
<Link to="/dev">
<li>Development</li>
</Link>
<Link to="/wood">
<li>Wood Working</li>
</Link>
...
<Route
path="/"
exact
render={(props) => (
<Home {...props} />
)}
/>
<Route
path="/dev"
render={(props) => (
<Code {...props} />
)}
/>
<Route
path="/wood"
render={(props) => (
<Wood {...props} />
)}
/>
<Route
path="/ansible/"
exact
render={(props) => (
<Ansible {...props} checked={this.state.checked} />
)}
/>
...
</Router>
);
}
}
export default App;
Actual usage: chizl.com
EDIT: changed from localStorage to sessionStorage. sessionStorage goes away when you close the tab or browser and cannot be read by other tabs in your browser.
NOTE: Answering just the title of the question
Previous Version
<Redirect from="/old-url" to="/new-url" />
Latest version
<Route path="/old-url" element={<Navigate to="/new-url" />} />
In v6 of react-router you can accomplish this using <Navigate/> tag as there is no <Redirect/> Component.
In my case. I was required to maintain the connection to the server between /Home route and /chat route; setting window.location to something would re-render that destroys client-server connection I did this.
<div className="home-container">
{redirect && <Navigate to="/chat"/>}
<div className="home__title">
....
<div className="home__group-list" onClick={handleJoin}>
</div>
const [redirect, doRedirect] = useState(false)
handleJoin changes the state of redirect to true.
you can write a hoc for this purpose and write a method call redirect, here is the code:
import React, {useState} from 'react';
import {Redirect} from "react-router-dom";
const RedirectHoc = (WrappedComponent) => () => {
const [routName, setRoutName] = useState("");
const redirect = (to) => {
setRoutName(to);
};
if (routName) {
return <Redirect to={"/" + routName}/>
}
return (
<>
<WrappedComponent redirect={redirect}/>
</>
);
};
export default RedirectHoc;
"react": "^16.3.2",
"react-dom": "^16.3.2",
"react-router-dom": "^4.2.2"
For navigate to another page (About page in my case), I installed prop-types. Then I import it in the corresponding component.And I used this.context.router.history.push('/about').And it gets navigated.
My code is,
import React, { Component } from 'react';
import '../assets/mystyle.css';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';
export default class Header extends Component {
viewAbout() {
this.context.router.history.push('/about')
}
render() {
return (
<header className="App-header">
<div className="myapp_menu">
<input type="button" value="Home" />
<input type="button" value="Services" />
<input type="button" value="Contact" />
<input type="button" value="About" onClick={() => { this.viewAbout() }} />
</div>
</header>
)
}
}
Header.contextTypes = {
router: PropTypes.object
};
Alternatively, you can use React conditional rendering.
import { Redirect } from "react-router";
import React, { Component } from 'react';
class UserSignup extends Component {
constructor(props) {
super(props);
this.state = {
redirect: false
}
}
render() {
<React.Fragment>
{ this.state.redirect && <Redirect to="/signin" /> } // you will be redirected to signin route
}
</React.Fragment>
}
Hi if you are using react-router v-6.0.0-beta or V6 in This version Redirect Changes to Navigate like this
import { Navigate } from 'react-router-dom'; // like this CORRECT in v6
import { Redirect } from 'react-router-dom'; // like this CORRECT in v5
import { Redirect } from 'react-router-dom'; // like this WRONG in v6
// This will give you error in V6 of react-router and react-router dom
please make sure use both same version in package.json
{
"react-router": "^6.0.0-beta.0", //Like this
"react-router-dom": "^6.0.0-beta.0", // like this
}
this above things only works well in react Router Version 6
The simplest solution to navigate to another component is( Example
navigates to mails component by click on icon):
<MailIcon
onClick={ () => { this.props.history.push('/mails') } }
/>
To navigate to another component you can use this.props.history.push('/main');
import React, { Component, Fragment } from 'react'
class Example extends Component {
redirect() {
this.props.history.push('/main')
}
render() {
return (
<Fragment>
{this.redirect()}
</Fragment>
);
}
}
export default Example
I found that place to put the redirect complent of react-router is in the method render, but if you want to redirect after some validation, by example, the best way to redirect is using the old reliable, window.location.href, i.e.:
evalSuccessResponse(data){
if(data.code===200){
window.location.href = urlOneSignHome;
}else{
//TODO Something
}
}
When you are programming React Native never will need to go outside of the app, and the mechanism to open another app is completely different.
I want 2 pages in my Chrome extension. For example: first(default) page with list of users and second with actions for this user.
I want to display second page by clicking on user(ClickableListItem in my case). I use React and React Router. Here the component in which I have:
class Resents extends React.Component {
constructor(props) {
super(props);
this.handleOnClick = this.handleOnClick.bind(this);
}
handleOnClick() {
console.log('navigate to next page');
const path = '/description-view';
browserHistory.push(path);
}
render() {
const ClickableListItem = clickableEnhance(ListItem);
return (
<div>
<List>
<ClickableListItem
primaryText="Donald Trump"
leftAvatar={<Avatar src="img/some-guy.jpg" />}
rightIcon={<ImageNavigateNext />}
onClick={this.handleOnClick}
/>
// some code missed for simplicity
</List>
</div>
);
}
}
I also tried to wrap ClickableListItem into Link component(from react-router) but it does nothing.
Maybe the thing is that Chrome Extensions haven`t their browserHistory... But I don`t see any errors in console...
What can I do for routing with React?
I know this post is old. Nevertheless, I'll leave my answer here just in case somebody still looking for it and want a quick answer to fix their existing router.
In my case, I get away with just switching from BrowserRouter to MemoryRouter. It works like charm without a need of additional memory package!
import { MemoryRouter as Router } from 'react-router-dom';
ReactDOM.render(
<React.StrictMode>
<Router>
<OptionsComponent />
</Router>
</React.StrictMode>,
document.querySelector('#root')
);
You can try other methods, that suits for you in the ReactRouter Documentation
While you wouldn't want to use the browser (or hash) history for your extension, you could use a memory history. A memory history replicates the browser history, but maintains its own history stack.
import { createMemoryHistory } from 'history'
const history = createMemoryHistory()
For an extension with only two pages, using React Router is overkill. It would be simpler to maintain a value in state describing which "page" to render and use a switch or if/else statements to only render the correct page component.
render() {
let page = null
switch (this.state.page) {
case 'home':
page = <Home />
break
case 'user':
page = <User />
break
}
return page
}
I solved this problem by using single routes instead of nested. The problem was in another place...
Also, I created an issue: https://github.com/ReactTraining/react-router/issues/4309
This is a very lightweight solution I just found. I just tried it - simple and performant: react-chrome-extension-router
I just had to use createMemoryHistory instead of createBrowserHistory:
import React from "react";
import ReactDOM from "react-dom";
import { Router, Switch, Route, Link } from "react-router-dom";
import { createMemoryHistory } from "history";
import Page1 from "./Page1";
import Page2 from "./Page2";
const history = createMemoryHistory();
const App: React.FC<{}> = () => {
return (
<Router history={history}>
<Switch>
<Route exact path="/">
<Page1 />
</Route>
<Route path="/page2">
<Page2 />
</Route>
</Switch>
</Router>
);
};
const root = document.createElement("div");
document.body.appendChild(root);
ReactDOM.render(<App />, root);
import React from "react";
import { useHistory } from "react-router-dom";
const Page1 = () => {
const history = useHistory();
return (
<button onClick={() => history.push("/page2")}>Navigate to Page 2</button>
);
};
export default Page1;
A modern lightweight option has presented itself with the package wouter.
You can create a custom hook to change route based on the hash.
see wouter docs.
import { useState, useEffect } from "react";
import { Router, Route } from "wouter";
// returns the current hash location in a normalized form
// (excluding the leading '#' symbol)
const currentLocation = () => {
return window.location.hash.replace(/^#/, "") || "/";
};
const navigate = (to) => (window.location.hash = to);
const useHashLocation = () => {
const [loc, setLoc] = useState(currentLocation());
useEffect(() => {
// this function is called whenever the hash changes
const handler = () => setLoc(currentLocation());
// subscribe to hash changes
window.addEventListener("hashchange", handler);
return () => window.removeEventListener("hashchange", handler);
}, []);
return [loc, navigate];
};
const App = () => (
<Router hook={useHashLocation}>
<Route path="/about" component={About} />
...
</Router>
);