Search code examples
reactjsreact-propsuse-effectreact-jsonschema-forms

Prop change not triggering useEffect


I have a react component with a useEffect that is not being fired when the props change. I would like this component to update what it is displaying based on the prop. However, when the prop changes nothing happens.

For debugging I added a button that would manually update the value, that works fine. I just cannot get the useEffect to run for the life of me.

const ReadOnly = (props: WidgetProps) => {
    const [value, setValue] = React.useState('No Value');
    const { formContext } = props;

    const firstName = () => formContext.queryData[0]?.basicInformation?.firstName ?? 'No value';

    // This only runs once when the component is first rendered never fired when formContext updates.
    React.useEffect(() => {
        setValue(firstName());
    }, [formContext]);

    // This correctly updates the value
    const onClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
        e.preventDefault();
        setValue(firstName());
    };

    return (
        <>
            <p>{value}</p>
            <Button onClick={onClick}>Update Value</Button>
        </>
    );
};

export default ReadOnly;

I have tried changing the value the useEffect functions watches from formContext to formContext.queryData[0].basicInformation.firstName to no effect. I've also tried using props instead. I even tried removing the watch array altogether in the hopes it would just spam update, but nothing.

Any advice on how I can achieve this? I know the value is getting through to the component because it works when I press the button. And when I check it out in the React inspector on the webpage I can clearly see that the value is there. Cheers.

Edit: This component is being used as a widget for react-jsonschema-form. Basically what happens is everytime the formData is updated I pass it back into the formContext. formContext is the only way I can get other data to that widget. Below is a stripped down version of what I am doing.

const [formDataAndIncludings, setFormDataAndIncludings] = React.useState<any>(null);

ClientRect.useEffect(() => {
    ...
    // Initial loading of formDataAndIncludings
    ...
}, [])

const onChange = (e: IChangeEvent<any>) => {
    const newFormDataAndIncludings = { ...formDataAndIncludings };
    newFormDataAndIncludings.queryData[0] = e.formData;
    setFormDataAndIncludings(newFormDataAndIncludings);
};

return (
    <Form
        ...
        onChange={onChange}
        formContext={formDataAndIncludings}
        ...
    />
)

Solution

  • The state is being mutated directly in the parent's onChange and hence your useEffect in ReadOnly component is not being triggered.

    Update your onChange

    const onChange = (e: IChangeEvent<any>) => {
        setFormDataAndIncludings(prev => ({
                ...prev ,
                queryData : [
                    e.formData,
                    ...prev.queryData.slice(0,1)
                ]
            }))

        //const newFormDataAndIncludings = { ...formDataAndIncludings };
        //newFormDataAndIncludings.queryData[0] = e.formData;
        //setFormDataAndIncludings(newFormDataAndIncludings);
    };
    

    Provide useEffect dependency as usual

    React.useEffect(() => {
            setValue(firstName());
        }, [formContext]);