Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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}
        ...
    />
)
like image 225
Lenny Meerwood Avatar asked Oct 27 '22 22:10

Lenny Meerwood


People also ask

Does useEffect run when props change?

Just like the state, we can also use props as a dependency to run the side effect. In this case, the side effect will run every time there is a change to the props passed as a dependency. useEffect(() => { // Side Effect }, [props]);

Does useEffect work with props?

Run useEffect When a Prop Changes Just as we were able to set up useEffect to run when a state variable changed, the same can be done with props. Remember they're all regular variables! useEffect can trigger on any of them.

Does useEffect always run after render?

Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update.

How do you run useEffect only when state changes?

Use the useEffect hook to listen for state changes in React. You can add the state variables you want to track to the hook's dependencies array and the logic in your useEffect hook will run every time the state variables change.


1 Answers

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]);
like image 149
gdh Avatar answered Oct 29 '22 16:10

gdh