Why does the POST stop working when setting state in useEffect?

Viewed 54

Why does the POST stop working when trying to set State using useEffect and fetch?

In this first example, I can see in the POST log in the Chrome console and print in Flask:

React:

import React, { useEffect, useState } from "react";
import Button from '@mui/material/Button';
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
import TextField from '@mui/material/TextField';

export default function Heater(props) {

  const [status, setStatus] = useState({
    "id": props.id,
    "name": props.name,
    "pin": props.pin,
    "setpoint": 1
  });

  function incrementSetpoint() {
    setStatus(previousValue => {
      return {
        ...previousValue,
        "setpoint": status.setpoint + 1
      };
    });
  };
 
  useEffect(() => {
    fetch('/heaters', {
      method: 'POST',
      headers: { 'Content-type': 'application/json' },
      body: JSON.stringify(status)
    })
      .then((response) => response.json())
      .then((response) => console.log(response))
      .catch((error) => (console.error(error)))
  });
     
  return (
    <div>
      <h1>{ props.name }</h1>
      <div className="inputs-container" style={{ marginBottom: '2ch' }}>
        <TextField label="Setpoint" value={ status.setpoint } />
        <Button variant="contained" onClick={ incrementSetpoint }>
          <ArrowUpwardIcon></ArrowUpwardIcon>
        </Button>
      </div>
    </div>
  );
}

Flask:

from flask import jsonify

@app.route('/heaters', methods=['GET', 'POST'])
def heaters():
    """Control heaters and return the current heater configuration."""
    if request.method == 'POST':
        print(request.json)
    return jsonify(request.json)

Now, if I comment out the console.log and use setState instead, it causes and infinite loop in Flask.

  ...

  useEffect(() => {
    fetch('/heaters', {
      method: 'POST',
      headers: { 'Content-type': 'application/json' },
      body: JSON.stringify(status)
    })
      .then((response) => response.json())
      // .then((response) => console.log(response))
      .then((response) => setStatus(response)) // New line here
      .catch((error) => (console.error(error)))
  });

  ...

After reading some other posts, I decided to add an empty array to useEffect. This stops the infinite loop. I can see that setStatus is working by adding console.log(status). However, the POST is no longer working.

  ...
 
  useEffect(() => {
    fetch('/heaters', {
      method: 'POST',
      headers: { 'Content-type': 'application/json' },
      body: JSON.stringify(status)
    })
      .then((response) => response.json())
      // .then((response) => console.log(response))
      .then((response) => setStatus(response)) // New line here
      .catch((error) => (console.error(error)))
  }, []); // Empty array added here

  console.log(status); // New line here to confirm setStatus works
  
  ...

On the initial startup, I can see the POST log in the Chrome console and print in Flask. After it has loaded, the POST does not work. Any ideas on what is causing the issue?

1 Answers

When you use useEffect() bound to an empty array, it only executes once on the first run.

useEffect(() => {
  // fetch code here
}, []);

I'm guessing that you want to run it again on every status update?

If that's the case, bind useEffect() to your status state.

useEffect(() => {
  // fetch code here
}, [status]);

Thing is, if you setStatus in that same useEffect() hook, you end up in the infinite loop - because it runs again after the status is updated.. and then again.. and again.

So you want to create a different state heaters [heaters, setHeaters] then setHeaters after you fetch, rather than setStatus again.

const [status, setStatus] = useState({
    "id": props.id,
    "name": props.name,
    "pin": props.pin,
    "setpoint": 1
  });
const [heaters, setHeaters] = useState({});

useEffect(() => {
    fetch('/heaters', {
      method: 'POST',
      headers: { 'Content-type': 'application/json' },
      body: JSON.stringify(status)
    })
      .then((response) => response.json())
      .then((response) => setHeaters(response))
      .catch((error) => (console.error(error)))
  }, [status]);

That way, whenever you incrementSetpoint() the status will update, the fetch will run, and heaters is set.

Finally, you see your fetched data by using another useEffect() bound to the heaters state.

useEffect(() => {
  console.log(heaters);
}, [heaters]);

This should be the convention.

Related