React Router - How to Determine If Back button was hit? - reactjs

I have these scenarios
Settings Page -> Results Page -> Details Page
User chooses settings, clicks next, gets results and then clicks into more details.
Details Page -> Results Page
User goes back to Results page from Details Page. This causes a full re-render, causing me to hit the server again for no point(I have the results stored in an array).
Details Page -> Results Page -> Settings Page -> Results Page
The user goes to details, then back to results(want to grab stored results), then goes back to settings page, makes changes and then goes back to results page(now I want a full grab from server again).
I am wondering if there is away in react router to determine if I came to the page via the browser history or if I was going in a forward motion.

I was looking for the same thing then I finally find a solution that seems simple.
Quick answer:
I use history.action value to determine if the user is coming from a back button or from a classic navigation.
If history.action is equal to 'POP' then the back button has been hit. Otherwise it's 'PUSH'.
Detailed answer:
I get access to history in props of each page because I do something like that:
<Provider store = { store }>
<AppRouter />
</Provider>
Where AppRouter is:
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import PublicRoute from './PublicRoute';
import PageHome from '../pages/front/home/PageHome';
export const history = createHistory();
const AppRouter = () => (
<Router history={ history }>
<div>
<Switch>
<PublicRoute
exact = { true }
path = "/"
component = { PageHome }
history={ history }
/>
< ..... Other routes here ..... >
</Switch>
</div>
</Router>
);
export default AppRouter;
And PublicRoute component is something like that:
import React from 'react';
import { Route } from 'react-router-dom';
import Header from '../components/header/Header';
const PublicRoute = ( { component: Component, ...rest } ) => (
<Route
component={ ( props ) => (
<div>
<Header />
<Component { ...props } />
</div>
)}
{ ...rest }
/>
);
export default PublicRoute;

In React Router, the component stays mounted if router calls for paths that are children of that component. So, in your example, you can do something like the following:
<Route path="items" component={Results}>
<Route path=":id" component={Detail} />
</Route>
This way, Results component does not get unmounted when Detail component is being mounted because Detail is a child of Results. However, if you do not want to see Results component getting rendered when you are in Detail, you can only render children when they exist. Something like the following:
class Results extends React.Component {
render() {
if (this.props.children) {
// This will enter Detail component
return this.props.children;
}
return (
// Render results component
);
}
}

Related

React Milti Page Routing

I am trying to make a multi page app with react routing.
I am have some questions as to how I should structure the routing in the react project.
I want to load my component in my app.js file. In my Home component I would like to have the ability to press a button which will take me to the Poems component, I want the code to be clean and structured into components, therefore I dont want to do all this in the app.js file.
If someone would explain to me how to best do this I can from there be able to route around to multiple pages afterwards depending on the page you are on. I dont want to have a global menu currently (I will want that in the Poems component later though).
Here is my current App.js file & Home.jsx component code just for a more easily adjustable experience for you guys!
Currently it is not optimized to work so if anyone knows a good routing solution for my issue, please give me an example of the routing fix.
Thanks alot
/Jacob
import React from 'react'
import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'
import './App.scss'
import { Home, Poems, Favourites } from './Pages'
const App = () => {
return (
<Router>
<div className="app">
<Home />
<Routes> {/* I read that the Switch was replaces with Routes */}
<Route path="/" exact component={ Home } />
<Route path="/Poems" component={ Poems } />
<Route path="/Favourites" component={ Favourites } />
</Routes>
</div>
</Router>
)
}
export default App
import React from 'react'
import { Route, BrowserRouter as Router, Routes, Link } from 'react-router-dom'
import { Poems } from './Pages'
import './Home.scss'
const Home = () => {
return (
<Router>
<div>
<h1>Petry For All</h1>
<Routes>
<Route path="/Poems" component={ Poems } />
<Link to="/Poems">Poems</Link>
</Routes>
</div>
</Router>
)
}
export default Home
You don't need to (and actually shouldn't) duplicate the <Router> component in all of the route pages. It is only the root component that is acting as a router. So you can keep the App component the same, and then replace the Home component with the following:
import React from 'react'
import { Poems } from './Pages'
import './Home.scss'
const Home = () => {
return (
<div>
<h1>Petry For All</h1>
<Link to="/Poems">Poems</Link>
</div>
)
}
export default Home
The <Link> component resolves into an anchor element which, when clicked, navigates the user to the route passed into the to property.

Conditional Redirection to a URL in react

I am building a "Forgot password" page. Following is the flow of my code:
A page with url /auth/forgot-password opens on clicking on Forgot Password.
It takes input as email and sends OTP to the registered email(if it exists in DB).
After sending OTP, it redirects to a new URL /auth/new-password.
Here, the remaining details are entered(ex. OTP,new password etc.)
Due to this flow, the user can access the path /auth/new-password by searching for it. But I don't want that to happen. User should only reach this url via /auth/forgot-password. User should be redirected to /auth/forgot-password URL if user searches for the prior one.
Currently in my Routes page I am doing this:
<ContentRoute path="/auth/forgot-password" component={ForgotPassword}/>
<ContentRoute path="/auth/new-password" component={NewPassword} />
Due to some restrictions I can't change the existing flow of the code.
How can I change this to exhibit the behavior explained above?
The easiest way is to create a HOC (Higher Order Component).
When I want a user to authenticate before accessing a sites' page, I always create a HOC called AuthRoute like this.
AuthRoute.js
import { connect } from "react-redux";
import { Redirect, Route } from "react-router-dom";
const AuthRoute = props => {
const { authUser, location } = props;
if(!authUser) {
return <Redirect to={{
pathname: "/",
state: { prevLoc: location.pathname }
}} />
}
return <Route {...props} />;
};
function mapStateToProps({ authUser }) {
return {
authUser
}
}
export default connect(mapStateToProps)(AuthRoute);
Then include that to the App component like this.
App.js
import { Fragment } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import AuthRoute from './components/AuthRoute'; // AuthRoute Import
import Dashboard from './components/Dashboard';
const App = () => {
return (
<Router>
<Switch>
{/* Include the AuthRoute component for the relevant page */}
<AuthRoute path="/home" component={Dashboard} />
<Route path="/" component={Login} />
</Switch>
</Router>
)
}
This implementation will check whether the user entered their email address on /auth/forgot-password page.
Completed project with HOC implementation - https://github.com/yushanwebdev/reactnd-would-you-rather

