0

I have a parent component that should be updated when the form on the child component is submitted. When the form is submitted in the child component, the fetchData function is being called, however, the data is not updated in the parent component here, and requires a page refresh to update the data:

{JSON.stringify(data)}

I cannot add the data from the setState to useEffect because it would create an infinite loop.

How do I update the data on the parent component when submitting the form from the child component?

parent component

export function ParentComponent() {
    const [data, setData] = useState(null);

    const fetchData = async () => {

        const result = await axios(
          '/api/info/'
          );
          setData(result.data)
    }

    useEffect(() => {
        fetchData();
      },[]);


      return (
        <div>
            <h1>
                Parent Page
            </h1>
            
            {JSON.stringify(data)}

            <ChildComponent dataUpdated={fetchData} />
        </div>
    )
}

child component

export function ChildComponent(props) {
    const [formData, setFormData] = useState('');

    const handleSubmit = evt => {
            evt.preventDefault();
            axios.post(`/api/info/create`,
                        {formData}
                  )
                  .then(response => { return response.data })
                  .then(props.dataUpdated()) // calling this function does not update the data on the parent component
        }
        
    return (
        <div>
            <h1>
                Child Component
            </h1>

            <form className={classes.form} onSubmit={handleSubmit}>
                    <TextField value={formData.number}
                        id="filled-number"
                        label="number"
                        type="number"
                        onChange={evt => setFormData({...formData, number: evt.target.value})}
                    />

                    <Button
                        type="submit"
                    >
                        Submit
                    </Button>
            </form>
        </div>
    )
}
ivan7707
  • 1,146
  • 1
  • 13
  • 24
  • 1
    The last `then` in the child should be `props.dataUpdated` without `()` afterwards – fast-reflexes Apr 11 '21 at 05:36
  • @fast-reflexes, that worked thanks, but why does it work? How is the function expression being called without the parenthesis? – ivan7707 Apr 11 '21 at 13:15
  • I believe I found the reason. As functions are eagerly evaluated, the function how I had it written would have been called before the then is called. https://stackoverflow.com/a/52769736/3856624 – ivan7707 Apr 11 '21 at 13:49
  • 2
    Answer is that `.then` takes a function as argument, when you do `props.dataUpdated()` you RUN the function and give its return value for the `then` to run whenever it should. The return value of `fetchData` is `undefined`, so you're passing `undefined` as argument to the `then`, which of course does not work. When you remove the parenthesis, you supply the `then` with a function with 0 parameters that works well to run when it should. – fast-reflexes Apr 11 '21 at 13:57
  • @fast-reflexes, if you make this an answer, I'll accept it. Thank you for the info. – ivan7707 Apr 12 '21 at 00:56

1 Answers1

0

You have to remove the parenthesis from this line:

.then(props.dataUpdated())

so that it is instead:

.then(props.dataUpdated)

Why?

Well this is a common mistake with Promises (which I supposed is returned from axios.post()), namely that we tend to forget that the argument to the then, catch and finally functions of the Promise should be functions which are executed asynchronously as the Promise returns.

Otherwise, a Promise is like a regular class / object and in this case you're synchronously running its then function two times with different arguments. But like always, when we supply an argument to a function that is, itself, a function call, this call executes first and its return value is given as the actual argument.

So when you do:

.then(props.dataUpdated())

... you're synchronously (that is, before the Promise has returned) running the props.dataUpdated function and handing its returns value as the argument to then. Since props.dataUpdated = fetchData returns undefined (doesn't have a return value), this is the same as:

.then(undefined)

... which will not work.

Remember, the argument function is supposed to be executed asynchronously at a later stage (when the Promise resolves) but now instead you run it instantly and then leave the then function with an undefined to execute when the Promise resolves. Since the changed state has not yet occurred, running the update function at this premature time doesn't result in anything and since running undefined as a function later doesn't result in anything either, you're left with a UI that is not updated as expected (you might have an error in the console as well from when the then tries to run the undefined as a function).

To sum it up...

... you need to supply the then with an executable function containing the logic you want to run when the Promise returns asynchronously (like you have done in the first then already). The easiest way to do this here would be:

.then(props.dataUpdated) // forwarding the function given in props

... but you could also do:

.then(res => props.dataUpdated()) // arrow function calling the function given in props

or

.then(res => { props.dataUpdated() }) // arrow function with body calling the function given in props

... which would be handier if you wanted to do something with the input from the previous step, namely the value that you return from the previous then (response.data).

References

fast-reflexes
  • 4,891
  • 4
  • 31
  • 44