useEffect doesn't update state on route change

useeffect doesn t run
useeffect only on mount
useeffect componentdidmount
useeffect componentwillunmount
useeffect(async)
react hooks
useeffect prevprops
useeffect in class component

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.

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

Using the Effect Hook – React, import React, { useState, useEffect } from 'react'; function Example() { const [count, This tells React that your effect doesn't depend on any values from props or  [] means the effect doesn’t use any value that participates in React data flow, and is for that reason safe to apply once. It is also a common source of bugs when the value actually is used. You’ll need to learn a few strategies (primarily useReducer and useCallback ) that can remove the need for a dependency instead of incorrectly omitting it.

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.

UseEffect doesn't run after state updated · Issue #2091 · enzymejs , import React, { useEffect, useState } from 'react'; import { mount } from 'enzyme'; export const MyComponent = () => { const [value, setValue]  In the simplest terms, useEffect is a Hook that allows you to perform side-effects in functional components. For even more detail, these effects are only executed after the component has rendered,

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.

Why useEffect doesn't fire on every render?, In Mobx, just like Observer component which provides a render function callback, autorun function also executes independently of the react  The useEffect function is like the swiss army knife of hooks. It can be used for a ton of things, from setting up subscriptions to creating and cleaning up timers to changing the value of a ref. One thing it’s not good for is making DOM changes that are visible to the user.

A Complete Guide to useEffect, means the effect doesn't use any value that participates in React data flow, and is for that reason safe to apply once. It is also a common source  propertiesis a Map, and when passed in dependency array to useEffect, it will be recreated on every render, and when compared, it will always be not equal to itself, since the map, same as other non-primitive types in JS are compared by reference and not by value. So this will cause the function inside the useEffectto be run on every re-render.

React useEffect hook with code examples, React useEffect is a hook that gets triggered for componentDidMount, know that your useEffect function doesn't depend on any values from props or state. It doesn’t suffer from this bug. (But we also didn’t make any changes to it.) There is no special code for handling updates because useEffect handles them by default. It cleans up the previous effects before applying the next effects.

Fetching Data and Updating State with Hooks, setState() call in class components, except that it doesn't merge the old and The useEffect hook takes two arguments, a function and a list of  Stack Overflow for Teams is a private, secure spot for you and your coworkers to find and share information. Learn more How to call an async function inside a UseEffect() in React?

Comments
  • But It is calling the function twice on page load. Can you please guide here?
  • Open a new question with the relevant code
  • 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.
  • 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.