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

91

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

85

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

78

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 
vote vote

69

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

51

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?







Related QUESTION?