Search code examples
javascriptreactjstypescriptkey-value

Re-rendering on key-value pair object components


I want to avoid re-render of my child component <ChildComponent/> whenever I update my state using a onClick in <ChildComponent/>. I have my callback function in <ParentComponent/> which updates one of the values for the key-value pair object.

In the parent component

const _keyValueObject = useMemo(() => utilityFunction(array, object), [array, object])

const [keyValueObject, setKeyValueObject] = useState<SomeTransport>(_keyValueObject)

const handleStateChange = useCallback((id: number) => {
        setKeyValueObject(keyValueObject => {
            const temp = { ... keyValueObject }
            keyValueObject[id].isChecked = ! keyValueObject[id].isChecked
            return temp
        })
  }, [])

return( 
    <Container>
          {!! keyValueObject &&
               Object.values(keyValueObject).map(value => (
                   <ValueItem
                      key={value.id}
                      category={value}
                      handleStateChange ={handleStateChange}
                    />
               ))}
      </Container>
)

In child component ValueItem

   const clickHandler = useCallback(
        event => {
            event.preventDefault()
            event.stopPropagation()
            handleStateChange(value.id)
        },
        [handleStateChange, value.id],
    )

return (
        <Container>
            <CheckBox checked={value.isChecked} onClick={clickHandler}>
                {value.isChecked && <Icon as={CheckboxCheckedIcon as AnyStyledComponent} />}
            </CheckBox>
            <CategoryItem key={value.id}>{value.title}</CategoryItem>
        </Container>
    )
    
export default ValueItem

In child component if I use export default memo(ValueItem), then the checkbox does not get updated on the click.

What I need now is to not re-render every child component, but keeping in mind that the checkbox works. Any suggestions?


Solution

  • Spreading (const temp = { ... keyValueObject }) doesn't deep clone the object as you might think. So while keyValueObject will have a new reference, it's object values will not be cloned, so will have the same reference, so memo will think nothing changes when comparing the category prop.

    Solution: make sure you create a new value for the keyValueObject's id which you want to update. Example: setKeyValueObject(keyValueObject => ({...keyValueObject, [id]: {...keyValueObject[id], isChecked: !keyValueObject[id].isChecked})). Now keyValueObject[id] is a new object/reference, so memo will see that and render your component. It will not render the other children since their references stay the same.