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:
- useContext and Provider
- Rendering Rule
- 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>
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>
);
ChildA.tsx
const ChildA = () => {
const value = useContext(ParentContext);
return (
<>
<p>Child A</p>
<div>{value}</div>
<hr />
<GrandChild />
<hr />
</>
);
}
ChildB.tsx
const ChildB = () => {
const value = useContext(ParentContext);
return (
<>
<p>Child B</p>
<div>{value}</div>
<hr />
</>
);
}
ChildC.tsx
const ChildC = () => {
const value = useContext(ParentContext);
return (
<>
<p>Child C</p>
<div>{value}</div>
<hr />
</>
);
}
GrandChild.tsx
const GrandChild = () => {
const value = useContext(ParentContext);
return (
<>
<p>GrandChild</p>
<div>{value}</div>
<hr />
</>
);
}
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>
}
<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>
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)