Darren
Darren

Reputation: 2290

useEffect doesn't update state on route change

I am looking for a solution to register the route change and apply new state using setState and useEffect. The current code below doesn't update functions of setState when the route is changed.

For example, I register the pathname of / with location.pathname === '/' within createContext, if the pathname is / the setState of isHome is registered true, however if pathname is /page-1 setState is registered false.

On browser reloads, onMount the state is correctly set, however on a route change using Link this does not. Also, please note that I am using Gatsby and in doing so, importing { Link } from 'gatsby'

CreateContext.js

export const GlobalProvider = ({ children, location }) => {


const prevScrollY = useRef(0);
  const [state, setState] = useState({
    isHome: location.pathname === '/',
    // other states
  });

  const detectHome = () => {
    const homePath = location.pathname === '/';
    if (!homePath) {
      setState(prevState => ({
        ...prevState,
        isHome: false
      }));
    }
    if (homePath) {
      setState(prevState => ({
        ...prevState,
        isHome: true
      }));
    }
  };

  useEffect(() => {
    detectHome();
    return () => {
      detectHome();
    };
  }, [state.isHome]);

  return (
    <GlobalConsumer.Provider
      value={{
        dataContext: state,
      }}
    >
      {children}
    </GlobalConsumer.Provider>
  );
};

If I console.log(state.isHome) on pathname / I get true, any other pathnames I get false, however, if I change route, the current isHome state remains previous, until I scroll and useEffect applies.

The point of registering the isHome state is to alter CSS per page.

How can I update state with useEffect when changing route. Previously, I would have done this with componentDidUpdate and register prevProps.location.pathname against props.location.pathname, however, my understanding is that this is no longer necessary with the useEffect hook.

Upvotes: 7

Views: 19538

Answers (3)

Mohamed Ramrami
Mohamed Ramrami

Reputation: 12691

The effect you want is "When the location change then update my state", this is translated in useEffect code like this :

  useEffect(() => {
    detectHome();
    return () => {
      detectHome();
    };
  }, [location]);

Upvotes: 5

Fuechter
Fuechter

Reputation: 1240

I think if you use GlobalProvider as root component, in this case, it only render one time unless something change the states or props. So some explanation:

  useEffect(() => {
    detectHome();
    return () => {
      detectHome();
    };
  }, [state.isHome]);

This code above, the state is updated by only this useEffect, so it only update the state one time after first render, and the detectHome inside of return of useEffect only execute when other update happen or the state.isHome is different of first time. It's little confused this explanation, but that is.

But for the solution is use the 'popstate' event of window:

export const GlobalProvider = ({ children, location }) => {


const prevScrollY = useRef(0);
  const [state, setState] = useState({
    isHome: location.pathname === '/',
    // other states
  });

  const detectHome = () => {
    const homePath = location.pathname === '/';
    if (!homePath) {
      setState(prevState => ({
        ...prevState,
        isHome: false
      }));
    }
    if (homePath) {
      setState(prevState => ({
        ...prevState,
        isHome: true
      }));
    }
  };

  useEffect(() => {
    window.addEventListener('popstate', detectHome)
    return () => {
      window.removeEventListener('popstate', detectHome)
    };
  }, [state.isHome]);

  return (
    <GlobalConsumer.Provider
      value={{
        dataContext: state,
      }}
    >
      {children}
    </GlobalConsumer.Provider>
  );
};

I think that Gatsby should manipulate the history, so this will fire popstate of browser, and you can detect the url's changes.

Upvotes: 0

Alex Gessen
Alex Gessen

Reputation: 568

If you are using react-router you can subscribe to location change event in your useEffect:

import {browserHistory} from 'react-router';

...
useEffect(() => {
  return browserHistory.listen(detectHome);
}, []);
...

This would subscribe your detectHome function for location change on mount and unsubscribe it on unmount.

Upvotes: 1

Related Questions