7

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.

Darren
  • 2,176
  • 9
  • 42
  • 98

3 Answers3

5

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]);
Mohamed Ramrami
  • 12,026
  • 4
  • 33
  • 49
1

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.

Alex Gessen
  • 550
  • 5
  • 4
  • Thank you Alex, I believe Gatsby uses Reach-Router and browserHistory is not available. I will look into this further but thank you for your answer, no doubt it will help those not using Gatsby. – Darren Sep 25 '19 at 13:17
0

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.

Fuechter
  • 1,200
  • 2
  • 10
  • 27
  • Thank you Fuechter, unfortunately, this doesn't work. To further explain, the state `isHome` remains `true` or `false` and stops updating on scrolling or other event changes. – Darren Sep 25 '19 at 13:22
  • 1
    [This](https://stackoverflow.com/questions/4570093/how-to-get-notified-about-changes-of-the-history-via-history-pushstate/4585031#4585031) worked for me. – Chad Johnson May 07 '21 at 18:14