'Why useEffect doesn't run on window.location.pathname changes?
Why useEffect doesn't run on window.location.pathname changes? I get loc logged only once.
How can I make to run useEffect when pathname changes without any additional libraries?
useEffect(() => {
const loc = window.location.pathname
console.log({ loc })
}, [window.location.pathname])
Solution 1:[1]
Create a hook, something like:
const useReactPath = () => {
const [path, setPath] = React.useState(window.location.pathname);
const listenToPopstate = () => {
const winPath = window.location.pathname;
setPath(winPath);
};
React.useEffect(() => {
window.addEventListener("popstate", listenToPopstate);
return () => {
window.removeEventListener("popstate", listenToPopstate);
};
}, []);
return path;
};
Then in your component use it like this:
const path = useReactPath();
React.useEffect(() => {
// do something when path changes ...
}, [path]);
Of course you'll have to do this in a top component.
Solution 2:[2]
I adapted Rafael Mora's answer to work for the entire location object and also work in the front end of Next.js apps using the useIsMounted approach, and added typescript types.
hooks/useWindowLocation.ts
import useIsMounted from './useIsMounted'
import { useEffect, useState } from 'react'
const useWindowLocation = (): Location|void => {
const isMounted = useIsMounted()
const [location, setLocation] = useState<Location|void>(isMounted ? window.location : undefined)
useEffect(() => {
if (!isMounted) return
const setWindowLocation = () => {
setLocation(window.location)
}
if (!location) {
setWindowLocation()
}
window.addEventListener('popstate', setWindowLocation)
return () => {
window.removeEventListener('popstate', setWindowLocation)
}
}, [isMounted, location])
return location
}
export default useWindowLocation
hooks/useIsMounted.ts
import { useState, useEffect } from 'react'
const useIsMounted = (): boolean => {
const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
setIsMounted(() => true)
}, [])
return isMounted
}
export default useIsMounted
Solution 3:[3]
useEffect is evaluated every time your component renders. To subscribe to changes to location.pathname, you'll need to add a listener to the window's 'popstate' event that updates state, which tells the component tree to rerender.
Rafel Mora's answer implements a hook using setState, which will cause the component to rerender. You can then use the returned state value from the hook in your useEffect in place of window.location.pathname.
Related - here's the ESLint warning you'll see if you use eslint-plugin-react-hooks:
Outer scope values like 'window.location.pathname' aren't valid dependencies because mutating them doesn't re-render the component
If you're open to using a library, React Router offers a useLocation hook.
Solution 4:[4]
I don't know why but for me adding listeners for 'popstate' never worked and I was able to get useEffect to change when window.location.pathname changes, similar to what karolis did in their original question without issue. This is what I did:
let path = window.location.pathname;
/* potentially changes navbar on page change */
useEffect(() => {
if (window.location.pathname === "/") {
setScrollNav(false);
} else {
setScrollNav(true);
}
}, [path]);
This seemed to solve the problem I was having, but it seems I had a very different experience compared to everyone else so I would love to know your thoughts.
Solution 5:[5]
Weird no one mentioned this but, you can get location from react-router-dom using the useLocation hook. So you can just use that in the dependency array.
Docs here
const location = useLocation();
useEffect(() => {
console.log(location);
}, [location.pathname]);
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Rafael Mora |
| Solution 2 | animatedgif |
| Solution 3 | JBallin |
| Solution 4 | PrettyMuchDone |
| Solution 5 | Scramjet |
