How to route to new page in ReactJS - reactjs

I have a router which has a link to a product details page, problem is, the application is being displayed in a android webview so ... if am viewing a product details page and clicked back, the whole application will exit.
is there a way to make the use go back to the home page ? or I have to implement a back button ?
class App extends React.Component {
render() {
return (
<div>
<Router>
<Switch>
{/* <PrivateRoute exact path="/" component={HomePage} /> */}
<Route exact path="/" component={Home} />
<Template>
<Route path="/:id" component={PropertyTemplate} />
</Template>
</Switch>
</Router>
</div>)
}
}

It's not neccessary to implement backbutton - just this code make work:
history.goBack()
Here is implemented BackButton - just add this in common folder in architecture & reuse.
import React from 'react';
import { withRouter } from 'react-router-dom';
import ReturnIcon from '../../img/tech/return-white-icon.svg'
const GoBack = ({ history }) => <img src={ReturnIcon} onClick={() => history.goBack()} alt="Go back" />;
export default withRouter(GoBack);

Related

Route open as a full page instead of opening in same page

I am using react router for a SPA Dashboard. I have these two routes(Routes.js) in dashboard right now. Also, a separate route in App.js for landing page.
Now it works fine as expected when it loads and when I click different routes. For example, When I click customers route(in sidebar), it open the customers section on the same page in right side. But, when I refresh the customers page localhost:5000/customers, now it opens as a separate page. How can I solve this.
I tried debugging it but still no luck.
Here's an example : https://github.com/gouravthakur39/MRE
Unfortunately can't reproduce it in code sandbox
Routes.js
import React from "react";
import { Route, Switch } from "react-router-dom";
import Dashboard from "../pages/Dashboard";
import Customers from "../pages/Customers";
const Routes = () => {
return (
<Switch>
<Route path="/home" exact component={Dashboard} />
<Route path="/customers" exact component={Customers} />
</Switch>
);
};
export default Routes;
Part of App.js
import { Fragment } from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import PrivateRoute from "./private/PrivateRoute";
import Landing from "./pages/Landing";
import Home from "./pages/Home";
import Customers from "./pages/Customers";
function App() {
return (
<Fragment>
<div className="App">
<BrowserRouter>
<Switch>
<Route exact path="/" component={Landing} />
<PrivateRoute exact path="/home" component={Home} />
<PrivateRoute exact path="/customers" component={Customers} />
</Switch>
</BrowserRouter>
</div>
</Fragment>
);
}
export default App;
Question/Issue
Based on your code and the description on the Dashboard component it sounds like you want Dashboard and Customers to render "nested" in the "/home" path.
const Dashboard = () => {
return (
<div>
<h1>Dashboard</h1>
<p>I want to open dashboard and customers here when clicked</p>
<p>
Right now, when you click customers, it will open it in new page and not
here.
</p>
</div>
);
};
This is because Home is rendering the Dashboard on the same "/home" path, and the sidebar is linking to a "/customers" path at the root router level. This Customers component is outside the nested home/dashboard component.
Solution
App
For this the root "/home" in App needs to not exactly match in order for it to render nested routes. Remember also that in the Switch component that path order and specificity matter. Order the paths from more specific to less specific, i.e. "/home" is more specific than "/" and should be ordered higher/before. The "/customers" route/path should be removed since it will be rendered by Home.
function App() {
return (
<Fragment>
<BrowserRouter>
<Switch>
<Route path="/home" component={Home} />
<Route path="/" component={Landing} />
</Switch>
</BrowserRouter>
</Fragment>
);
}
Routes (rendered by Home)
Use the useRouteMatch hook to gather the current matched path value and build the nested routes. Remember the path order and specificity rule for the Switch.
import { Route, Switch, useRouteMatch } from "react-router-dom";
const Routes = () => {
const { path } = useRouteMatch();
return (
<Switch>
<Route path={`${path}/customers`} component={Customers} /> // "/home/customers"
<Route path={path} component={Dashboard} /> // "/home"
</Switch>
);
};
Sidebar (rendered by Home)
Use the useRouteMatch hook to gather the current matched url value and build the nested links.
import { Link, useRouteMatch } from "react-router-dom";
const Sidebar = () => {
const { url } = useRouteMatch();
return (
<div>
<ul>
<li>
<Link to={url}>Dashboard</Link> // to "/home"
</li>
<li>
<Link to={`${url}/customers`}>Customers</Link> // to "/home/customers"
</li>
</ul>
</div>
);
};

Routing inside subcomponents not working while using 'exact' : ReactJs