How to get the React router path on route change

I have this App.jsx that routes to (renders) different components.
But I have set <NavigationBar /> and an h1 tag between Router and Switch because I need to render those two components for every page.
So now what I want is to get the current route name/path name that displays on the browser address bar. This path is changing when I click on different links (Link) to render different components.
But the path value is the same / even though the path is changing for every Link click.
I even used componentDidUpdate but it didn't work as it gave the error
maximum update depth exceeded componentdidupdate
this is my App.jsx
import React, { Component } from "react";
import "./App.css";
import "./css/custom.css";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import NavigationBar from "./pages/homepage-components/1-navbar";
import HomePage from "./pages/HomePage";
import Post from "./pages/Post";
class App extends Component {
constructor(props) {
super(props);
this.state = {
currentPath: "",
};
}
componentDidMount() {
this.setState({
currentPath: window.location.pathname,
});
}
render() {
return (
<Router>
{/* --------------- Navigation Bar --------------- */}
<NavigationBar />
<h1>Path is: {this.state.currentPath}</h1>
{/* --------------- End of Navigation Bar --------------- */}
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/post" component={Post} />
</Switch>
</Router>
);
}
}
export default App;
Even though the different components are rendered as path changes, the value for this.state.currentPath doesn't update.
Can someone help, please?
useLocation hook provides current location:
function NavigationHeader() {
const location = useLocation();
return <h1>Path is: {location.pathname}</h1>;
}

Redirect in React Router 5 is successfully redirecting, but not rendering the child component

I have changed the routes in my app, and in case any users have bookmarked urls to the old routes I have added some redirects to the new routes. Most of them are working fine, however this one is not -
App.tsx
import { Router } from 'react-router-dom';
import Routes from './Routes';
import history from './history';
const App: FunctionComponent = () => (
<Router history={history}>
<Routes />
</Router>
);
export default App;
RouteSwitch.txs
import React, { FunctionComponent } from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
const RouteSwitch: FunctionComponent = () => {
return (
<Switch>
<Redirect exact from="/documents" to="/documents/list" />
<Route exact path="/documents/list">
<DocumentsContainer />
</Route>
</Switch>
);
};
export default RouteSwitch;
The redirect from /documents to /documents/list works, however the DocumentsContainer does not get rendered. If I directly request /documents/list then it renders fine. It's as if <Switch> finds its first match (the Redirect) and then decides its job is done. I tried adding the push prop to the Redirect but it didn't make a difference.
My example is very similar to the one given on the React Training site - https://reacttraining.com/react-router/web/api/Redirect/from-string
Thoughts?
Problem solved - the DocumentsContainer component was actually rendering, but just not showing anything due to a quirk with how the use of the redirect was causing a loader count to be incremented by one (but not decremented) resulting in the loader count not returning to 0 to allow document data to be loaded from Redux and the content to be rendered.

Implement search bar using ReactJS

I am building a beginner React app and am not able to understand how to handle my state so that I can redirect to a search results page:
I have a main App component which uses React Router to deliver two components:
1) Landing (/) -- has an input and should take you to /search and show only those objects whose title match your input
2) Search (/search) -- either shows all objects if accessing the page directly or your filtered based upon your input
My question is: if I handle the state in the App component, it will cause the state to update and a rerender upon a user typing in the Landing input element, but how can I get it to go to /search with the updated state? The index route will keep getting hit since it's just a rerender and the user is still on the landing page.
I would like to handle this without redux as this will be a very small app.
Here is the code for my parent component:
import React, { Component } from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { shape, string } from "prop-types";
import Landing from "./Landing";
import Search from "./Search";
import { shows } from "../data.json";
class App extends Component {
constructor(props) {
super(props);
this.state = {
searchTerm: ""
};
this.updateSearchTermHandler = this.updateSearchTermHandler.bind(this);
}
updateSearchTermHandler(searchTerm) {
this.setState({ searchTerm });
}
render() {
return (
<BrowserRouter>
<div className="app">
<Switch>
<Route
exact
path="/"
component={props => (
<Landing
updateSearchTermHandler={this.updateSearchTermHandler}
searchTerm={this.state.searchTerm}
{...props}
/>
)}
/>
<Route
path="/search"
component={props => (
<Search
updateSearchTermHandler={this.updateSearchTermHandler}
shows={shows}
{...props}
/>
)}
/>
</Switch>
</div>
</BrowserRouter>
);
}
}
App.propTypes = {
match: shape({
params: string.isRequired
}).isRequired
};
export default App;
One potential solution is to instead use a <Router> with your own history. You could then call history.replace('/search', { searchTerm: 'foo' })
And then in your Landing component, you will have this.props.history.location.state.searchTerm
See https://reacttraining.com/react-router/web/api/Router for further details on creating history

Resources