Home > Back-end >  Why taking component out of return makes additional React rerenders?
Why taking component out of return makes additional React rerenders?

Time:09-30

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}
    />
)
  • Related