Search code examples
javascriptreactjsreact-hooksuse-effectuse-state

React child component state is lost after parent component re-renders


I am using a React hook for parent/child component.

Now I have state in my parent component (companyIcon), which I need to update based on some validation in the child component. I pass validationCallback as a callback function to the child component and update my parent state based on the value I get from the child.

Now the issue is after I update the parent state, the state value in my child component gets reset. What I am doing wrong in the below implementation ?

function ParentComp(props) {
    const [companyIcon, setCompanyIcon] = useState({ name: "icon", value: '' });

    const validationCallback = useCallback((tabId, hasError) => {
        if (hasError) {
            setCompanyIcon(prevItem => ({ ...prevItem, value: 'error'}));
// AFTER ABOVE LINE IS EXECUTED, my Child component state "myAddress" is lost i.e. it seems to reset back to empty value.
        }
    }, []);
    

    const MyChildCmp = (props) => { 
        const [myAddress, setmyAddress] = useState('');

        useEffect(() => {
                if (myAddressExceptions.length > 0) {
                    props.validationCallback('MyInfo', true);
                } else {
                    props.validationCallback('MyInfo', false);
                }
            }, [myAddressExceptions])

    
        const handlemyAddressChange = (event) => {        
            //setmyAddress(event.target.value);
            //setmyAddressExceptions(event.target.value);
            console.log(myAddressExceptions);
        }

        return (
            <>
                <div className="row" style={{ display: 'flex', flexDirection: 'row', width: '1000px'}}>
                        <div style={{ width: '20%'}}>
                            <FormField 
                                label='Company Address' 
                                required
                                helperText={mergedErrorMessages(myAddressExceptions)}
                                validationState={
                                    myAddressExceptions[0] ? myAddressExceptions[0].type : ''
                                }
                            >
                                <Input id='myAddress'
                                    value={myAddress}
                                    //onChange={handlemyAddressChange}
                                    onChange={({ target: { value } }) => {
                                        validateInputValue(value);
                                    }}
                                    onBlur={handleBlur}
                                    inputProps={{maxLength: 9}} />
                            </FormField>
                        </div>
                </div>
            </>
        ); 
    }

    return (
        <div className="mainBlock">
            Parent : {companyIcon}
            {displayMyChild && <MyChildCmp validationCallback={validationCallback}/>}
        </div>
    )
}

export default withRouter(ParentComp);

Solution

  • Here are some reasons why you can lose state in child (there could be more, but these apply to you most):

        {displayMyChild && <MyChildCmp validationCallback={validationCallback}/>}
    

    Here if at one point displayMyChild is truthy, then made falsy, this means the component MyChildCmp will get unmounted, hence all its state will be gone.

    But now, even if you didn't have that condition and rendered the MyChildCmp always you would still run into similar problem, this is because you defined MyChildCmp inside another component. When you do that, on each render of the parent component, the function MyChildCmp is recreated, and the reconciliation algorithm of react thinks you rendered a different component type on next render, so it will destroy the component instance. Move definition of that component outside the parent component.