I have created one simple project with 3 main components/pages.
Home page (path: "/") with is login page. once we click on login button, will create a dynamic URL "admin/random_num
LogIn.js
import { Link, Route } from "react-router-dom";
import Dashboard from "../Admin/Dashboard";
export default function Login(props) {
return (
<div>
<h1>Login form</h1>
<Link to={`/admin/${Math.random()}`}>Login Button</Link>
<Route path="/admin/:aid" component={Dashboard} />
</div>
);
}
2nd component/page is admin(path: "/admin/randum_num" , eg: "/admin/132"), which should be displayed when clicked on login button on login page.
Admin.js
export default function Dashboard(props) {
return (
<div>
<h1>Admin Dashboard</h1>
</div>
)
}
3rd component is 404page, which will simply display when no route is matched.
Error404.js
export default function Error404() {
return (
<div>
<h1>404</h1>
</div>
)
}
last not the least, in App.js i am setting up routing.
App.js
import React from 'react'
import './App.css';
import Error404 from './pages/Error404';
import Login from './pages/StartingScreen/Login';
function App() {
return (
<BrowserRouter>
<div className="App">
<Switch>
<Route exact path="/" component={Login} />
<Route component={Error404} />
</Switch>
</div>
</BrowserRouter>
);
}
export default App;
Note: i am able to display the admin dashboard page if i remove "exact" parameter in Route statement but after removing "exact" both Login and Dashboard page will be visible which i dont want. I want only one component should be visible at given route.
I am struggling to find an answer regarding this problem since one week, kindly help. If the given detail is incomplete, let me know.
Since you are using exact for your login component, that means your Login component will only render for the root path /.
And your Dashboard component is inside your Login component, that means you also need to add "/admin/:aid" to your login route.
You need to change this
<Route exact path="/" component={Login} />
to
<Route exact path={["/", "/admin/:aid"]} component={Login} />
Update:
To hide login button, you can use another Switch in your Login component
export default function Login(props) {
return (
<div>
<Switch>
<Route exact path="/">
<h1>Login form</h1>
<Link to={`/admin/${Math.random()}`}>Login Button</Link>
</Route>
<Route exact path="/admin/:aid" component={Dashboard} />
</Switch>
</div>
);
}

Use layout for certain routes in React

