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:
Possible problems:
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.
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.