Using useState hook in useEffect on history.listen

I am having some troubles with useState when listening to history changes in useEffect.

When the pathname changes, setState is initiated but then the state is added back.

For example, I have a flag component that collects groups of notifications, but on the pathname change, I want all flags to be dismissed and removed from state.

The flag component

const PageFlag = ({ history }: InterfaceProps) => {
const { contextData, dismissFlag, dismissAllFlags } = useContext(GlobalConsumer);

  useEffect(() => {
    history.listen(() => {
      dismissAllFlags();
    });
  });

  return (
    <>
      <FlagGroup onDismissed={dismissFlag}>
        {contextData.flags.map((flag, index) => (
          <Flag key={index} {...flag} />
        ))}
      </FlagGroup>
    </>
  );
};

History prop is used from import { withRouter } from 'react-router-dom'

The state and function for dismissAllFlags is shown in a createContext component as

const DefaultState: InterfaceState = {
  otherStateExample: false,
  flags: []
};

export const GlobalConsumer = createContext({
  contextData: DefaultState,
  addFlag: (flagData: any) => {},
  dismissFlag: () => {},
  dismissAllFlags: () => {}
});

export const GlobalProvider = ({ children }: InterfaceProps) => {
  const [state, setState] = useState<InterfaceState>({
    ...DefaultState
  });

  return (
    <GlobalConsumer.Provider
      value={{
        contextData: state,
        addFlag: (flagData: any) => {
          setState({ ...state, flags: [flagData].concat(state.flags) });
        },
        dismissFlag: () => {
          setState({ ...state, flags: state.flags.slice(1) });
        },
        dismissAllFlags: () => {
          setState({ ...state, flags: [] });
        }
      }}
    >
      {children}
    </GlobalConsumer.Provider>
  );
};

The problem arises, where on pathname change, dismissAllFlags uses setState to set flags as [] but then adds back the previous state with the flags.

How can I remove all flags but remember the current state for other items?

You are missing the second input parameter on useEffect(), which is going to cause the listener to be readded on every render.

It should look like this, note you also should not need the inner function.

useEffect(() => {
  history.listen(dismissAllFlags)
}, []);

How to listen to route changes in react router v4?, If you are using BrowserRouter , you can import withRouter and wrap your withRouter , history.listen , and useEffect (React Hooks) works quite nicely setSearch] = useState(parsed.search ? parsed.search : '') useEffect(()  For example, useState is a Hook that lets you add React state to function components. We’ll learn other Hooks later. When would I use a Hook? If you write a function component and realize you need to add some state to it, previously you had to convert it to a class. Now you can use a Hook inside the existing function component.

We use it like this:

const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
    history.listen(() => setIsOpen(false));
  }, [history]);

Building a site navigation menu using React Hooks, I was able to use state in my component while keeping it as a functional component. The history object has a .listen() method which accepts a callback open/close state, and the built-in useEffect hook to listen to changes  Combine the ability to use useRef as an instance variable with the fact that the useEffect Hook is always triggered after the return statement of the parent component is evaluated, and you have an invaluable weapon at your fingertips. You can find even more use cases for these and gain a deeper understanding of how this works to truly master Hooks.

If I understand what your asking is this:

You would like to set the flags to an empty array without have to add the previous values using the spread method that you are currently doing {...state, flags: []}.

Well, this is not possible using useState and you should be cautions with nested state objects as cloning can becoming expensive with larger objects. Maybe that is what your trying to avoid here.

Even if you switch to useReducer you would still end up prop spreading for the state.

Perhaps you should just have flags as its own state const [flags, setFlags] = useState([]) or look into immutable-helper.

Moreover, Try to respect react-hooks/exhaustive-deps otherwives funny things can start happening. If no deps then at least give your hook an empty array to ensure it is performed once.

How to convert withRouter to a React Hook - Charles Stover, One of the greatest benefits of React Hooks in the new v16.7 is the removal of the not expose the Router Context, which we are using to listen to location changes. This declutters the history, location, and match props from my component It is merely a shorthand for calling the useState hook to force a  Fetch Data With useEffect. Let’s look at another common use case: fetching data and displaying it. In a class component, you’d put this code in the componentDidMount method. To do it with hooks, we’ll pull in useEffect. We’ll also need useState to store the data.

React useEffect hook with code examples, Those lifecycles are componentDidMount , componentDidUpdate , and componentWillUnmount lifecycles. Basic usage of useEffect. React useState example With hooks, we can use state and other React features without writing a class. With useState, we can read and set state in function component: import { useState, useEffect }

useHooks, We bring you easy to understand React Hook code recipes so you can learn how React import React, { useState, useEffect, useRef } from 'react'; // Usage function useLocation(); const history = useHistory(); const match = useRouteMatch(); If you find yourself adding a lot of event listeners using useEffect you might  Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class. The Effect Hook lets you perform side effects in function components:

[v6] Unlisten history on unmounted · Issue #7195 · ReactTraining , Why not just move the entire subscription within the useEffect hook? We've run into tons of problems with this in React Redux for the same reason. useState(​history.location); let [startTransition, pending] = useTransition({ to listen to history before the Route component finish mounting (as shown in the above test case). The most popular and commonly used hooks are useState and useEffect. useState. The state can now be used inside functional components that is made possible by the useState hook.

Comments
  • can you provide a codesandbox?
  • It seems that your GlobalProvider could get unmounted in-between pathname changes along with its default state.