I have faced a problem. I am used react-router-dom for routing. It's working well but goBack is not working properly. When I clicked back button it's 1st going to NotFound/Signin page then redirect to back page. How can I overcome this issue?
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory';
import Signin from '../ui/signin/Signin';
import AddEvent from '../ui/events/AddEvent';
import EventView from '../ui/events/EventView';
import NotFound from '../ui/NotFound';
const history = createBrowserHistory();
const privatePages = [
'/events',
'/addevent',
];
const publicPages = ['/', '/signup','/forgotpassword'];
const onEnterPublicPage = () => {
if (Meteor.userId()) {
history.replace('/events');
}
};
const onEnterPrivatePage = () => {
if (!Meteor.userId()) {
history.replace('/');
}
};
export const onAuthenticationChange = (isAuthenticated) => {
const pathname = this.location.pathname;
const isUnauthenticatedPage = publicPages.includes(pathname);
const isAuthenticatedPage = privatePages.includes(pathname);
if (isAuthenticated && isUnauthenticatedPage) {
history.replace('/events');
} else if (!isAuthenticated && isAuthenticatedPage) {
history.replace('/');
}
}
export const routes = (
<Router history = {history}>
<Switch>
<Route
exact path="/events"
component={ListEvents}
onEnter={onEnterPrivatePage} />
<Route
exact path="/addevent"
component={AddEvent}
onEnter={onEnterPrivatePage} />
<Route component={NotFound}/>
<Route
exact path="/"
component={Signin}
onEnter={onEnterPublicPage} />
</Switch>
</Router>
);
In the component :
constructor(props){
super(props);
this.goBack = this.goBack.bind(this);
}
goBack(){
this.props.history.goBack();
// this.props.history.push.go(-1);
}
In the return
<Link
to=""
onClick={this.goBack}
className="back-icon">
Back
</Link>
Its because you are using history.replace('/'). You are replacing, not pushing so there is no previous route.
One possible way is, Instead of using Link, use history.push to change the route dynamically. To achieve that remove the Link component and define the onClick event on "li" or "button". Now first perform all the task inside onClick function and at the end use history.push to change the route means to navigate on other page.
I hope this helps
I have had the same issue and the history.goBack() function doesn't work with <Link /> component, but if you replace it for any other it will work
Related
My react app is inside a java struts project, which includes a header. There is a certain element in that header that changes depending on certain routes being hit.
For this it would be much simpler to listen to when a route changes where my Routes are defined. As opposed to doing it in every route.
Here is my
App.js
import {
BrowserRouter as Router,
Route,
useHistory,
useLocation,
Link
} from "react-router-dom";
const Nav = () => {
return (
<div>
<Link to="/">Page 1 </Link>
<Link to="/2">Page 2 </Link>
<Link to="/3">Page 3 </Link>
</div>
);
};
export default function App() {
const h = useHistory();
const l = useLocation();
const { listen } = useHistory();
useEffect(() => {
console.log("location change");
}, [l]);
useEffect(() => {
console.log("history change");
}, [h]);
h.listen(() => {
console.log("history listen");
});
listen((location) => {
console.log("listen change");
});
return (
<Router>
<Route path={"/"} component={Nav} />
<Route path={"/"} component={PageOne} exact />
<Route path={"/2"} component={PageTwo} exact />
<Route path={"/3"} component={PageThree} exact />
</Router>
);
}
None of the console logs get hit when clicking on the links in the Nav component. Is there a way around this?
I have a CodeSandbox to test this issue.
Keep in mind that react-router-dom passes the navigation object down the React tree.
You're trying to access history and location in your App component, but there's nothing "above" your App component to provide it a history or location.
If you instead put your useLocation and useHistory inside of PageOne/PageTwo/PageThree components, it works as intended.
Updated your codesandbox:
https://codesandbox.io/s/loving-lamarr-bzspg?fontsize=14&hidenavigation=1&theme=dark
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
)
}
}
React D3 component did not get unmounted upon (component) redirect using the following approach. That is, in a SPA, while on 'graphA', clicking a button redirects to 'graphB'. 'graphB' is rendered, however, 'graphA' is still visible. Thoughts on how I can remove/unmount 'graphA' such that only 'graphB' is visible. I tried calling ReactDOM.unmountComponentAtNode() in various React lifecycle hooks with no success.
this.props.history.push({ pathname: '/graphB' })
Assuming you're using React Router, this is how to use a Switch component. It will render the component for the first path that matches.
import React, { Component } from 'react';
import { render } from 'react-dom';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
const A = () => <h1>A</h1>
const B = () => <h1>B</h1>
const Landing = ({ history }) => {
const goToA = () => {
history.push('a');
}
const goToB = () => {
history.push('b');
}
return (
<div>
<button onClick={goToA}>Go to A</button>
<button onClick={goToB}>Go to B</button>
</div>
)
}
const App = ({ history }) => {
return (
<Switch>
<Route exact path="/" component={Landing} />
<Route path="/a" component={A} />
<Route path="/a" component={A} />
</Switch>
)
}
render(
<BrowserRouter>
<App />
</BrowserRouter>
, document.getElementById('root'));
Live example here.
Ive been trying to do a simple redirect to another component on button click, but for some reason it doesnt work.I want to redirect to '/dashboard' and display AdminServices from login as follows:
//index.js
ReactDOM.render(<BrowserRouter><App /></BrowserRouter>,
document.getElementById("root"));
//App.js
<Switch>
<Route path="/" component={Login} />
<Route path="/dashboard" component={AdminServices} />
</Switch>
//Login.js
<Button
onClick={this.login}
>
</Button>
login = () =>{
<Redirect to="/dashboard" />
}
//AdminServices.js
render(){
return(
<div>Test</div>
);
}
Instead of using the props.history, a better way is using the useHistory hook like below:
import { useHistory } from 'react-router-dom'
const MyComponent = (props) => {
const history = useHistory();
handleOnSubmit = () => {
history.push(`/dashboard`);
};
};
In react-router-dom v6 you can use useNavigate(), as answered here.
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
navigate('/dashboard');
if you're still using previous versions, you can use useHistory, as Tellisense mentioned.
import { useHistory } from 'react-router-dom';
const navigate = useHistory();
navigate.push('/dashboard');
You can route by function by like this
handleOnSubmit = () => {
this.props.history.push(`/dashboard`);
};
Hope this help
Simple, use NavLink instead of button
import { NavLink } from 'react-router-dom'
<NavLink to="/dashboard"> Dashboard </NavLink>
https://reacttraining.com/react-router/web/api/NavLink
<Switch> component render only the first route that matches. You just need to swap your <Route> components in <Switch> case and use #Adnan shah answer to make redirect function.
P.S
react router auth example
This happened to me once I had missed importing the component to the route.js file.
You need to reference/import files from the component to the routefile.
Use an a tag
<a href="/" />
Maybe this might help
import { useHistory } from "react-router-dom";
const history = useHistory();
<Button
onClick={this.login}
>
</Button>
login = () =>{
history.push("/about");
}
With react-router-dom v6 you will need to use useNavigate instead of useHistory:
import { useNavigate } from "react-router-dom";
export default function MyComponent(){
const navigate = useNavigate();
handleOnClick = () => {
navigate("/dashboard");
};
};
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>
);