React-router-dom (v6) go back only within application - javascript

Is there a built-in way in react-router-dom v6, to go back to the previous page, BUT in case the previous page is out of the context of the application, to route to the root and to thus not out of the application.
Example: I surf to a www.thing.com/thingy from www.google.com, this page (www.thing.com/thingy) has a go back button on it => when I click on the go back button => I am redirected to www.google.com instead of the wanted behaviour a redirect to www.thing.com.
Mockup of an example page.
I have tried several implementations and searched through the documentation but couldn't find a built-in way to resolve this. As far as I can see there isn't a way. I can however make something custom to resolve my issue if its not.

import { useNavigate } from 'react-router-dom';
function YourApp() {
const navigate = useNavigate();
return (
<>
<button onClick={() => navigate(-1)}>go back</button>
</>
);
}

I solved it by keeping track of the history.
If a user had not yet been on the page, I redirect them to the homepage.
Else redirect them to the previous page.
import {
useEffect
} from 'react';
import {
createContext,
useMemo,
useState
} from 'react';
import {
useLocation
} from 'react-router-dom';
export const LocationHistoryContext = createContext({});
const LocationHistoryProvider = ({
children
}) => {
const [locationHistory, setLocationHistory] = useState(
new Set(),
);
const location = useLocation();
useEffect(() => {
// if pathname has changed, add it to the history
let path = location.pathname.match(/^\/([^/])*/)[0];
setLocationHistory((prev) => new Set([path, ...prev]));
}, [location.pathname, location]);
const context = useMemo(() => {
return {
/* if the user has visited more than one page */
hasHistory: locationHistory.size > 1,
};
}, [locationHistory]);
return ( <
LocationHistoryContext.Provider value = {context}>
{
children
}
</LocationHistoryContext.Provider>
);
};
export default LocationHistoryProvider;

Related

How do I go back one page and refresh it using useNavigate?

I want to go back one page and refresh it because I need a list to be updated and it doesn't do it when I'm just using the navigate(-1)
Code for example:
import { useNavigate } from 'react-router-dom';
function YourApp() {
const navigate = useNavigate();
return (
<>
<button onClick={() => navigate(-1)}>go back</button>
</>
);
}
I was able to solve this partly.
To be able to refresh the list I wanted I used Redux and its useSelector() function.
The refresh page part is not possible yet because its not implemented into the navigator.
Btw: The functionality I was after is the equivalent of doing window.location.reload().
If you're using react-router v6
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
const refreshPage = () => {
navigate(0);
}
You can set location to useEffect array:
const location = useLocation();
useEffect(() => {
// your code
}, [..., location, ...]);

How to add Nextjs cookie based secure authentication?

I have a small issue. I'm very very very new to Nextjs and I'm trying to learn by making a app. I have managed to make a Login system using next and I have few issues when securing routes. I have successfully added a cookie after successful login. Now I want to validate the cookie whenever user go to a protected route. I have followed below steps using this tutorial.
Made a Higher order component and checked the cookie validation using it.
Wrap the protected component using it.
Below is my HOD.
import { useRouter } from "next/router";
import Cookies from 'js-cookie';
const withAuth = (WrappedComponent) => {
return (props) => {
if (typeof window !== "undefined") {
const Router = useRouter();
const accessToken = Cookies.get('token');
if (!accessToken) {
Router.replace("/");
return null;
}
return <WrappedComponent {...props} />;
}
return null;
};
};
export default withAuth;
And then I have wrapped my component using above HOD.
import React, { Component } from 'react';
import withAuth from '../utils/withAuth';
class Home extends Component {
render() {
return (
<div>
HOME
</div>
);
}
}
export default withAuth(Home);
ISSUE #1
Above HOD is showing a console warning saying below.
Warning: Expected server HTML to contain a matching in .
div
Is their anyway I can fix this issue? As per some github answer I have found this can be solved using useEffect. SOURCE
Can anyone help me with this?
ISSUE #2
In this way, I have to wrap each and every protected component with my HOD. Is this the correct way of doing this or is there any other way to do this better than this?
Thank you so much or your support.
After spending some time. I was able to fix the issue by using below code. Now I just want to know the answer for 2nd issue mentioned above.
ISSUE #2 In this way, I have to wrap each and every protected component with my HOD. Is this the correct way of doing this or is there any other way to do this better than this?
Thank you so much or your support.
Code I use to fix the issue
import Router from 'next/router'
import Cookies from 'js-cookie';
import React, { useState, useEffect } from 'react';
const withAuth = (WrappedComponent) => {
return (props) => {
const [isLoggedIn, setLoginStatus] = useState(false);
useEffect(() => {
if (typeof window !== "undefined") {
const accessToken = Cookies.get('token');
if (accessToken) {
setLoginStatus(true)
}
else {
Router.push("/")
}
}
}, []);
if (isLoggedIn) {
return <WrappedComponent {...props} />;
} else {
return null;
}
}
};
export default withAuth;