I need to maintain a section of my app available for all pages (most of the pages actually)
to do that I wrapped the routes in a Layout
<Router>
<Route path="/login" exact strict component={Login}/>
<Layout>
<Route path="/list" exact strict component={List}/>
<Route path="/settings" exact strict component={Settings}/>
</Layout>
</Router>
the Layout component looks like this
class Layout extends Component {
render() {
return (
<React.Fragment>
<div>this box should be visible in all pages using layout</div>
<div>{this.props.children}</div>
</React.Fragment>
)
}
}
this work perfectly fine. the only poblem is when I go to /login
is rendering the Login component and also the Layout component.
I shouldn´t render the Layout if is not for one of the ¨protected¨ routes.
is there a way to achieve that without moving the layout inside of the List of Settings component?
the reason for this is because the box that´s fixed in all pages makes one API request and there´s no need to do that same API call for /list and /settings. I do it only once.
hope this makes sense to you. thank you!
update:
"react-router-dom": "^5.0.1"
I've done a fair bit of research on the subject and came up with this. I have it set up in my routes:
import React from 'react'
import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom'
// import PublicRoute, PrivateRoute, Login, Private
<Router>
<Switch>
<PublicRoute exact path='/login' component={Login} />
<PrivateRoute path='/private' component={Private} />
</Switch>
<Router>
Then in my privateRoute.js:
import React from 'react'
import { Redirect, Route } from 'react-router-dom'
// import useAuth and PrivateLayout
export default ({ componet: Component, ...rest }) => {
const { token } = useAuth() // wrapper I wrote to handle auth
if (!token) return <Redirect to='/login' />
return (
<Route {...rest} render={props => (
<PrivateLayout><Component {...props} /></PrivateLayout>
)} />
)
}
And my publicRoute.js is similar but without authentication. My layouts are a simple wrapper, all you need to do is include children:
export default ({ children }) => {
return (
<>
<Topbar />
<Sidebar />
{children}
</>
)
Note: For those who don't know <> is the shorthand for <React.Fragment>.

How to change the page and change only one component, not the entire html

I'm starting in React and I'm curious about about if have any way to change a page without reload all the html, changing only a content component for example.
I know that there is a way to change the component without change the url but I thought that if the url change too the application would be better.
React Router is the exact thing you're looking for
Here, how you can achieve what you're looking for.
First, wrap your app with BrowserRouter
import { BrowserRouter } from "react-router-dom";
import React from 'react';
class App extends React.Component {
return (){
return (
<BrowserRouter>
<SomeComponent />
</BrowserRouter>
)
}
}
Now just use the Route and Link. Route told the application which component to render on the basis of the current route and Link changes the URL without reloading the whole page
import { Route, Link, Switch } from "react-router-dom";
import React from 'react';
import {Circle, Square} from './someFileWithComponents';
class SomeComponent extends React.Component {
render(){
return (
<div>
<Link to='/circle' >Circle</Link>
<Link to='/square' >Square</Link>
<Switch>
<Route path='/circle' component={Circle} />
<Route path='/square' component={Square} />
</Switch>
</div>
)
}
}
React Router is what you looking for
const AppRouter =()=>(
<BrowserRouter>
<div>
<Header/>//where Header components contains the navigation
<Switch>
<Route path="/" component={BookListPage} exact={true} />
<Route path="/create" component={AddBookItem} />
<Route path="/edit/:id" component={EditBookItem} />
<Route path="/help" component={HelpPage} />
<Route component={NotFoundPage} />
</Switch>
</div>
</BrowserRouter>
);
export default AppRouter;

How to redirect to log in page after click logout button on navbar header in React?

I'm new to React. I have react router config in App.js like this:
<BrowserRouter>
<div className="App">
<Header />
<Switch>
<Route exact path="/" component={Home}>
</Route>
<Route exact path="/management" component={Management}>
</Route>
<Route exact path="/sign-up" component={SignUpForm}>
</Route>
<Route exact path="/sign-in" component={SignInForm}>
</Route>
<Route component={Error}>
</Route>
</Switch>
</div>
</BrowserRouter >
I want header to show in every page, there's a button of logout at header, I want to redirect to /sign-in page after I click it. In my header component it's like this:
class Header extends Component {
constructor(props) {
super(props);
this.state = {
redirect: false
}
}
logout = () => {
sessionStorage.setItem("userToken", '');
sessionStorage.clear();
this.setState({ redirect: true });
}
render() {
if (this.state.redirect) {
return (
<Redirect to={'/sign-in'} />
)
}
return (
<div>
<Navbar collapseOnSelect expand="md" bg="dark" variant="dark" fixed="top" >
......
<NavLink to="/management" className="header-link"><FontAwesomeIcon icon="cog" size="lg" /></NavLink>
<button type='button' onClick={this.logout}>Log Out</button>
</Nav>
</Navbar.Collapse>
</Navbar>
</div>
);
}
}
export default Header;
There will be errors "Warning: You tried to redirect to the same route you're currently on: "/sign-in", and the nav bar will disappear only the body of sign-in shows. May I know what is the correct way to do this? I also tried this.props.history.push('/sign-in') but there's no props.history, probably because header is not in route? Should i use with Router? Or should I actually just make every page import header instead put it in app.js? or what is actually the right way to do this? Thank you so much for your help!
You can implement login/logout with route using HOC that checks the session item with every route change. If the session has userToken then it will redirect to given component otherwise will redirect to login component.
import React from "react"
import {Redirect} from "react-router-dom"
export const PrivateRoute = ({component: Component, ...rest}) => (
<Route {...rest} render={(props) => (
sessionStorage.getItem('userToken') ? <Component {...props} /> : <Redirect to="/sign-in"/>
)} />
)
import <PrivateRoute> and use it as the authorized path. And keep all the other path as normal routes in which you don't want authorization.
<BrowserRouter>
<div className="App">
<Header />
<Switch>
<PrivateRoute path="/" component={Home} />
<PrivateRoute path="/management" component={Management} />
<Route path="/sign-up" component={SignUpForm} />
<Route path="/sign-in" component={SignInForm} />
<Route component={Error} />
</Switch>
</div>
</BrowserRouter >
So while you do log out, the session item will be removed and automatically redirect to sign-in page.
class Header extends Component {
....
logout = () => {
sessionStorage.removeItem("userToken");
sessionStorage.clear();
}
render() {
return (
<div>
...
<button type='button' onClick={this.logout}>Log Out</button>
</div>
)
}
}
export default Header;
Components tied up to Routes gets access to history object as prop so you can mutate it as you need, such as logging out. Since your Header component doesn't have access to the history object, you will have to use a lower level router to give it access to the history object:
import { Router } from "react-router"
import { createBrowserHistory } from "history"
const history = createBrowserHistory()
<Router history={history}>
<div className="App">
<Header history={history} />
<Switch>
<Route exact path="/" component={Home}>
</Route>
<Route exact path="/management" component={Management}>
</Route>
<Route exact path="/sign-up" component={SignUpForm}>
</Route>
<Route exact path="/sign-in" component={SignInForm}>
</Route>
<Route component={Error}>
</Route>
</Switch>
</div>
</Router>
now inside you Header component you can call history.push('/login')
see reference: https://reacttraining.com/react-router/web/api/Router/history-object
There are two approaches you're mentioning here. You can use the higher order component 'withRouter' that gives components access to the history object. By using the history object that would get passed to your component as a prop, you can push to the route you want.
Personally, I like setting up my signout links to render a component that houses the log-out logic and renders a redirect to log-in once it's complete. That way users can go directly to the sign-out link if they want, and you can link to it from anywhere in your app as needed, without having to duplicate the logic.
In your browser router, you can add a path for "/logout" that renders a component like this (based on your logic):
import React, { Component } from 'react';
import { Redirect } from 'react-router';
export default class LogOut extends Component {
state = {
redirect: false,
};
componentDidMount() {
sessionStorage.setItem("userToken", '');
sessionStorage.clear();
this.setState({ redirect: true });
}
render() {
return this.state.redirect ?
<Redirect to={'/sign-in'} /> :
null;
}
}
Normally I would make an ajax request to clear a session and then setState once that's complete, but yours is all server-side.

Resources