I’m trying to understand the overheads when handling a lot of subcomponents, extracting data from state using a reducer.
Considering these three different methods, which is the most efficient? My gut tells me option 2. But am I right? And, after it has been compiled does it make all that much difference?
Option 1
// call the reducer once, and pass all data
const MainComponent = () => {
const { data } = useMyReducer()
return (
<SubComponent id="one" data={data} />
<SubComponent id="two" data={data} />
<SubComponent id="three" data={data} />
<SubComponent id="four" data={data} />
// and many more
)
}
const SubComponent = ({id, data}) => {
const myData = data[id]
return (
<div>{myData}</div>
)
}
Option 2
// call the reducer once, and pass required data only
const MainComponent = () => {
const { data } = useMyReducer()
return (
<SubComponent id="one" data={data.one} />
<SubComponent id="two" data={data.two} />
<SubComponent id="three" data={data.three} />
<SubComponent id="four" data={data.four} />
// and many more
)
}
const SubComponent = ({id, data}) => {
return (
<div>{data}</div>
)
}
Option 3
// call the reducer in the subcomponent
const MainComponent = () => {
return (
<SubComponent id="one" />
<SubComponent id="two" />
<SubComponent id="three" />
<SubComponent id="four" />
// and many more
)
}
const SubComponent = ({id}) => {
const { data } = useMyReducer()
const myData = data[id]
return (
<div>{myData}</div>
)
}
edit: to show useMyReducer
code – essentially accessing context, there is react’s useReducer
and createContext
also
import { useContext } from 'react'
import { MyContext } from './context'
const useMyReducer = () => {
const context = useContext(MyContext)
if (context === undefined) {
throw new Error('useMyReducer must be used within MyContext')
}
return context
}
export default useMyReducer
// in context.js
export const MyContext = createContext(initialState)
export const MyProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, {})
... // reducer functions
const value = { state, // plus functions etc }
return <MyContext.Provider value={value}>{children}</MyContext.Provider>
}
2
Answers
yes! you are right, Option 2 is the most correct way to do it because:
1- Only passes required data to each component, avoiding passing unnecessary data.
2- Avoids calling the reducer multiple times like in Option 3. Calling useMyReducer() in each component could impact performance.
3- Maintains separation of concerns – the parent component handles data fetching, child components just render.
After compilation, the performance differences may be reduced, but Option 2 still keeps the code clean and maintinable by only passing relevant data to each component.
Option 1 passes unnecessary data to each child, while Option 3 calls the reducer multiple times.
%%
ADDED
%%comparing Option2 and Option3 :
Option2 calls the reducer once in parent component but Option3 calls the reducer in each child component
in Option2 child components are simple and only render data but in option3 each child component handles data fetching
Comparing #1 and #2, your example is simple enough that it doesn’t really make any difference. But in more complex examples, i’d generally lean towards #2, for the following reasons:
At some point, you may decide you want to improve performance by skipping rendering of SubComponent if none of its props have changed. If you use option 1, then a change to any portion of the data will break the memoization on all SubComponents. If you use option 2, then only 1 subcomponent breaks its memoization
If the component accepts just the minimum amount of data it needs to do its job (ie, option 2), then you may be able to use that component in a wider range of situations. If you find yourself on a page that has data for just a single component, which is not wrapped in a container object, then it can pass that into a SubComponent without having to first wrap it in an object.
Regarding option #3: My original answer assumed it would cause each component to have its own state. But seeing the code, i was wrong. The hook is using
useContext
to access a shared state in the provider.Option #3 will behave similarly to option #1. If any portion of the data changes, then all subcomponents must rerender, even if they use React.memo. And reusability will be reduced since now it can only use data from that one source.