如何使用从 API 返回的最新数据设置初始状态?

How to set initial state with most up to date data returned from API?

我正在制作一个简单的表单来编辑应用程序,应用程序的名称和描述的初始状态是使用从 API.

返回的数据设置的

目前,当提交表单时,初始数据似乎被记录为未定义,名称和描述被设置为未定义,这发生在第一次渲染中(我在日志所在的代码中进行了评论)

如何确保名称和描述的初始状态具有最新信息? 是渲染过度的问题吗?

感谢您的浏览,如有任何帮助,我们将不胜感激。

import React, { useState, useContext, useEffect } from "react";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
import Container from "@material-ui/core/Container";
import SaveIcon from "@mui/icons-material/Save";
import CloseIcon from "@mui/icons-material/Close";
import { makeStyles } from "@material-ui/styles";
import TextField from "@material-ui/core/TextField";
import { Grid } from "@mui/material";
import { useDispatch } from "react-redux";
import { updateApp, updateSelectedApp } from "../../services/reducers/apps";
import { EndpointContext } from "../../baxios/EndpointProvider";
import { useParams } from "react-router-dom";


export default function EditApp() {
  const { appid } = useParams();

  const classes = useStyles();
  const dispatch = useDispatch();
  const endpoints = useContext(EndpointContext);

  const [selectedApp, setSelectedApp] = useState({});
  const [isLoaded, setIsLoaded] = useState(false); // <--- Is there anyway I can also remove this useState? without this the default values in the forms dont populate

  useEffect(() => {
    async function fetchApp() {
      await endpoints.appEndpoints.get(appid).then((response) => {
        if (response.status === 200) {
          setSelectedApp(response.data);
          setIsLoaded(true);
        }
      });
    }
    fetchApp();
  }, []);

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

  const [name, setName] = useState(selectedApp.name);
  const [description, setDescription] = useState(selectedApp.description);

  console.log("---", name, selectedApp.name); // <--- The page renders 3 times, each render log looks like this 
// 1st render - --- undefined, undefined
// 2nd render - --- undefined, Appname
// 3rd render - --- undefined, Appname


  const handleSubmit = (e) => {
    e.preventDefault();

    console.log("triggered", name, description); // <--- This logs (triggered, undefined, undefined)

    if (name && description) {
      const body = { name: name, description: description };
      endpoints.appEndpoints.put(selectedApp.id, body).then((response) => {
        if (response.status === 200) {
          dispatch(updateApp(response.data));
          setSelectedApp(response.data);
          setName(selectedApp.name);
          setDescription(selectedApp.description);
        }
      });
    }
  };

  return (
    <div style={{ margin: 100, marginLeft: 350 }}>
      {isLoaded ? (
        <Container size="sm" style={{ marginTop: 40 }}>
          <Typography
            variant="h6"
            color="textSecondary"
            component="h2"
            gutterBottom
          >
            Edit App
          </Typography>

          <form noValidate autoComplete="off" onSubmit={handleSubmit}>
            <TextField
              className={classes.field}
              onChange={(e) => setName(e.target.value)}
              label="App Name"
              variant="outlined"
              color="secondary"
              fullWidth
              required
              size="small"
              defaultValue={selectedApp.name}
              error={nameError}
            />
            <TextField
              className={classes.field}
              onChange={(e) => setDescription(e.target.value)}
              label="Description"
              variant="outlined"
              color="secondary"
              rows={4}
              fullWidth
              required
              size="small"
              defaultValue={selectedApp.description}
              error={descriptionError}
            />
            <Grid container spacing={2}>
              <Grid item>
                <Button
                  // onClick={handleSubmit}
                  type="submit"
                  color="primary"
                  variant="contained"
                  endIcon={<SaveIcon />}
                >
                  Save
                </Button>
              </Grid>
            </Grid>
          </form>
        </Container>
      ) : (
        <></>
      )}
    </div>
  );
}

使用 const [name, setName] = useState(defaultName) 时,如果 defaultName 在未来的渲染中更新,则 name 值将 不会 更新为这个最新值。

因此,对于您的情况,您可以进行以下更改:

  const [name, setName] = useState();
  const [description, setDescription] = useState();
  useEffect(() => {
    setName(selectedApp.name)
    setDescription(selectedApp.description)
  }, [selectedApp])
)

  1. 名称和描述未定义

您的 selectedApp 被初始化为一个空对象。您的 useEffect 触发请求以检索该数据,但页面在获得响应之前呈现一次。有几种方法可以处理这个问题。您可以做任何事情,从在字段上显示加载图标,到为字段设置默认值,直到调用 [selectedApp] 的 useEffect。检索并发回该信息后,您的信息将处于最新状态,但如果您需要存储它以备后用,则需要构建一个函数来保存该数据。

默认值:

const [name, setName] = useState(selectedApp.name ?? "Your default value here");
const [description, setDescription] = useState(selectedApp.description ?? "Your default value here");

加载图标:

{selectedApp ? (
          <form noValidate autoComplete="off" onSubmit={handleSubmit}>
            <TextField
              className={classes.field}
              onChange={(e) => setName(e.target.value)}
              label="App Name"
              variant="outlined"
              color="secondary"
              fullWidth
              required
              size="small"
              defaultValue={selectedApp.name}
              error={nameError}
            />
            <TextField
              className={classes.field}
              onChange={(e) => setDescription(e.target.value)}
              label="Description"
              variant="outlined"
              color="secondary"
              rows={4}
              fullWidth
              required
              size="small"
              defaultValue={selectedApp.description}
              error={descriptionError}
            />
            <Grid container spacing={2}>
              <Grid item>
                <Button
                  // onClick={handleSubmit}
                  type="submit"
                  color="primary"
                  variant="contained"
                  endIcon={<SaveIcon />}
                >
                  Save
                </Button>
              </Grid>
            </Grid>
          </form>
) : <LoadingIconComponent/>}