简体   繁体   中英

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)
}, []);

We use it like this:

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

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.

I tried to reproduce your problem on CodeSandbox, using your code. But everything works fine.

You can check it here:

编辑居高临下的kapitsa-l2j4i

Possible problems:

  • May be you call addFlag somewhere right after calling dismissAllFlags . I added commented line in the sandbox example. With this line the code works like you have described in the question.
  • Another versions of dependencies.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM