In my react app, I am using the useHistory hook to redirect to my Home Component. Why is useHistory undefined, and how would I solve this problem?
App.js
import 'bootstrap/dist/css/bootstrap.css'
import React from "react";
import { Button } from 'react-bootstrap';
import { BrowserRouter, Route, useHistory} from 'react-router-dom';
import { Switch } from 'react-router-dom/cjs/react-router-dom.min';
import './App.css';
import Home from './components/Home/Home';
const App = () =>
{
const history = useHistory()
const handleClick = () =>
{
console.log(history)
console.log("handle click")
// history.push("/home") this line throws "cannot read property of undefined" error
}
return (
<>
<BrowserRouter>
<Button onClick = {handleClick} variant = "primary">Get Started</Button>
<Switch>
<Route path="/home" exact component = {Home}/>
</Switch>
</BrowserRouter>
</>
);
}
export default App;
Try to use the useHistory() outside this component and see it work. It seems you are calling the function before the Router itself and both are imported from react-router-dom .
Related
I have routes.js -
import React from 'react'
import { Switch, Route , Link } from 'react-router';
import {BrowserRouter, Router} from 'react-router-dom';
import SignIn from './Components/Login/SignIn';
import MainPage from './Components/MainPage';
function Routes() {
return (
<Router>
<Switch>
<Route path="/logout" component={SignIn} ></Route>
<Route path="/home" component={MainPage} ></Route>
</Switch>
</Router>
)
}
export default Routes
Within SignIn.js I have button from which I want to render to main page.
Main bits of SignIn.Js are as below -
import MainPage from '../MainPage';
import { useHistory } from 'react-router-dom';
<FormGroup>
<Button style={{width:'100%',backgroundColor:"#FCB724",color:"black",fontWeight:"bold"}} onClick={NavigateToMainPage} >Sign in using our secure server</Button>
</FormGroup>
function NavigateToMainPage(){
let path = `/home`;
let history = useHistory();
history.push(path);
}
This is not navigating.
How can I navigate on button click to another component in this case ?
I'm not sure how this code isn't breaking completely because you can't use a hook outside of a react component, you can't use it in a normal function.
const Form = () => {
const history = useHistory()
return <form>
<button onClick={() => history.push('/home')}>Example</button>
</form>
}
You want something like that.
You have to change how you call a function inside an on click prop.
If you write onClick={functionName}, the functionName would be always executed when the component is rendered and it would produce an infinite loop.
You have to change it into this onClick={() => functionName()} means that the functionName will be executed only after the the onclick is executed.
Don't forget to add return to the component.
Also if you use hook (like something starts from use word), you have to put it inside the functional component.
Here is the full code:
import MainPage from '../MainPage';
import { useHistory } from 'react-router-dom';
const SignIn = () => {
const NavigateToMainPage = () => {
let path = `/home`;
let history = useHistory();
history.push(path);
}
return(
<FormGroup>
<Button
style={{width:'100%',backgroundColor:"#FCB724",color:"black",fontWeight:"bold"}}
onClick={() => NavigateToMainPage()}
>
Sign in using our secure server
</Button>
</FormGroup>
)
}
export default SignIn
Additional info:
I personally use const (ES6 arrow function https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) rather than function to declare a function.
In the MainPage.js, you can directly import the Route, Switch, and Link from react-router-dom.
So you can change it from this:
import {Switch, Route , Link} from 'react-router';
import {BrowserRouter, Router} from 'react-router-dom';
into this:
import {BrowserRouter, Router, Switch, Route, Link} from 'react-router-dom';
I'd implement your example using NavLink component instead of useHistory hook.
import MainPage from '../MainPage';
import { NavLink} from 'react-router-dom';
<FormGroup>
<NavLink to="/home" style={{width:'100%',backgroundColor:"#FCB724",color:"black",fontWeight:"bold"}} onClick={NavigateToMainPage}>Sign in using our secure server</NavLink>
</FormGroup>
To top it off, please use const variables, forget about var.
I have a very basic setup, trying out react router v6. But I always get the errormessage mentioned in the headline, when I click on the "go home" button. How can I fix this?
App.js
render({AppRoutes})
AppRoutes.js
import { BrowserRouter, Routes, Route, HashRouter } from 'react-router-dom';
import React from 'react';
import DefaultComponent from './scenes/Default/Default2';
import TestComponent from './scenes/Default/TestComponent';
const AppRoutes = (
<HashRouter>
<Routes>
<Route path="/" element = {<DefaultComponent/>}>
<Route path="test" element={<TestComponent/>}/>
</Route>
</Routes>
</HashRouter>
);
export default AppRoutes;
DefaultComponent.js
import React from 'react';
import { useNavigate } from 'react-router-dom';
function DefaultComponent() {
let navigate = useNavigate();
function handleClick() {
navigate('/test');
}
return (
<div>
<button onClick={handleClick}>go home</button>
</div>
);
}
export default DefaultComponent;
TestComponent.js
import React from 'react';
function TestComponent (){
return (
<h1>This is a test!</h1>
);
}
export default TestComponent;
Using React ^16.13.1 and react-router-dom ^5.2.0, We have multiple Navigation files to make nested navigation, the first Navigation.js runs and redirects fine, but the second Navigation.js does not work as we expected.
Created a react APP using npx create-react-app nested
Listing the important files for review:
App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Navigation from "./Navigation";
import { BrowserRouter } from "react-router-dom";
const App = () => {
return (
<BrowserRouter>
<Navigation />
</BrowserRouter>
);
};
export default App;
Navigation.js
import React from "react";
import { Switch, Route, BrowserRouter } from "react-router-dom";
import nestedNavigation from "./nested/Navigation";
const NotFound = () => <h1>Not Found</h1>;
const Navigation = () => {
return (
<Switch>
<Route exact path="/welcome" component={nestedNavigation} />
<Route path="/" component={NotFound} />
</Switch>
);
};
export default Navigation;
nested/Navigation.js nested navigation - the second one
import React from "react";
import {
Switch,
Route,
BrowserRouter,
useRouteMatch,
} from "react-router-dom";
import Welcome from "../Welcome"
const Navigation = () => {
let { path, url } = useRouteMatch();
debugger;
return (
<Switch>
<Route path={`${path}/nested`} exact component={Welcome} />
</Switch>
);
}
export default Navigation;
Nested routes require the full path in the most recent full release version of React Router, add the rest of the URL from the upper components to the path prop. codesandbox from react-router Docs
Also remove the exact from your welcome. Sub-routes wont likely work with exact because they aren’t exactly that route!
I expect that console.log('Refresh') runs every time the route changes (switching from Component1 to Component2). But it's only triggering on first render. Why?
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(<BrowserRouter><App /></BrowserRouter>, document.getElementById('root'));
App.js:
import React, { useEffect } from 'react';
import { Switch, Route } from 'react-router-dom';
import Nav from './Nav';
import Component1 from './Component1';
import Component2 from './Component2';
const App = () => {
useEffect( () => console.log('Refresh'));
return (
[<Switch>
<Route component = {Nav}/>
</Switch>,
<Switch>
<Route exact path = '/component1' component = {Component1}/>
<Route exact path = '/component2' component = {Component2}/>
</Switch>]
);
}
export default App;
Nav.js:
import React from 'react';
import { Link } from 'react-router-dom';
const Nav = () => {
return (
<div>
<Link to = '/component1'>Component 1</Link>
<Link to = '/component2'>Component 2</Link>
</div>
);
}
export default Nav;
Component1.js:
import React from 'react';
const Component1 = () => {
return (
<div>
<p>Hi</p>
</div>
);
}
export default Component1;
Component2.js:
import React from 'react';
const Component2 = () => {
return (
<div>
<p>Bye</p>
</div>
);
}
export default Component2;
The useEffect is not triggered because the App component is not re-rendered, nothing changed in that component (no state or props update).
If you want the App component to re-render when the route change, you can use the withRouter HOC to inject route props, like this :
import { Switch, Route, withRouter } from 'react-router-dom';
const App = () => {
useEffect( () => console.log('Refresh'));
return (...);
}
export default withRouter(App);
Example : https://codesandbox.io/s/youthful-pare-n8p1y
use the key attribute so everytime we render new component (different key)
<Route path='/mypath/:username' exact render= {routeProps =><MyCompo {...routeProps} key={document.location.href} />} />
Use the 2nd argument to useEffect to conditionally apply effect. For example via react-router-dom, you get some properties
const { schoolId, classId } = props
useEffect(() => {
// fetch something here
}, [schoolId, classId)
Here [schoolId, classId acts as the unique identifier for useEffect to trigger.
Using Hooks:
use useLocation and useLayoutEffect get more efficiency:
import { useLocation } from "react-router-dom";
//...
const location = useLocation();
//...
useLayoutEffect(() => {
console.log("location",location)
}, [location])
I have a Dashboard with rotating slides, each of which has a corresponding tab in Bldgs. Both Dashboard.js and Bldgs.js are children to my App.js.
When a user clicks on a specific slide A in Dashboard.js, Dashboard needs to tell App.js so that App can tell Bldgs.js to have tab A displayed when it routes to Bldgs.
I believe that I am passing the correct index value from Dashboard up to App and down to Bldgs. However, an error is being thrown in my App.js file stating:
Uncaught TypeError: Cannot read property 'push' of undefined
My code was working fine before I started passing my handleClick() function to my Dashboard component.
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import injectTapEventPlugin from 'react-tap-event-plugin';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import { BrowserRouter as Router } from 'react-router-dom';
import { hashHistory } from 'react-router';
// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
injectTapEventPlugin();
ReactDOM.render(
<MuiThemeProvider>
<Router history={hashHistory}>
<App />
</Router>
</MuiThemeProvider>,
document.getElementById('root')
);
App.js
import React from 'react';
import { Route } from 'react-router-dom';
import Dashboard from './Dashboard';
import Bldgs from './Bldgs';
var selectedTab;
class App extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
selectedTab = 0;
}
handleClick(value) {
selectedTab = value;
// console.log(selectedTab);
this.props.history.push('/Bldgs');
// console.log(this.props);
}
render() {
var _this = this;
return (
<div>
<Route exact path="/" render={(props) => <Dashboard {...props} handleClick={_this.handleClick} />} />
<Route path="/Bldgs" component={Bldgs} curTab={selectedTab} />
</div>
);
}
}
export default App;
Dashboard.js
import React, { Component } from 'react';
import './Dashboard.css';
import { AutoRotatingCarousel, Slide } from 'material-auto-rotating-carousel';
...
var curIndex;
class Dashboard extends Component {
constructor(props) {
super(props);
this.handleEnter = this.handleEnter.bind(this);
this.handleChange = this.handleChange.bind(this);
curIndex = 0;
}
handleEnter(e) {
// console.log(curIndex);
this.props.handleClick(curIndex);
}
handleChange(value) {
// console.log(value);
curIndex = value;
}
...
}
export default Dashboard;
Bldgs.js
...
var curTab;
class Bldgs extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.goHome = this.goHome.bind(this);
curTab = 0;
}
handleChange(value) {
this.setState({'selectedTab': value});
console.log(this.state);
}
goHome(e) {
this.props.history.push('/');
}
...
}
export default Bldgs;
In order to make use of history in the App component use it with withRouter. You need to make use of withRouter only when your component is not receiving the Router props,
This may happen in cases when your component is a nested child of a component rendered by the Router or you haven't passed the Router props to it or when the component is not linked to the Router at all and is rendered as a separate component from the Routes.
import React from 'react';
import { Route , withRouter} from 'react-router-dom';
import Dashboard from './Dashboard';
import Bldgs from './Bldgs';
var selectedTab;
class App extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
selectedTab = 0;
}
handleClick(value) {
selectedTab = value;
// console.log(selectedTab);
this.props.history.push('/Bldgs');
// console.log(this.props);
}
render() {
var _this = this;
return (
<div>
<Route exact path="/" render={(props) => <Dashboard {...props} handleClick={_this.handleClick} />} />
<Route path="/Bldgs" component={Bldgs} curTab={selectedTab} />
</div>
);
}
}
export default withRouter(App);
Documentation on withRouter
for React-router V4
change the function to
onClick={this.fun.bind(this)}
fun() {
this.props.history.push("/Home");
}
and
import { withRouter } from 'react-router-dom';
export it later as:
export default withRouter (comp_name);
When working with functional components we can use useHistory() to history to push method
import { useHistory } from "react-router-dom";
and then in function we have to assign the useHistory()
let history = useHistory();
Now We can use history.push to relocate to desired page
history.push('/asdfg')
You are trying to push with out giving the task to a valid library. on App.js
So:
on App.js
You are using BrowserRouter which is handling the history of pages by default, with this react-router-dom function you are relying on BrowserRouter to do this fore you.
Use Router instead of BrowserRouter to gain control of you're history, use history to control the behavior.
Use npm history "yarn add history#4.x.x" / "npm i history#4.x.x"
import Route from 'react-router-dom'; //don't use BrowserRouter
import createBrowserHistory from 'createBrowserHistory';
Remember to exoport it !!
export const history = createBrowserHistory();
4.import history to Dashboard.js
5.import history to Bldgs.js
hope this helps !!!
#brunomiyamotto
See my App.js file below. I ended up passing {...this.props} to the NavBar Component that I had created.
The NavBar component is not part of my Switch routes.
import React, { Component } from 'react';
import { Route, Link, Redirect, Switch, withRouter } from 'react-router-dom'
import Navbar from './Navbar.js';
import Home from './Home/HomeCont';
import Login from './Register/Login';
import Dashboard from './Dashboard/DashboardCont';
import './App.css';
class App extends Component {
constructor(props){
super(props);
}
render() {
return (
<div className="App">
<header className="App-header">
//SOLUTION!!!
<Navbar {...this.props}/>
</header>
<Switch>
<React.Fragment>
<Route exact path="/" render={(props) => <Home {...props}/>}/>
<Route exact path="/login" render={(props) => <Login {...props}/>}/>
</React.Fragment>
</Switch>
</div>
);
}
}
export default withRouter(App);
Within my Navbar. The button I used had the following code. I had to .bind(this) to see the history and be able to user this.props.history.push("/")
<Button
color="inherit"
onClick={this.handleLogout.bind(this)}>Logout</Button>
My mistake was the wrong import in conjuction with BrowserRouter, ie:
incorrect:
import { useHistory } from 'react-router'
correct:
import { useHistory } from 'react-router-dom'
I solved this error by wrapping the component inside the BrowserRouter.
Don't forget about this, it's a very common mistake.
import { BrowserRouter} from 'react-router-dom';
<BrowserRouter>
<Menu/>
<BrowserRouter>
Only the router childrens receive the history hook.