Query values lost on page refresh in Next js? [Example given]

I am making a simple Next Js application which has only two pages..
index.tsx:
import React from "react";
import Link from "next/link";
export default function Index() {
return (
<div>
<Link
href={{
pathname: "/about",
query: { candidateId: 8432 }
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
As per the above code, on click Go to the about page it goes to about page and using query I also receive the passed query values in about page.
about.tsx
import React from "react";
import Router, { withRouter } from "next/router";
function About({ router: { query } }: any) {
return (
<div>
Candidate Id: <b> {query.candidateId} </b>
</div>
);
}
export default withRouter(About);
This displays the value but on page refresh while we are in /about page, the candidateId received gets disappeared.
Requirement: Kindly help me to retain the query value passed down from one page to another page even on page refresh.
Note: As per my requirement I should not display the canidateId on url while navigating and hence I am using as approach.. I know I can achieve it if I remove as but I cannot remove that here in index page while navigating.. Reason is this will lead to displaying candidateId in the url which is not intended..
Tried this solution: https://stackoverflow.com/a/62974489/7785337 but this gives empty query object on refresh of page.
Stuck for very long time with this please kindly help me.
If you do not want to use the query parameter you may need to create a "store" that saves your variable that persist throughout your pages.
Sample code as follows.
//candidatestore.js
export const CandidateStoreContext = createContext()
export const useCandidateStore = () => {
const context = useContext(CandidateStoreContext)
if (!context) {
throw new Error(`useStore must be used within a CandidateStoreContext`)
}
return context
}
export const CandidateStoreProvider = ({ children }) => {
const [candidateId, setCandidateId] = useState(null);
return (
<CandidateStoreContext.Provider value={{ candidateId, setCandidateId }}>
{children}
</CandidateStoreContext.Provider >
)
}
Then you need to wrap the Provider around your app like
<CandidateStoreProvider><App /></CandidateStoreProvider>
This way you can use anywhere as follows both in your index page and your about page.
const { candidateId, setCandidateId } = useCandidateStore()
UseContext
In your codes, it should probably look something like that.
import React from "react";
import Link from "next/link";
import { useCandidateStore } from './candidatestore'
export default function Index() {
const { candidateId, setCandidateId } = useCandidateStore()
useEffect(() => {
setCandidateId(thecandidateId)
})
return (
<div>
<Link
href={{
pathname: "/about",
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
function About({ router: { query } }: any) {
const { candidateId, setCandidateId } = useCandidateStore()
return (
<div>
Candidate Id: <b> {candidateId} </b>
</div>
);
}
Update to Next.JS 10. It comes with Automatic Resolving of href which fixes your problem.
Try to delete the as="about" and then navigate again to the "about" page, the issue should be gone.
Codesandbox
My best bet would be to store the candidateId in an encrypted session on the client side. You could read/verify cookies in getServerSideProps() and pass their contents to the page component. If this sounds feasible, I'd recommend checking out the next-iron-session.
Another approach would be to check if candidateId exists in the query object in getServerSideProps(). If it does then pass it straight to the page component. If not, either get it elsewhere, redirect, or pass some default value. Append the following starter code to your about.tsx:
/* ... */
export function getServerSideProps({ query }: any) {
// if query object was received, return it as a router prop:
if (query.candidateId) {
return { props: { router: { query } } };
}
// obtain candidateId elsewhere, redirect or fallback to some default value:
/* ... */
return { props: { router: { query: { candidateId: 8432 } } } };
}
index.tsx file
Keep the code same as it is.
import React from "react";
import Link from "next/link";
export default function Index() {
return (
<div>
<Link
href={{
pathname: "/about",
query: { candidateId: 8432 }
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
AboutUs.tsx
Code starts from here
Adding router as a dependency in the useEffect the issue should get solved.
import Router, { useRouter } from "next/router";
import React, { useState, useEffect } from 'react';
function About({ router: { query } }: any) {
const route = userRouter();
const [candidateId, setCandidateid] = useState();
useEffect(() => {
const {candidateId} = router.query
if(candidateId) {
setCandidateid(candidateid)
}},[router]) //Here goes the dependency
return (
<div>
Candidate Id: <b> {candidateId} </b>
</div>
);
}
export default (About);

Use anchors with react-router

How can I use react-router, and have a link navigate to a particular place on a particular page? (e.g. /home-page#section-three)
Details:
I am using react-router in my React app.
I have a site-wide navbar that needs to link to a particular parts of a page, like /home-page#section-three.
So even if you are on say /blog, clicking this link will still load the home page, with section-three scrolled into view. This is exactly how a standard <a href="/home-page#section-three> would work.
Note: The creators of react-router have not given an explicit answer. They say it is in progress, and in the mean time use other people's answers. I'll do my best to keep this question updated with progress & possible solutions until a dominant one emerges.
Research:
How to use normal anchor links with react-router
This question is from 2015 (so 10 years ago in react time). The most upvoted answer says to use HistoryLocation instead of HashLocation. Basically that means store the location in the window history, instead of in the hash fragment.
Bad news is... even using HistoryLocation (what most tutorials and docs say to do in 2016), anchor tags still don't work.
https://github.com/ReactTraining/react-router/issues/394
A thread on ReactTraining about how use anchor links with react-router. This is no confirmed answer. Be careful since most proposed answers are out of date (e.g. using the "hash" prop in <Link>)
React Router Hash Link worked for me and is easy to install and implement:
$ npm install --save react-router-hash-link
In your component.js import it as Link:
import { HashLink as Link } from 'react-router-hash-link';
And instead of using an anchor <a>, use <Link> :
<Link to="home-page#section-three">Section three</Link>
Note: I used HashRouter instead of Router:
This solution works with react-router v5
import React, { useEffect } from 'react'
import { Route, Switch, useLocation } from 'react-router-dom'
export default function App() {
const { pathname, hash, key } = useLocation();
useEffect(() => {
// if not a hash link, scroll to top
if (hash === '') {
window.scrollTo(0, 0);
}
// else scroll to id
else {
setTimeout(() => {
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) {
element.scrollIntoView();
}
}, 0);
}
}, [pathname, hash, key]); // do this on route change
return (
<Switch>
<Route exact path="/" component={Home} />
.
.
</Switch>
)
}
In the component
<Link to="/#home"> Home </Link>
Here is one solution I have found (October 2016). It is is cross-browser compatible (tested in Internet Explorer, Firefox, Chrome, mobile Safari, and Safari).
You can provide an onUpdate property to your Router. This is called any time a route updates. This solution uses the onUpdate property to check if there is a DOM element that matches the hash, and then scrolls to it after the route transition is complete.
You must be using browserHistory and not hashHistory.
The answer is by "Rafrax" in Hash links #394.
Add this code to the place where you define <Router>:
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';
const routes = (
// your routes
);
function hashLinkScroll() {
const { hash } = window.location;
if (hash !== '') {
// Push onto callback queue so it runs after the DOM is updated,
// this is required when navigating from a different page so that
// the element is rendered on the page before trying to getElementById.
setTimeout(() => {
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) element.scrollIntoView();
}, 0);
}
}
render(
<Router
history={browserHistory}
routes={routes}
onUpdate={hashLinkScroll}
/>,
document.getElementById('root')
)
If you are feeling lazy and don't want to copy that code, you can use Anchorate which just defines that function for you. https://github.com/adjohnson916/anchorate
Here's a simple solution that doesn't require any subscriptions nor third-party packages. It should work with react-router#3 and above and react-router-dom.
Working example: https://fglet.codesandbox.io/
Source (unfortunately, it doesn't currently work within the editor):
#ScrollHandler Hook Example
import { useEffect } from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router-dom";
const ScrollHandler = ({ location, children }) => {
useEffect(
() => {
const element = document.getElementById(location.hash.replace("#", ""));
setTimeout(() => {
window.scrollTo({
behavior: element ? "smooth" : "auto",
top: element ? element.offsetTop : 0
});
}, 100);
}, [location]);
);
return children;
};
ScrollHandler.propTypes = {
children: PropTypes.node.isRequired,
location: PropTypes.shape({
hash: PropTypes.string,
}).isRequired
};
export default withRouter(ScrollHandler);
#ScrollHandler Class Example
import { PureComponent } from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router-dom";
class ScrollHandler extends PureComponent {
componentDidMount = () => this.handleScroll();
componentDidUpdate = prevProps => {
const { location: { pathname, hash } } = this.props;
if (
pathname !== prevProps.location.pathname ||
hash !== prevProps.location.hash
) {
this.handleScroll();
}
};
handleScroll = () => {
const { location: { hash } } = this.props;
const element = document.getElementById(hash.replace("#", ""));
setTimeout(() => {
window.scrollTo({
behavior: element ? "smooth" : "auto",
top: element ? element.offsetTop : 0
});
}, 100);
};
render = () => this.props.children;
};
ScrollHandler.propTypes = {
children: PropTypes.node.isRequired,
location: PropTypes.shape({
hash: PropTypes.string,
pathname: PropTypes.string,
})
};
export default withRouter(ScrollHandler);
Just avoid using react-router for local scrolling:
document.getElementById('myElementSomewhere').scrollIntoView()
The problem with Don P's answer is sometimes the element with the id is still been rendered or loaded if that section depends on some async action. The following function will try to find the element by id and navigate to it and retry every 100 ms until it reaches a maximum of 50 retries:
scrollToLocation = () => {
const { hash } = window.location;
if (hash !== '') {
let retries = 0;
const id = hash.replace('#', '');
const scroll = () => {
retries += 0;
if (retries > 50) return;
const element = document.getElementById(id);
if (element) {
setTimeout(() => element.scrollIntoView(), 0);
} else {
setTimeout(scroll, 100);
}
};
scroll();
}
}
I adapted Don P's solution (see above) to react-router 4 (Jan 2019) because there is no onUpdate prop on <Router> any more.
import React from 'react';
import * as ReactDOM from 'react-dom';
import { Router, Route } from 'react-router';
import { createBrowserHistory } from 'history';
const browserHistory = createBrowserHistory();
browserHistory.listen(location => {
const { hash } = location;
if (hash !== '') {
// Push onto callback queue so it runs after the DOM is updated,
// this is required when navigating from a different page so that
// the element is rendered on the page before trying to getElementById.
setTimeout(
() => {
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) {
element.scrollIntoView();
}
},
0
);
}
});
ReactDOM.render(
<Router history={browserHistory}>
// insert your routes here...
/>,
document.getElementById('root')
)
<Link to='/homepage#faq-1'>Question 1</Link>
useEffect(() => {
const hash = props.history.location.hash
if (hash && document.getElementById(hash.substr(1))) {
// Check if there is a hash and if an element with that id exists
document.getElementById(hash.substr(1)).scrollIntoView({behavior: "smooth"})
}
}, [props.history.location.hash]) // Fires when component mounts and every time hash changes
For simple in-page navigation you could add something like this, though it doesn't handle initializing the page -
// handle back/fwd buttons
function hashHandler() {
const id = window.location.hash.slice(1) // remove leading '#'
const el = document.getElementById(id)
if (el) {
el.scrollIntoView()
}
}
window.addEventListener('hashchange', hashHandler, false)
An alternative: react-scrollchor https://www.npmjs.com/package/react-scrollchor
react-scrollchor: A React component for scroll to #hash links with smooth animations. Scrollchor is a mix of Scroll and Anchor
Note: It doesn't use react-router
Create A scrollHandle component
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export const ScrollHandler = ({ children}) => {
const { pathname, hash } = useLocation()
const handleScroll = () => {
const element = document.getElementById(hash.replace("#", ""));
setTimeout(() => {
window.scrollTo({
behavior: element ? "smooth" : "auto",
top: element ? element.offsetTop : 0
});
}, 100);
};
useEffect(() => {
handleScroll()
}, [pathname, hash])
return children
}
Import ScrollHandler component directly into your app.js file
or you can create a higher order component withScrollHandler and export your app as withScrollHandler(App)
And in links <Link to='/page#section'>Section</Link> or <Link to='#section'>Section</Link>
And add id="section" in your section component
I know it's old but in my latest react-router-dom#6.4.4, this simple attribute reloadDocument is working:
div>
<Link to="#result" reloadDocument>GO TO ⬇ (Navigate to Same Page) </Link>
</div>
<div id='result'>CLICK 'GO TO' ABOVE TO REACH HERE</div>

Redirect to the previous page using session history/management using nodejs/react/javascript

import React, { PropTypes } from 'react';
import createMemoryHistory from 'history/lib/createMemoryHistory';
import { ArrowButton } from '../../components';
const history = createMemoryHistory();
const unlisten = history.listen((location, action) => {
console.log(action, location.pathname, location.state);
});
history.push('/home', { some: 'state' });
createMemoryHistory({
initialEntries: ['/'], // The initial URLs in the history stack
initialIndex: 0, // The starting index in the history stack
keyLength: 6, // The length of location.key
// A function to use to confirm navigation with the user. Required
// if you return string prompts from transition hooks (see below)
getUserConfirmation: null,
});
history.listen((location, action) => {
console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`);
console.log(`The last navigation action was ${action}`);
});
const genericMessage = 'We have a problem.';
const RegistrationError = ({
reason,
returnToUrl,
goBack,
}) => (
<div className="registration-error">
<h2 className="register-title">Sorry!</h2>
<p>{ reason || genericMessage }</p>
{ !reason && (
<ArrowButton
onClick={() => returnToUrl(history.go(-1))}
>
Close
</ArrowButton>
I am new to Nodejs, and having difficulties understanding some concept. The problem that I have is when I click on the Close button, the page should redirect to the previously viewed site. However this is not working. What am i doing wrong? If anyone could point me to the right direction. I have checked similar problems on stack overflow, which did not help me.

Categories