DEV Community

Ayako yk
Ayako yk

Posted on

Challenges, Solutions, and Reflections Encountered with useContext and Provider

While refactoring a project, I encountered difficulty obtaining values. Despite familiarizing myself with useContext and Provider, I encountered challenges with managing parent/child structures, particularly as the code grew longer and more complex. Reflecting on this now, I realize that the issue I faced was quite fundamental. To prevent similar challenges in the future, I will summarize the mistakes I made and their corresponding solutions.

Topic:

  1. useContext and Provider
  2. Rendering Rule
  3. Challenges Encountered During Refactoring

useContext and Provider:
Components within the provider can access the value without the need for prop passing.

<ParentProvider>
  <ChildA />
  <ChildB />
  <ChildC />
</ParentProvider>
Enter fullscreen mode Exit fullscreen mode

ParentProvider.tsx

type ParentProviderProps = {
  children: ReactNode;
};

export const ParentContext = createContext<string>('');

export const ParentProvider = ({ children }: ParentProviderProps) => {
  const [value, setValue] = useState<string>('example');

  return (
    <ParentContext.Provider value={value}>{children}</ParentContext.Provider>
  );
Enter fullscreen mode Exit fullscreen mode

ChildA.tsx

const ChildA = () => {
  const value = useContext(ParentContext);
  return (
    <>
      <p>Child A</p>
      <div>{value}</div>
      <hr />
      <GrandChild />
      <hr />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

ChildB.tsx

const ChildB = () => {
  const value = useContext(ParentContext);
  return (
    <>
      <p>Child B</p>
      <div>{value}</div>
      <hr />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

ChildC.tsx

const ChildC = () => {
  const value = useContext(ParentContext);
  return (
    <>
      <p>Child C</p>
      <div>{value}</div>
      <hr />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

GrandChild.tsx

const GrandChild = () => {
  const value = useContext(ParentContext);
  return (
    <>
      <p>GrandChild</p>
      <div>{value}</div>
      <hr />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Image description

React checks whether the value within the provider has changed. If it has, the consuming components must be updated accordingly.

Rendering Rule:
When a value changes, React renders recursively from top to bottom, updating parents first and then their children, such as Child A, Child B, and so on.

Even if Child A doesn't directly consume the value from the provider, it will still render due to its parent's update.
--> This aspect could be explored in more depth.

The problem I encountered was as follows:

export const Page = () => {
  //...
  <xxxProvider>
    <yyyProvider>
      <DeleteBtn />
      <DeletePopUp />
    </yyyProvider>
  </xxxProvider>
}
Enter fullscreen mode Exit fullscreen mode

<DeletePopUp />contained values and dispatched actions from useContext(). Since both <DeleteBtn /> and <DeletePopUp /> were child components of <Page>, whenever there were changes in the state, the values were promptly updated and made available in the child components.

Refactoring:
I needed to refactor it and move all the functions inside <DeletePopUp /> to <Page>. Then, const zzz = useContext(xxxDispatchContext) returned undefined.
Now, const zzz = useContext(xxxDispatchContext) is in the parent component and is outside the scope of the provider, so <DeletePopUp /> no longer has direct access to the context provided by <xxxProvider>.

<Page> // const zzz = useContext(xxxDispatchContext) is now up here
  <xxxProvider>
    <yyyProvider>
      <DeleteBtn />
      <DeletePopUp /> // const zzz = useContext(xxxDispatchContext) was here
    </yyyProvider>
  </xxxProvider>
</Page>
Enter fullscreen mode Exit fullscreen mode

When I call const zzz = useContext(xxxProvider) inside <Page />, it tries to access the value provided by <xxxProvider>. However, if <Page /> is outside the scope of <xxxProvider>, then zzz will be undefined because there's no provider higher up in the component tree providing the xxxProvider context.

If I had been able to simplify the structure and create the image above, I would have solved the issue much faster. In the actual code, the Page component was a grandchild component, and the separate DeletePopUp, though it shared the same name, was a global component that I also needed to modify to pass the values. The key here is to break problems into small pieces and solve them one by one.

During my research, I encountered another curious topic: rendering and reconciliation. Familiarizing myself with this will be my next step.

Top comments (0)