I am using a custom IsEditedContext
. There are 3 status, initial
, edited
, and unedited
. shouldRefresh
is depending on status
. I am using react-router-dom
to navigate across the page with <Link>
component.
The expected result of the context is:
- I navigate to
First
page,status
is initial,shouldRefresh
is true. - API in
First
page is called,markAsUnedited
is called. - I navigate to
Second
page, trigger the useEffect callback forFirst
page, which change thestatus
to initial andshouldRefresh
to true. - API in
Second
page is called,markAsUnedited
is called.
IsEditedContext
import { FC, PropsWithChildren, createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
enum EditStatus {
Initial = 'initial',
Edited = 'edited',
Unedited = 'unedited',
}
interface IsEditedContextType {
shouldRefresh: boolean;
status: EditStatus;
markAsEdited: () => void;
markAsUnedited: () => void;
init: () => void;
}
const defaultContext: IsEditedContextType = {
status: EditStatus.Initial,
markAsEdited: () => {},
markAsUnedited: () => {},
shouldRefresh: false,
init: () => {},
};
const IsEditedContext = createContext<IsEditedContextType>(defaultContext);
export const useIsEdited = (resetOnUnmount = false) => {
const context = useContext(IsEditedContext);
if (!context) {
throw new Error('useIsEdited must be used within a IsEditedProvider');
}
if (resetOnUnmount) {
useEffect(() => {
return context.init;
}, []);
}
return context;
};
export const IsEditedProvider: FC<PropsWithChildren> = ({ children }) => {
const [status, setStatus] = useState<EditStatus>(EditStatus.Initial);
const markAsEdited = useCallback(() => setStatus(EditStatus.Edited), []);
const markAsUnedited = useCallback(() => setStatus(EditStatus.Unedited), []);
const init = useCallback(() => setStatus(EditStatus.Initial), []);
const shouldRefresh = useMemo(() => {
return status === EditStatus.Initial || status === EditStatus.Edited;
}, [status]);
return (
<IsEditedContext.Provider value={{ status, init, markAsEdited, markAsUnedited, shouldRefresh }}>
{children}
</IsEditedContext.Provider>
);
};
First and Second page
import { useEffect } from "react";
import { useIsEdited } from "../IsEditedContext";
const First = () => {
const { shouldRefresh, markAsUnedited } = useIsEdited(true);
useEffect(() => {
if (shouldRefresh) {
console.log('First page API call');
markAsUnedited();
};
}, []);
return ( <>First</> );
}
export default First;
Problem encounter
The unmount for First
is fired after the mount of Second
, which does not clean up the status to initial and cause the API is Second
is not called.
2
Answers
After few trials with different approach, I found the solution, but still figuring why it is working...
I need to add the context into the useEffect dependencies. Appreciate if any can help to explain this...
inside your effect, in the if condition, try
return () => init
. when the component unmounts, after it refreshed, it will reset your memo, which will clear the if condition, and clean up on unmount