skip to Main Content

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:

  1. I navigate to First page, status is initial, shouldRefresh is true.
  2. API in First page is called, markAsUnedited is called.
  3. I navigate to Second page, trigger the useEffect callback for First page, which change the status to initial and shouldRefresh to true.
  4. 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.

Demo reproduce of the problem

2

Answers


  1. Chosen as BEST ANSWER

    After few trials with different approach, I found the solution, but still figuring why it is working...

    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;
        }, [context]); // <-- Add the Context here
      }
      return context;
    };
    

    I need to add the context into the useEffect dependencies. Appreciate if any can help to explain this...


  2. 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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search