reactjs - Why useEffect doesn't run on window.location.pathname changes?

ID : 274479

viewed : 49

Tags : reactjsreactjs

Top 5 Answer for reactjs - Why useEffect doesn't run on window.location.pathname changes?

vote vote


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.

vote vote


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]); 
vote vote


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.


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 


import { useState, useEffect } from 'react'  const useIsMounted = (): boolean => {   const [isMounted, setIsMounted] = useState(false)   useEffect(() => {     setIsMounted(() => true)   }, [])    return isMounted }  export default useIsMounted 
vote vote


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.

vote vote


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.

Top 3 video Explaining reactjs - Why useEffect doesn't run on window.location.pathname changes?