How can i use react-router Link efficiently? - reactjs

when I use the Link in the navbar in react it takes me to the suggested URL again and again even if I am present at that home ,how can I redirect conditionally such that if I am on the home page then after clicking the home button again, I will not go to the home page again
<Link className="link" to="/leaderboards">
<Home/>
</Link>

Use <NavLink /> component to redirect to other routes and set the active className as disabled class.
App
<NavLink to="/" className="link" activeClassName="disabled-link">Home</NavLink>
CSS
.disabled-link {
pointer-events: none;
}

That's not how you use a Link. You just specify a pathname in a Link component, and manage your paths-components inside a Router.
Now you just render a <Home /> component always, hence the issue.

The open Github issue (at time of writing) for this is here: https://github.com/ReactTraining/react-router/issues/5362
To reiterate the solution here, you can create a hook for to prevent similar locations from being pushed consecutively:
import { useHistory } from "react-router-dom";
export default function useLocationBlocker() {
const history = useHistory();
useEffect(
() => {
history.block(
(location, action) =>
action !== "PUSH" ||
getLocationId(location) !== getLocationId(history.location)
);
},
[] // eslint-disable-line react-hooks/exhaustive-deps
);
}
function getLocationId({ pathname, search, hash }) {
return pathname + (search ? "?" + search : "") + (hash ? "#" + hash : "");
}
To reproduce, click the Home link more than once and notice that you can press the back button the same number of times. (https://codesandbox.io/s/r0wXp0Njw?file=/index.js)

Related

How to prevent the page CSS transitions when the browser’s back button is clicked

I have a simple page transition that gets called when clicking a link. The new page animates from the bottom of the screen upwards to the top. It works fine, but it's also working when you click the back button in the browser. I don't want the transition to fire if someone clicks the back button on the browser, I just want to go back to the page at whatever scrolled position I left it in, in exactly the same way you would expect to happen by clicking the browser back button.
If I take the CSS transitions off then the back button works fine, so I need to somehow differentiate between the back button being clicked and a link being clicked and then play the transition accordingly.
I don't know if I need to setState or something else? I did try that, but it seemed to set the state variable after the transition had happened.
import React, { useEffect, useState } from 'react';
import {Route, NavLink, Switch, useHistory } from "react-router-dom";
import './App.css';
import Home from './Home';
import About from './About';
import { CSSTransition, TransitionGroup,} from 'react-transition-group';
const usePageMeta = (title, description) =>{
const defaultTitle = "app-name";
const defaultDesc = "meta description";
useEffect(() => {
document.title = title || defaultTitle;
document.querySelector("meta[name='description']").setAttribute("content", description || defaultDesc);
}, [defaultTitl
e, title, defaultDesc, description]);
};
const App = () =>{
return (
<div className="App">
<div className="nav fixed top-0 left-0 z-10 w-full">
<NavLink exact to="/" activeClassName="active">Home</NavLink>
<NavLink to="/about" activeClassName="active">About</NavLink>
</div>
<Route render={({location}) => (
<TransitionGroup>
<CSSTransition
key={location.key}
timeout={1000}
classNames="fade"
>
<Switch location={location}>
<Route exact path="/">
<Home usePageMeta={usePageMeta}/>
</Route>
<Route path="/about">
<About usePageMeta={usePageMeta}/>
</Route>
</Switch>
</CSSTransition>
</TransitionGroup>
)} />
</div>
);
}
export default App;
Can anyone point me in the right direction please?
After some testing, I can see that this is bound to happen as the path is being loaded into the history of the browser.
tl;dr
This is not a quirk of the library, its just the way the browsers work. The current session always pushes state on the history object.
To prevent this you can do two things
Overwrite the history state whenever the user navigates to a new path.
Prevent the default action of the back button and redirect to the entry site.
Solution 1:
import createBrowserHistory from 'history' package and make a mutable history outside the app component.
make a immutable string of the start state when entering the app with history.createHref(history.location)
define an onClick handler which replaces the histories's top state to current location.
register a click handler on both NavLinks with the above defined function.
add a listener to the history (history.listen) and call history.goBack() if the action is pop.
call unlisten on unmount.
import { createBrowserHistory } from 'history';
let history = createBrowserHistory();
const ContainerComponent = () => {
// removed uselocation and usehistory hooks
const location = history.createHref(history.location);
const replaceHistory = () => {
history.replace(location);
}
const unlisten = history.listen((location, action) => {
if (action == "POP")
history.goBack();
})
useEffect(() => {
return () => unlisten();
})
return (
<div>
<Navlink to='/' onClick={replaceHistory} />
<Navlink to='/about' onClick={replaceHistory} />
</div>
);
}
Solution 2:
import createBrowserHistory from the 'history' module and create a mutable history object outside the app component.
add a listener to the history (history.listen) which goes back '-1' number of pages when it recives the "POP" action.
useEffect to 'unlisten' this event.
Note: this is assuming the user has to be navigated to the site which came from.
import { createBrowserHistory } from 'history';
// Creates a mutable history state.
let history = createBrowserHistory();
const ContainerComponent = () => {
//removed the eventlistener on window
const unlisten() = history.listen((location, action) => {
//remove the logging once satisfied with the result.
console.log(`${location}, ${action}`);
if ( action == "POP" ) {
history.goBack();
}
})
// removed 'popstate' evenhandler
// adding useEffect to call unlisten on unmount
useEffect (() => {
return () => unlisten();
}, [])
const count = () => {
setPageChanges(pageChanges + 1);
}
return (
<div>
<NavLink to='/' onClick={count} />
<NavLink to='/about' onClick={count} />
</div>
{/*... rest of the switch code with the transition ...*/}
);
}
Edit: Solution 2 is refactored to use the 'history' package utilities.
This is seen more appropriate as the browser states are not touched with react code.
Also in the docs, it was mentioned that the history object is mutable which does result in some problems with mounting.
Caveat: this solution goes through all the states in between where the user is and where they started from, so expect some flashes of the earlier components.
Solution 1 also uses the same package, with the caveat that it will go to the first page and then goBack() 1 page more. This will be less of a flash then solution 2.

Access the route in different way in next js

I have an application in nextjs. There i built the next route:
<Link
href="colors/[type]"
as={`colors/${type}`}
>
<a>click</a>
</Link>
This route redirect me on /colors/red, or colors/blue and so on, depending by the user. type here is a varible = dynamic element.
Also i have other link:
<Link href="colors">
<a>click to colors</a>
</Link>
This route should redirect me on /colors, but when i click i get error, because the first url colors/red is not equal with this type: colors. So i have to put something after colors to make things happen.
How to solve the issue? and how to make my first route to accept the last parameter as optional.?
The best way to do this is to use dynamic route (more). Start by creating a new page in your /colors folder named [[...type]].js that will catch either /colors or /colors/red. This setup will also catch route like /colors/red/blue.
You can retrieve the variable part of the url by using the next Router (see example below).
import React from 'react';
import Link from "next/link";
import {useRouter} from "next/router";
export default () => {
const router = useRouter()
const { type } = router.query;
return (
<div>
<Link href={'/'}>
Home
</Link>
<Link href={'/colors/red'}>
Red
</Link>
<Link href={'/colors/blue'}>
Blue
</Link>
<div>
{type ? (
`Type: ${type}`
) : (
'no type'
)}
</div>
</div>
);
}

what differences between Link in react-router and `window.history.pushState()`?

in my code when i click button, component Aaaaa is not re-rendered, but when i tap on link, component Aaaaa is re-rendered. i can't find cause of it?
function App() {
return (
<>
<button onClick={() => window.history.pushState('','','/about')}>About</button>
<Link to='/about'>to About</Link>
<Aaaaaa/>
</>
);
}
and:
Aaaaaa(){
const location = useLocation()
return <div>About </div>
}
The proper way is to use <Link to='/about'>to About</Link> when trying to navigate manually (by clicking a button) and window.history.pushState('','','/about') when trying to navigate automatically (like after completing an API call).
cause window.history.pushState is not using the react router
so you can use link to navigate between the pages.
but if you have limits and you want it to be a buttn and still navigate using react router, you can use history from react-router-dom
import { withRouter } from 'react-router-dom'
// some other codes
const { history } = props;
// some other codes
<button onClick={() => history.push('/about')}>About</button>
// some other codes
export default withRouter(MyComponent)
or you can use 'useHistory' hook if you're using react-router v5.
import { useHistory } from 'react-router-dom'
// some other codes
const history = useHistory();
// some other codes
<button onClick={() => history.push('/about')}>About</button>
// some other codes
export default MyComponent
I found that window.history.pushState('','','/about') does not work as expected. The router does not update what route to display.
If you cant use a button and need to control the location programatically, but use class components wrap it in a function component like this:
... other routes
<Route exact path="/register" component={()=>{
const history = useHistory();
return <RegisterPage onRegister={async (account) => {
this.setState({ account });
history.push('/'); // sends user automatically to main page
}} />
}} />
...
window.history.pushState method doesn't trigger any browser event, which in turn doesn't notify react router about the update you made to the history object
React router custom Link component is most likely going to use a custom event or the observer design pattern to be able to notify and respond to changes in the window history object

react-router-dom history.goBack() not working on Firefox and Safari

I have a component like this (simplified version)
import React, {Fragment, useEffect, useState} from 'react';
import {Link, useHistory, useParams} from 'react-router-dom';
const MyComponent = () => {
let history = useHistory();
const handleGoBack = () => {
console.log('going back...');
history.go(-1); // or history.goBack();
};
return (
<Fragment>
<Link to="#" onClick={handleGoBack}>< Back</Link>
</Fragment>
);
}
it works as expected in chrome. not working at all in Firefox and safari. The console shows me the handleGoBack function is called as expected but the history.goBack does only on chrome.
any idea please?
The problem is not in the code you provided, but in something else. I created Codesandbox based on your code snippet, and history.go(-1) is working in latest versions of Firefox and Safari
I just faced the exact same issue. So in your code, you are putting a link like this:
<Link to="#" onClick={handleGoBack}>< Back</Link>
I saw that in firefox, that gets rendered as an anchor tag which has the url as current url.
So I just replaced that Link with a button and handled the onclick event as usual and it worked like a charm.
This is what worked for me while using React Router:
history.goBack() works, but I had to check for "/" and stop when that was reached. The history counting business doesn't work, so I had to search for the "/" path to know when to hide/disable the "<" back button.
import React from "react";
import { withRouter } from "react-router";
import { NavBar, Icon } from "antd-mobile";
import MainSearchContainer from "../components/MainSearchContainer";
import { render } from "#testing-library/react";
function PageHeader({location, history, db}) {
const appTitle = "My App";
return (
<React.Fragment>
<NavBar
icon={location.pathname !== "/" ? <Icon type="left" /> : ""}
onLeftClick={() => { history.goBack(); render(); } }
mode="dark"
rightContent={[<Icon key="1" type="ellipsis" />]}
>
{appTitle}
</NavBar>
<MainSearchContainer db={db} history={history} />
</React.Fragment>
);
}
export default withRouter(PageHeader);
I just faced the same issue, but Safari only. In my case the issue was caused by "#" present in href attribute in my "Go back" link. My onClick callback was not calling evt.preventDefault(), so it seems that the "#" was "overwriting" the history.goBack() call.
Before
onBackClick()
{
let history = this.props.history;
history.goBack();
}
...
</i>
After (Solution)
onBackClick(evt)
{
evt.preventDefault();
evt.stopPropagation();
let history = this.props.history;
history.goBack();
}
...
</i>

Can I add a property or state into Next JS Router like React-Route?

I would like to know if it's possible to add a state inside a Router.router link using Next.JS.
I know how to handle router.query but i don't want to show the state on that query.
You can try
<Link
to={{
pathname: "/courses",
search: "?sort=name",
hash: "#the-hash",
state: { fromDashboard: true }
}}
/>
later you will be able to access that state in the location object.
see:
https://reacttraining.com/react-router/web/api/location
https://reacttraining.com/react-router/web/api/Link
Edit: with Next JS's Link component it is a bit more limited. you could utilize the as prop.
<Link href="/dashboard?from=loginPage" as="/dashboard" />
only /dashboard will be shown in the URL, you will still have access to from=loginPage in the query. You will have to serialize your state to a query string (and values will become string.
here's a quick example:
in Nav:
<Link href='/?from=navClick' as="/">
<a>Home</a>
</Link>
In the home component (which renders on '/')
import { useRouter } from 'next/router';
const Home = () => {
const { query } = useRouter();
return (
<>
{query.from ? `You are from: ${query.from}` : 'Well met.'}
</>
);
};
If the user goes to the home page by clicking on that link, you will get the from value in the query object, however it won't be rendered in the browser's address bar.

Resources