'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