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)