My initial code was:
<Container>
<Carbs defaultCollapsed/>
<Citrus defaultCollapsed/>
<Gluten defaultCollapsed/>
</Container>
let's say Carbs
, Gluten
and Citrus
have similar structure of:
<Carbs>
<Collapsible defaultCollapsed={defaultCollapsed}>
<List/>
</Collapsible>
</Carbs>
it worked perfectly fine. But I needed to take out components from Container
and generate them based on passed config. My code now looks like:
const CarbsOption = ({defaultCollapsed}) => <Carbs defaultCollapsed={defaultCollapsed}/>
const GlutenOption = ({defaultCollapsed}) => <Gluten defaultCollapsed={defaultCollapsed}/>
const CitrusOption = ({defaultCollapsed}) => <Citrus defaultCollapsed={defaultCollapsed}/>
const options = ['gluten', 'carbs', 'citrus']
const componentList = {
gluten: {
collapsed: true,
component: GlutenOption,
},
citrus: {
collapsed: true,
component: CitrusOption,
},
carbs: {
collapsed: true,
component: CarbsOption,
},
}
return(
<Container>
{
options.map((option,i) => {
const Component = componentList[option].component;
return <Component key={i} defaultCollapsed={componentList[option].collapsed}/>
})
}
</Container>
)
Main idea, is that components will be rendered based if they are in options
array. Components are rendering fine, but once I uncollapse component and change one of the list selection in lets say Carbs
, it triggers its parents to get rerendered and it causes component to collapse again. Why it's happening once I took code out of main return
? And how to prevent extra rerenders so that collapsed state won't get updated on List
change?
CodePudding user response:
The reason why your child components are being re-rendered is because they are being defined in the body if your functional component, meaning that they will be re-instantiated every single time your functional component re-renders. A quick fix will be to memoize them using React.memo
, which will prevent unnecessary re-rendering:
const CarbsOption = React.memo(({defaultCollapsed}) => <Carbs defaultCollapsed={defaultCollapsed}/>);
const GlutenOption = React.memo(({defaultCollapsed}) => <Gluten defaultCollapsed={defaultCollapsed}/>);
const CitrusOption = React.memo(({defaultCollapsed}) => <Citrus defaultCollapsed={defaultCollapsed}/>);
However this feels a bit too verbose of my liking: since you are only swapping out the name of the component, why not just assigned it to a memoized variable?
const componentList = {
gluten: {
collapsed: true,
component: Gluten,
},
citrus: {
collapsed: true,
component: Citrus,
},
carbs: {
collapsed: true,
component: Carbs,
},
}
This will map your functional components Carbs
, Gluten
and Citrus
directly to your template without needing an unnecessary layer of memoization.
CodePudding user response:
edit: This answer was based off an earlier version of the question and is not correct, but leaving it up for posterity.
You should set a key
for each component you render as part of a map
. See here for details.
options.map((option, index) => {
const Component = componentList[option].component;
return <Component key={index} defaultCollapsed={componentList[option].collapsed}/>
})
(Note, the link I provided states We don’t recommend using indexes for keys if the order of items may change
, but that's only important when the data you're mapping from changes, in which case you could have a stable key be part of the data. For something like this where the data is static, index is fine)
edit: To improve/simplify it a bit, I'd actually recommend getting rid of your options
variable and doing this:
Object.entries(componentList).map(([componentName, options]) => (
<options.component
key={componentName}
defaultCollapsed={options.collapsed}
/>
)