Trying on simple navigation with react-router-dom. Managed to have it works with this.props.history.push('/pathName'); However, I have doubts about my understanding and implementation, appreciate your 2 cents on correcting my mistakes.
Am I in the right direction?
My use case scenario:
Firstly, I have 3 pages with a common component <Navbar> implemented on top of each page. Navbar consists of 2 buttons Page A and Page B. User should be navigated to either screen when the respective button is clicked.
Homepage
Page A
Page B
My Implementation:
Homepage.js - (Besides the class name is different, both Page A and B has the same implementation)
import React from 'react';
import { Redirect } from "react-router-dom";
import Navbar from '../common/navbar';
class Homepage extends React.Component{
callBackFromNavBar = (value) => {
NavigationHelper.historyPush(this.props, value);
}
render(){
return (
<div>
<Navbar callback={this.callBackFromNavBar}/>
</div>
);
}
}
export default Homepage;
NavigationHelper.js
export const historyPush = (props,value) => {
console.log('helper calling');
switch(value){
case 'PageA':
props.history.push('/PageA');
break;
case 'PageB':
props.history.push('/PageB');
break;
default:
break;
}
}
Navbar - Following shows how the value is being pass back to parent
<Button variant="contained" onClick={ () => {
props.callback('PageA');
} }>Page A</Button>
Learning source:
https://dev.to/projectescape/programmatic-navigation-in-react-3p1l
Link to Sandbox
https://codesandbox.io/embed/react-router-dom-navigation-4tpzs?fontsize=14&hidenavigation=1&theme=dark
I would try something like this to simplify things
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import PageA from './'
import PageB from './'
function App() {
return (
<Router>
<Navbar />
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/pageA" component={PageA} />
<Route exact path="/pageB" component={PageB} />
</Switch>
</Router>
);
}
And then in your Navbar component just setup the <Link> to each of those pages, here i have setup the routes in the App.js file however i have seen people just use a dedicated component for routing but find what works best for you
Related
As an exercise, I'm making a react app (still learning React) that implements a login system with firebase. Of course, to implement such a feature, react router is necessary and I have successfully implemented it. However, once the user logs in he should be able to see a sidebar alongside other content that is changed dynamically. I now need to again use react router to change those pages when a user clicks on a specific item in the sidebar without having to render the sidebar with each component. I have read the docs for nesting routers but just cant get it to work. Any help is greatly appreciated.
Here's the code:
App.js:
import "./App.css";
import LoginForm from "./components/LoginForm";
import { AuthProvider } from "./contexts/AuthContext";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Dashboard from "./components/Dashboard";
import PrivateRoute from "./components/PrivateRoute";
function App() {
return (
<div className="App">
<Router>
<AuthProvider>
<Switch>
<PrivateRoute exact path="/" component={Dashboard} />
<Route path="/login" component={LoginForm} />
</Switch>
</AuthProvider>
</Router>
</div>
);
}
export default App;
Dashboard.js:
import React from "react";
import { useAuth } from "../contexts/AuthContext";
import { useHistory } from "react-router";
import Sidebar from "./Sidebar/Sidebar";
import { useRouteMatch } from "react-router";
const Dashboard = () => {
const { currentUser, logout } = useAuth();
const history = useHistory();
let { path, url } = useRouteMatch();
const handleLogout = async () => {
try {
await logout();
history.push("/login");
} catch (error) {
console.log(error);
}
};
if (!currentUser) return null;
return (
<div>
<Sidebar logout={handleLogout} />
</div>
);
};
export default Dashboard;
PS. I'm quite new to react and any tip/critique is welcome
You can always conditionally render the sidebar.
function Sidebar() {
const { currentUser } = useAuth()
if (!currentUser) return null
// ...
}
Within your App component, just render the Sidebar component outside of the Switch:
function App() {
return (
<div className="App">
<Router>
<AuthProvider>
<Sidebar />
<Routes />
</AuthProvider>
</Router>
</div>
);
}
function Routes() {
const { currentUser } = useAuth()
return (
<Switch>
{currentUser && <PrivateRoutes />}
<PublicRoutes />
</Switch>
)
}
Basically all you need to do is render the sidebar on all routes. If you need to render custom Sidebar content based off of routes, you can add another Switch within Sidebar. You can add as many Switch components as you want as long as they are within your Router.
Even though i understand what your trying to do, i don't think you should mind put the sidebar inside the component.
React is powerfull enough to cache a lots of stuffs and disable unnecessary renders. I think the path you should go its figure out how to use wisely useCallback useMemo, memo and make all the tricks to prevent re-renders inside the sidebar components. This way you can reuse the sidebarcomponent, or any component, without to think about location.
I'm trying to build a simple example project where the user is redirected to the 'contact' page upon clicking a button, using React. I'm trying to achieve this by setting the value of a state property. When I run the code I have, it does change the browser address bar URL to that of the contact page, but does not seem to actually load the component - I get a blank page instead. If I manually navigate to that URL (http://localhost:3000/contact) I can see the contents.
Here are my App.js and Contact.js files -
App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Switch, Route, Link, Redirect } from 'react-router-dom';
import Contact from './Contact';
class App extends Component {
state = {
redirect: false
}
setRedirect = () => {
this.setState({
redirect: true
})
}
renderRedirect = () => {
if (this.state.redirect) {
return <Redirect to='/contact' />
}
}
render() {
return (
<Router>
<Switch>
<Route exact path='/contact' component={Contact} />
</Switch>
<div>
{this.renderRedirect()}
<button onClick={this.setRedirect}>Redirect</button>
</div>
</Router>
)
}
}
export default App;
Contact.js
import React, { Component } from 'react';
class Contact extends Component {
render() {
return (
<div>
<h2>Contact Me</h2>
<input type="text"></input>
</div>
);
}
}
export default Contact;
Using state isn't really a requirement for me, so other (preferably simpler) methods of redirection would be appreciated too.
Since your button is nothing more than a link, you could replace it with:
<Link to="/contact">Redirect</Link>
There are many alternatives though, you could for example look into BrowserRouter's browserHistory:
import { browserHistory } from 'react-router'
browserHistory.push("/contact")
Or perhaps this.props.history.push("/contact").
There are pros and cons to every method, you'll have to look into each and see which you prefer.
I got here for a similiar situation. It's possible use withRouter (https://reactrouter.com/web/api/withRouter) to handle that.
This example was tested with "react": "^16.13.1","react-router-dom": "^5.2.0" and "history": "^5.0.0" into "dependecies" sections in package.json file.
In App.js I have the BrowserRouter (usually people import BrowserRouter as Router, I prefer work with original names) with Home and Contact.
App.js
import React, { Component } from "react";
import {
BrowserRouter,
Switch,
Route,
} from "react-router-dom";
import Home from "./pages/Home";
import Contact from "./pages/Contact";
class App extends Component
{
// stuff...
render()
{
return (
<BrowserRouter>
<Switch>
<Route path="/contact">
<Contact />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</BrowserRouter>
);
}
}
export default App;
ASIDE 1: The Route with path="/contact" is placed before path="/" because Switch render the first match, so put Home at the end. If you have path="/something" and path="/something/:id" place the more specific route (with /:id in this case) before. (https://reactrouter.com/web/api/Switch)
ASIDE 2: I'm using class component but I believe (I didn't test it) a functional component will also work.
In Home.js and Contact.js I use withRouter associated with export keyword. This makes Home and Contact components receive the history object of BrowserRouter via props. Use method push() to add "/contact" and "/" to the history stack. (https://reactrouter.com/web/api/history).
Home.js
import React from "react";
import {
withRouter
} from "react-router-dom";
export const Home = ( props ) =>
{
return (
<div>
Home!
<button
onClick={ () => props.history.push( "/contact" ) }
>
Get in Touch
<button>
</div>
);
}
export default withRouter( Home );
Contact.js
import React from "react";
import {
withRouter
} from "react-router-dom";
export const Contact = ( props ) =>
{
return (
<div>
Contact!
<button
onClick={ () => props.history.push( "/" ) }
>
Go Home
<button>
</div>
);
}
export default withRouter( Contact );
Particularly, I'm using also in a BackButton component with goBack() to navigate backwards:
BackButton.js
import React from "react";
import {
withRouter
} from "react-router-dom";
export const BackButton = ( props ) =>
{
return (
<button
onClick={ () => props.history.goBack() }
>
Go back
<button>
);
}
export default withRouter( BackButton );
So I could modify the Contact to:
Contact.js (with BackButton)
import React from "react";
import BackButton from "../components/BackButton";
export const Contact = ( props ) =>
{
return (
<div>
Contact!
<BackButton />
</div>
);
}
export default Contact; // now I'm not using history in this file.
// the navigation responsability is inside BackButton component.
Above was the best solution for me. Other possible solutions are:
useHistory Hook (https://reactrouter.com/web/api/Hooks)
work with Router instead BrowserRouter - (https://reactrouter.com/web/api/Router)
I want to implement a button, in the AppBar provided by Material UI, that when clicked, it redirects me to a new page (from '/' to '/login'). So, I used history.push( ) and it works. But, the page does not render another component that it should display. The component is a function. How can I make this work?
I'm new to ReactJS and I'm using stuff from Material-UI so I don't how to tweak things to my favor.
It's a lot of code, so I made a working example for those who want to see what I mean:
https://codesandbox.io/embed/6jmlxlj3wz?fontsize=14
A few things:
In your App.js you are passing history to Router but you haven't imported it...
import React, { Component } from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import Home from './components/Home';
import Login from './components/Login';
import history from './history'; // import it to use it
class App extends Component {
render() {
return (
<Router history={history}>
<div className="App">
<Switch>
<Route exact path="/" component={Home} /> // notice I also added exact
<Route path="/login" component={Login} />
</Switch>
</div>
</Router>
);
}
}
export default App;
And here is your Home.js cleaned up... (no need for the handleClick function...)
function gotoLogin() {
history.push('/login');
}
function ContainedButtons(props) {
const { classes } = props;
return (
<div>
<Button
variant="contained"
color="secondary"
className={classes.button}
onClick={gotoLogin}
>
Login
</Button>
</div>
);
}
See a working demo here: https://codesandbox.io/s/ovwrm11wl5
Use react-router-dom to manage your routes and for history.push functionality. Its support for React is built in so it will be a much more declarative coding experience for you.
i am making a login page that redirect the user after a successful login to home page i am using react router dom i tried to look for a simple way to do it but i couldn't find :
import Authen from './Pages/Authen';
import Home from './Pages/Home';
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom';
class App extends Component {
render() {
return (
<div className="App">
<Router>
<div>
<ul>
</ul>
<Route exact path="/" component={Authen}/>
<Route path="Home" component={Home}/>
</div>
</Router>
</div>
thank you for your help i really appreciate it :)
login page that redirect the user after a successful login to home
page
Use the withRouter higher order component that comes with react-router-dom. It will give your component acess to the history prop. With the history prop you can push to any new URL.
import React from 'react'
import {withRouter} from 'react-router-dom'
class Authen extends React.Component {
onLogin = () => {
// also other authentication code
this.props.history.push('/home')
}
render() {
return (
<button onClick={() => this.onLogin()}> Login </button>
)
}
}
export default withRouter(Authen);
First off, I have read through just about every example I can find and looked through various boilerplates to see how others have done this. I am having issues loading pages when clicking <Link>'s with react-router v4. I have also installed the package react-router-connected and have been trying that out as well but no improvement can be seen (however it shows the changes in the redux-logger which is nice).
Currently, the url updates just fine and if I manually change the url and press enter, then the next page will load. But, it will not redirect if I click a link. I am also using create-react app for the project, just for your reference. My actual app is setup as the exact example from usage with react-router in the official redux docs. For simplicity, I have changed my routes to only include links to basic components that do nothing but redirect to one another.
Root.js which houses my routes
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router'
// import App from './App';
import NewComponent from './NewComponent';
import OldComponentent from './OldComponent';
const Root = ({ store, history }) => (
<Provider store={store}>
<ConnectedRouter history={history}>
<Router>
<Switch>
<Route exact path='/' component={OldComponentent}/>
<Route path='/new' component={NewComponent}/>
{/* <Route path='/' component={App}/>
<Route path='/:filter' component={App}/> */}
</Switch>
</Router>
</ConnectedRouter>
</Provider>
)
export default Root;
Home component
import React from 'react';
import { connect } from 'react-redux';
import { push } from 'connected-react-router';
import { withRouter } from 'react-router';
import { Link } from 'react-router-dom';
import Button from 'material-ui/Button';
class OldComponent extends React.Component {
redirectPage = () => { this.props.dispatch(push('/new')); };
redirectPage1 = () => { this.props.dispatch(push('/')); };
render() {
return (
<div>
OLD COMPONENT
<Button onClick={this.redirectPage}>Redirect new</Button>
<Button onClick={this.redirectPage1}>Redirect /</Button>
<Link to='/new'>Redirect Link</Link>
</div>
)
}
}
export default withRouter(connect()(OldComponent));
Other basic component for redirection purposes
import React from 'react';
import { connect } from 'react-redux';
import { push } from 'connected-react-router';
import { withRouter } from 'react-router';
import { Link } from 'react-router-dom';
import Button from 'material-ui/Button';
class NewComponent extends React.Component {
redirectPage = () => { this.props.dispatch(push('/')); };
redirectPage1 = () => { this.props.dispatch(push('/new')); };
render() {
return (
<div>
NEW COMPONENT
<Button onClick={this.redirectPage}>Redirect /</Button>
<Button onClick={this.redirectPage1}>Redirect new</Button>
<Link to='/new'>Redirect Link</Link>
</div>
)
}
}
export default withRouter(connect()(NewComponent));
As you can see, they are essentially the same component with minor differences. The url will change to /new or / and will also update the pathname found in the ##router/LOCATION-CHANGE state objects created by react-router-connected package. The url will also change by clicking the <Link> tag but with no redirect.
Any help on how to approach this would be greatly appreciated.
The comment posted by #Supertopoz works this.props.history.push('/pathname') works. However, after setting that up, the <Link> now works as well. I am also using withRouter (which I was before) throughout, so that was another important factor in egtting it to work.