How to use useNavigate previous page without accidentally leaving my site - reactjs

Problem: I'm trying to use useNavigate(-1) to go to previous page, but I just implement a way to people arrive in my SPA with a pasted link (or social media tho), so, when user tries to go back they leave my site xP.
Process: I could use useNavigate("/"), but this makes my page scroll to the top and I want to keep the previous users position, I rather not use a state with my scroll position, but if nothing else is possible or simple...
Context: is a restaurant menu ifood-like page, this problems occurs in product page and I want to go back to product list.
What I'm trying to achieve: some way to check if previous page is out of my domain and not go there, replacing with my root.
ps. I'm using "react-router-dom": "^6.0.0-beta.0"

In earlier versions of React Router DOM, there was a smarter goBack method, but in modern versions (including the v6 beta), you must use the fact that the location.key is "default" for the initial entry to the app's history.
I created an npm package that encapsulates this functionality in a simple React hook.
Install use-back
npm install use-back
Usage
import {useBack} from "use-back";
const BackButton = () => {
const {hasBack, handleBack} = useBack();
return (
<button type="button" onClick={handleBack}>
{hasBack ? "Go Back" : "Go Home"}
</button>
);
};
CodeSandbox Demo
Source Code
The source code is basically the following, which you can also include in your application.
const MagicalBack = () => {
const navigate = useNavigate();
const location = useLocation();
const hasPreviousState = location.key !== "default";
const handleClick = () => {
if (hasPreviousState) {
navigate(-1);
} else {
navigate("/");
}
};
return (
<button type="button" onClick={handleClick}>
{hasPreviousState ? "Go Back" : "Go Home"}
</button>
);
};
CodeSandbox Demo

Related

Components aren't re-rendered after setState update, requires updating twice before re-render

I am creating a custom blog listing page in React for a HubSpot site (I've already searched there, couldn't find anything relevant to my issue). I'm still somewhat new to React but it is my first project from scratch in over a year. Basically what I am trying to do is combine three separate blogs into one page and add filtering options to show only posts from certain blogs and/or posts with certain tags attached to them. Currently I am successfully pulling the post data and sorting it. I am almost finished with the filtering functionality, but I am hitting a snag when trying to reset all filters.
I understand that setState is asynchronous and you can't expect the state value to updated immediately. My issue is that even after I have successfully updated the state, the useEffect hook doesn't fire consistently. I have tabs at the top of the page to switch between blogs and that works perfectly, as soon as I click one it filters out every other blog post and I can go back and forth between them easily. The problem is on the reset, I have to click the button twice for it to work. I'm completely at a loss as to why this is. In the console I see that the useEffect function for blogActive fires after the first time I click the reset button, but the blog view doesn't change. The second time I click the reset button, the blog view changes but the useEffect function doesn't fire.
I know that useEffect isn't supposed to fire if the state isn't actually updated (its being set to undefined when the button is clicked, so the second time it won't change) but I am still confused as to why this happens with the reset button and not with the blog switch function. I was originally resetting the blog from the reset function instead of just the sort function but that wasn't working either and calling it from the sort function is simpler in terms of displaying results with resetting the other filters (I have removed the tag filtering functions and components and still get the same results). I've also tried calling the handleBlogSwitch method instead of using the setState hook but I get the same results.
I've pasted the most relevant code below, but I've also included a sandbox link with a skeleton of the whole project including the code to fetch the post data.
Any help would be much appreciated!
function Blog({ blogName, blogID, handleBlog }) {
return (
<div className="blog">
<button className={"blog-" + blogName} onClick={() => handleBlog(blogID)}>
{blogName}
</button>
</div>
);
}
export default Blog;
function Archive() {
const [page, setPage] = useState(1);
const [blogActive, setBlog] = useState();
const [resetCheck, setChecked] = useState([]);
const [postState, setPosts] = useState(postArray);
let postArray = [];
function handleBlogSwitch(id) {
setBlog(id);
}
function sortPosts(sortme) {
let resetSort = false;
if (sortme) {
sortme.sort((a, b) => b["dataset"]["date"] - a["dataset"]["date"]);
postArray = [...postArray, ...sortme];
} else if (sortme === false) {
resetSort = true;
setBlog(() => {
return undefined;
});
}
let filtered = postArray;
if(!resetSort) {
if (blogActive) {
filtered = blogFilter(filtered);
setLoader("spinner-loaded");
}
}
setPosts(filtered);
setLoader("spinner-loaded");
}
function resetFilters(resetThis) {
setChecked([]); // Uncheck everything
sortPosts(resetThis);
}
// Handle logic for blog and tag filters
useEffect(() => {
setBtnLoad("btn-load-more"); // add this to the useEffect hook for tags too.
if(blogActive) {// Don't run this on page load.
sortPosts();
console.log("test blogactive effect");
}
}, [blogActive]);
return (
<div className="results__container">
<div className="tabs__container">
{Blogs.blogs.map((blog, key) => (
<Blog
key={key}
blogName={blog.name}
blogID={blog.id}
handleBlog={handleBlogSwitch}
></Blog>
))}
</div>
<div className="filter__container">
<button onClick={() => resetFilters(false)}>Reset Filters</button>
<div className="posts__container">
{postState.slice(0, page * resultsPerPage).map((html, key) => (
<Posts key={key} postHtml={html.outerHTML}></Posts>
))}
<img className={loader} src={logoSrc} />
<button className={btnClass} onClick={() => setPage(page + 1)}>
Load More
</button>
</div>
</div>
</div>
);
}
export default Archive;
https://codesandbox.io/s/wonderful-lalande-0w16yu?file=/src/App.js
Update: I didn't really fix this bug I'm facing, but I found a workaround. Instead of relying on the value of blogActive in sortPosts(), I created a new boolean in sortPosts which is set to true when it is called from the reset function. This way I know that the states should be resetting and skip that code, even if they aren't caught up yet.

React router v6, Remove History

Is there a way to remove history in react-router v6, I'm creating a web app, when the user reaches to homepage and presses back it should eventually exit the app. but as of now, it's going back if history exists. if react-router doesn't have this feature is there an alternative way to overcome this issue?
I have just done a PWA app like you and the way I solved this is that I always set option {replace: true} whenever it went forward or backward.
Ex:
navigate(/${SCREEN_1}, { replace: true })
or
navigate(-1, { replace: true })
So when user click on button "Next"/"Back" on the browser, there were only the first blank page of the browser (where before I open my URL) and the current page.
My app only have 6 screens.
I'm not sure whether this way will be useful for you or not, but for me it did.
Hope this help!
I didn't understood your question excatly.
But below code maybe helpful to go back to previous page.
import {useNavigate} from 'react-router-dom'
const Component = () => {
const navigate = useNavigate()
const goBack = () => {
navigate(-1)
}
return (
<>
<button onClick={goBack}>Back</button>
</>
)
}

React Native Navigation, open universal link from within own app

I am using React Navigation for React Native. I have successfully configured it to handle universal link, which is something like this
// linking.ts
import { APP_ID } from '#env';
const config = {
screens: {
LoginScreen: 'authorize',
RegisterScreen: 'register',
CustomerStack: {
screens: {
OrderDetailScreen: 'customer/order/:orderId',
},
},
},
};
const linking = {
prefixes: [`${APP_ID}://app/`, 'https://example.com/app/'],
config,
};
export default linking;
// App.tsx
import linking from './linking'
const App = () => {
return (
<NavigationContainer linking={linking}> <MyApp /> </NavigationContainer>
)
}
When I press a link in the browser such as https://example.com/app/customer/order/1234, then it successfully opens my app's order page.
Problem
I want to be able to open the url such as https://example.com/app/customer/order/1234 indside my app and have it open the order page. I have tried
<Button onPress={() => Linking.openURL('https://example.com/app/customer/order/1234')} />
but (testing on IOS) it switch to the web browser first to open the link, and then open my app.
Is it possible to open the order page directly inside my app without switching to the browser first.
Note: I am trying to implement an in-app notification history page, each notification item has the link saved in the database, and when the user clicks on the item I want to navigate the user to the page as configured in linking.ts. I know it is possible to parse the link and use navigation.navigate() instead, but that means I will have 2 places for the linking configuration. I think it would be great if I can reuse the existing logic provided by React Navigation.
In React Navigation, you can use the useLinkTo hook. This hook allows you to navigate inside the application using a path.
This will allow you to use the following option:
const linkTo = useLinkTo();
return (
<Button onPress={() => linkTo('/customer/order/1234')} />
);
If using a URL is mandatory, then you can use extractPathFromURL, an internal React Navigation function, to remove the prefix.
import extractPathFromURL from '#react-navigation/native/src/extractPathFromURL';
import linking from './linking'
// ...
const linkTo = useLinkTo();
return (
<Button onPress={() => {
const path = extractPathFromURL(linking.prefixes, 'https://example.com/app/customer/order/1234');
const pathWithSlash = path.startsWith('/') ? path : '/' + path;
linkTo(pathWithSlash);
} />
);
extractPathFromURL is not part of the official API and may be removed in future versions. For reliability, you can create a duplicate of this function in the project.

React-router-dom Link open at the top of page

So I am feeling pretty dum to ask this but cannot find a straight answer to it.
Do I need to give any css parameter or directly to <Link> in order for the pages on my react app open at the top?
EDIT
So refraining, everytime I click on a <Link>element at my reactJs project, the Link opens a page at the middle of the screen, not a the top.
For example. I have this component
<li ><Link to={{pathname: "/product", state: {products}}}><i className="fa fa-search"></i></Link></li>
and when I click on in, it opens a new page/component but not at the top of the page. The user needs to scroll up in order to see the top, and i would like to open new pages directly at the top!
This can be done without installing any additional libraries. You can check the official example of react-router for scroll restoration here.
You can use library react-scroll, and detect when change pathname of location, scroll to Top like this
import { animateScroll } from 'react-scroll';
useEffect(() => {
animateScroll.scrollToTop({
duration: 0
});
}, [location.pathname]);
make scrollToTop function and use it in your link tag
const scrollToTop = () => {
window.scrollTo(0, 0)
}
when user click on any link our function will make our page scroll to top.
<li onClick={scrollToTop} ><Link to={{pathname: "/product", state: {products}}}><i className="fa fa-search"></i></Link></li>
I am using the class component for my react blog. The issue got solved by adding the following method and calling it inside componentDidMount().
scrollToTop = () => {
window.scrollTo(0, 0)
}
componentDidMount() {
this.scrollToTop()
}
You can check this code is working. Live example, Blog.
If you are using a function component, then same method you can use and call it inside UseEffect().

react-router redirect to a different domain url

I am using react-router for client side routing. I have a button and when some one clicks the button, I want to redirect the user to a different url.
For e.g I want to redirect the user to "http://www.google.com". I used navigation mixin and used this.transitionTo("https://www.google.com"). But when I do this I get this error
Invariant Violation: Cannot find a route named "https://www.google.com".
I can use window.location but is that the right way to go?
As pointed out in the comments to this answer, default way of solving this would be to use anchor element (the a tag) with href attribute that points at the destination URL that you'd like to route the user to. A button that has appearance of a button but behavior or an anchor is pretty much a web anti-pattern. See more info in this answer: https://stackoverflow.com/a/1667512/1460905.
That said, there certainly is a potential scenario when a web app needs to perform some action and only then redirect the user. In this case, if primary action the user takes is submitting some data or really performing an action, and redirect is more of a side-effect, then the original question is valid.
In this case, why not use location property of window object? It even provides a nice functional method to go to external location. See the ref.
So, if you have a component, say
class Button extends Component {
render() {
return (
<button onClick={this.handleClick.bind(this)} />
);
}
}
then add handleClick that would make the component look like
class Button extends Component {
handleClick() {
// do something meaningful, Promises, if/else, whatever, and then
window.location.assign('http://github.com');
}
render() {
return (
<button onClick={this.handleClick.bind(this)} />
);
}
}
No need to import window since it's global. Should work perfectly in any modern browser.
Also, if you have a component that is declared as a function, you may possibly use the effect hook to change location when state changes, like
const Button = () => {
const [clicked, setClicked] = useState(false);
useEffect(() => {
if (clicked) {
// do something meaningful, Promises, if/else, whatever, and then
window.location.assign('http://github.com');
}
});
return (
<button onClick={() => setClicked(true)}></button>
);
};
You don't need react-router for external links, you can use regular link elements (i.e. <a href="..."/>) just fine.
You only need react-router when you have internal navigation (i.e. from component to component) for which the browser's URL bar should make it look like your app is actually switching "real" URLs.
Edit because people seem to think you can't use an <a href="..." if you need to "do work first", an example of doing exactly that:
render() {
return <a href={settings.externalLocation} onClick={evt => this.leave(evt)}/>
}
async leave(evt) {
if (this.state.finalized) return;
evt.preventDefault();
// Do whatever you need to do, but do it quickly, meaning that if you need to do
// various things, do them all in parallel instead of running them one by one:
await Promise.all([
utils.doAllTheMetrics(),
user.logOutUser(),
store.cleanUp(),
somelib.whatever(),
]);
// done, let's leave.
this.setState({ finalized: true }), () => evt.target.click());
}
And that's it: when you click the link (that you styled to look like a button because that's what CSS is for) React checks if it can safely navigate away as a state check.
If it can, it lets that happen.
If it can't:
it prevents the navigation of occurring via preventDefault(),
does whatever work it needs to do, and then
marks itself as "it is safe to leave now", then retriggers the link.
You can try and create a link element and click it from code. This work for me
const navigateUrl = (url) => {
let element = document.createElement('a');
if(url.startsWith('http://') || url.startsWith('https://')){
element.href = url;
} else{
element.href = 'http://' + url;
}
element.click();
}
As pointed by #Mike 'Pomax' Kamermans, you can just use to navigate to external link.
I usually do it this way, with is-internal-link
import React from 'react'
import { Link as ReactRouterLink} from 'react-router-dom'
import { isInternalLink } from 'is-internal-link'
const Link = ({ children, to, activeClassName, ...other }) => {
if (isInternalLink(to)) {
return (
<ReactRouterLink to={to} activeClassName={activeClassName} {...other}>
{children}
</ReactRouterLink>
)
}
return (
<a href={to} target="_blank" {...other}>
{children}
</a>
)
}
export default Link
Disclaimer: I am the author of this is-internal-link
I had the same issue and my research into the issue uncovered that I could simply use an "a href" tag. If using target="_blank" you should write your link this...
Your Link
I couldn't find a simple way to do that with React Router. As #Mike wrote you should use anchor (<a> tags) when sending the user to external site.
I created a custom <Link> component to dynamically decide whether to render a React-Router <Link> or regular <a> tag.
import * as React from "react";
import {Link, LinkProps} from "react-router-dom";
const ReloadableLink = (props: LinkProps & { forceReload?: boolean }) => {
const {forceReload, ...linkProps} = props;
if (forceReload)
return <a {...linkProps} href={String(props.to)}/>;
else
return <Link {...linkProps}>
{props.children}
</Link>
};
export default ReloadableLink;

Resources