我正在尝试构建一个用户系统,但是由于它不能实时工作,我对此感到困惑。

我创建了一个示例Sandbox来显示我的“问题”和代码。我没有添加任何类型的验证内容,仅用于示例目的。

我看到的一些问题是:


按钮动作在第二次点击时触发
创建/删除后,数据不会刷新。


这是<UsersPage />组件:

import React, { Fragment, useState, useEffect } from "react";
import { useMutation, useLazyQuery } from "@apollo/react-hooks";
import { ADD_USER, LIST_USERS, DELETE_USER } from "../../../config/constants";
import { useSnackbar } from "notistack";
import {
  Grid,
  Paper,
  TextField,
  Button,
  Typography,
  MenuItem,
  FormHelperText
} from "@material-ui/core";
import AddUserIcon from "@material-ui/icons/PersonAdd";
import { withStyles } from "@material-ui/core/styles";
import PropTypes from "prop-types";
import Table from "../../Table";

const styles = theme => ({
  grid: {
    margin: theme.spacing(3)
  },
  icon: {
    marginRight: theme.spacing(2)
  },
  form: {
    width: "100%",
    marginTop: theme.spacing(3),
    overflowX: "auto",
    padding: theme.spacing(2)
  },
  submit: {
    margin: theme.spacing(2)
  },
  container: {
    display: "flex",
    flexWrap: "wrap"
  },
  textField: {
    marginLeft: theme.spacing.unit,
    marginRight: theme.spacing.unit
  },
  root: {
    width: "100%",
    marginTop: theme.spacing(3),
    overflowX: "auto",
    padding: theme.spacing(2)
  },
  title: {
    margin: theme.spacing(2)
  },
  table: {
    minWidth: 700
  },
  noRecords: {
    textAlign: "center"
  },
  button: {
    margin: theme.spacing.unit
  }
});

const Users = props => {
  const [idState, setIdState] = useState(null);
  const [emailState, setEmailState] = useState("");
  const [passwordState, setPasswordState] = useState("");
  const [usersState, setUsersState] = useState([]);
  const [errorsState, setErrorsState] = useState({});
  const [loadingState, setLoadingState] = useState(false);
  const [addUser, addUserResponse] = useMutation(ADD_USER);
  const [loadUsers, usersResponse] = useLazyQuery(LIST_USERS);
  const [deleteUser, deleteUserResponse] = useMutation(DELETE_USER);
  const { enqueueSnackbar } = useSnackbar();

  useEffect(() => {
    loadUsers();

    if (usersResponse.called && usersResponse.loading) {
      setLoadingState(true);
    } else if (usersResponse.called && !usersResponse.loading) {
      setLoadingState(false);
    }

    if (usersResponse.data) {
      setUsersState(usersResponse.data.getUsers);
    }
  }, [usersResponse.called, usersResponse.loading, usersResponse.data]);

  function handleSubmit(e) {
    e.preventDefault();

    if (idState) {
    } else {
      addUser({
        variables: {
          email: emailState,
          password: passwordState
        }
      });
    }

    if (addUserResponse.called && addUserResponse.loading) {
      enqueueSnackbar("Creating user");
    }

    if (addUserResponse.error) {
      addUserResponse.error.graphQLErrors.map(exception => {
        const error = exception.extensions.exception;
        const messages = Object.values(error);
        enqueueSnackbar(messages[0], { variant: "error" });
      });
    }

    if (addUserResponse.data && addUserResponse.data.addUser) {
      enqueueSnackbar("user created", { variant: "success" });
      loadUsers();
    }
  }

  function handleEdit(user) {
    setIdState(user.id);
    setEmailState(user.email);
  }

  async function handleDelete(data) {
    if (typeof data === "object") {
      data.map(id => {
        deleteUser({ variables: { id } });
        if (deleteUserResponse.data && deleteUserResponse.data.deleteUser) {
          enqueueSnackbar("User deleted", { variant: "success" });
        }
      });
    } else {
      deleteUser({ variables: { id: data } });
      if (deleteUserResponse.data && deleteUserResponse.data.deleteUser) {
        enqueueSnackbar("User deleted", { variant: "success" });
      }
    }
  }

  function resetForm() {
    setIdState(null);
    setEmailState("");
  }
  const { classes } = props;

  return (
    <Fragment>
      <Grid container spacing={8}>
        <Grid item xs={3} className={classes.grid}>
          <Paper className={classes.form}>
            <Typography variant="h6" className={classes.title}>
              {idState ? `Edit user: ${emailState}` : "Create user"}
            </Typography>
            <form className={classes.container} onSubmit={handleSubmit}>
              <input type="hidden" name="id" value={idState} />
              <TextField
                className={classes.textField}
                label="E-mail address"
                type="email"
                variant="outlined"
                margin="normal"
                autoComplete="email"
                id="email"
                name="email"
                required={!idState}
                fullWidth
                onChange={e => setEmailState(e.target.value)}
                value={emailState}
                aria-describedby="email-error"
              />
              <FormHelperText id="email-error">
                {errorsState.email}
              </FormHelperText>
              <TextField
                className={classes.textField}
                label="Password"
                variant="outlined"
                margin="normal"
                autoComplete="password"
                id="password"
                name="password"
                required={!idState}
                type="password"
                fullWidth
                onChange={e => setPasswordState(e.target.value)}
                value={passwordState}
                aria-describedby="password-error"
              />
              <FormHelperText id="password-error">
                {errorsState.password}
              </FormHelperText>

              <Button
                variant="contained"
                color="primary"
                className={classes.submit}
                size="large"
                type="submit"
              >
                <AddUserIcon className={classes.icon} /> Save
              </Button>
              <Button
                variant="contained"
                color="secondary"
                className={classes.submit}
                type="button"
                onClick={resetForm}
              >
                <AddUserIcon className={classes.icon} /> Add new
              </Button>
            </form>
          </Paper>
        </Grid>
        <Grid item xs={8} className={classes.grid}>
          <Paper className={classes.root}>
            <Table
              data={usersState}
              className={classes.table}
              columns={{
                id: "ID",
                email: "E-mail address"
              }}
              classes={classes}
              title="Users"
              handleEdit={handleEdit}
              handleDelete={handleDelete}
              filter={true}
              loading={loadingState}
            />
          </Paper>
        </Grid>
      </Grid>
    </Fragment>
  );
};

Users.propTypes = {
  classes: PropTypes.object.isRequired
};

export default withStyles(styles)(Users);


如果您需要更多代码或进行编辑:

前端沙箱:App / Code

后端沙箱:App / Code

任何意见,建议或任何形式,将不胜感激。

最佳答案

按钮动作在第二次点击时触发
  


因为你打电话

if (addUserResponse.called && addUserResponse.loading) {
  enqueueSnackbar("Creating user");
}


在您致电addUser之后。选中if (addUserResponse.called && addUserResponse.loading)时状态未更改,该状态与调用addUser之前的状态相同。

当您第二次单击时,您具有第一次单击后的状态,如果

if (addUserResponse.data && addUserResponse.data.addUser) {
  enqueueSnackbar("user created", { variant: "success" });
  loadUsers();
}


是真的。

解:

创建一个useEffect来处理addUser状态,并从handleSubmit中删除​​if子句

 useEffect(() => {
    if (!addUserResponse.called) {
      return;
    }

    if (addUserResponse.loading) {
      enqueueSnackbar("Creating user");
      return;
    }

    if (addUserResponse.error) {
      addUserResponse.error.graphQLErrors.map(exception => {
        const error = exception.extensions.exception;
        const messages = Object.values(error);
        enqueueSnackbar(messages[0], { variant: "error" });
      });
      return;
    }

    enqueueSnackbar("user created", { variant: "success" });
  }, [addUserResponse.called, addUserResponse.loading]);

 function handleSubmit(e) {
    e.preventDefault();

    if (idState) {
    } else {
      addUser({
        variables: {
          email: emailState,
          password: passwordState
        }
      });
    }
  }



  
  创建/删除后,数据不会刷新。
  


您应update your cache after mutation,因为在调用突变时,Apollo不知道您要添加还是删除。

解:

 const [addUser, addUserResponse] = useMutation(ADD_USER, {
    update: (cache, { data: { addUser } }) => {
      // get current data cache
      const cachedUsers = cache.readQuery({ query: LIST_USERS });

      // create new users
      const newUsers = [addUser, ...cachedUsers.getUsers];

      // save newUsers on cache
      cache.writeQuery({
        query: LIST_USERS,
        data: {
          getUsers: newUsers
        }
      });
    }
  });


删除用户也是如此,期望newUsers将过滤当前用户:

const [deleteUser, deleteUserResponse] = useMutation(DELETE_USER, {
    update: (cache, { data: { deleteUser } }) => {
      const cachedUsers = cache.readQuery({ query: LIST_USERS });

      // NOTE: this didn't work because deleteUser return true instead user.
      // I'd suggest change your backend and deleteUser return user id to
      // be able to perform this filter.
      const newUsers = cachedUsers.getUsers.filter(
        ({ id }) => id !== deleteUser.id
      );

      cache.writeQuery({
        query: LIST_USERS,
        data: {
          getUsers: newUsers
        }
      });
    }
  });




注1:

您无需多次呼叫loadUsers。由于执行突变时会更新缓存,因此数据将始终是最新的。因此,我会这样称呼loadUsers

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

  useEffect(() => {
    if (usersResponse.called && usersResponse.loading) {
      setLoadingState(true);
    } else if (usersResponse.called && !usersResponse.loading) {
      setLoadingState(false);
    }
  }, [usersResponse.called, usersResponse.loading]);


笔记2

您无需为用户创建状态,您已经有一个来自usersResponse.data.getUsers的状态,但这是您的偏好。就我而言,我删除了const [usersState, setUsersState] = useState([]);并添加了

const users =
    usersResponse.data && usersResponse.data.getUsers
      ? usersResponse.data.getUsers
      : [];


传递到桌子。



编辑2019年10月10日

我所做的主要更改是创建一个名为batchDeleteUsers的变异,该变异在一个调用中删除了多个用户。

更新服务器

我对服务器进行了一些更改,以使应用程序正常运行。首先,deleteUser返回User,我创建了一个名为batchDeleteUsers的突变。

我当前的变异模式:

type Mutation {
  addUser(email: String!, password: String!): User
  deleteUser(id: String!): User
  batchDeleteUsers(ids: [String!]!): [User]
}


我目前的解析器:

deleteUser: (root, { id }, context) => {
  const user = USERSDB.find(user => user.id === id);
  USERSDB = USERSDB.filter(user => user.id !== id);
  return user;
},
  batchDeleteUsers: (root, { ids }, context) => {
  const users = USERSDB.filter(user => ids.includes(user.id));
  USERSDB = USERSDB.filter(user => !ids.includes(user.id));
  return users;
}


更新应用程序1

我使用的是useLazyQuery,而不是使用useEffect并在useQuery中调用它。这样,我们就不需要在useEffect内部执行查询,它是在组件初始化时触发的。

const usersResponse = useQuery(LIST_USERS);


更新应用程序2

以下是我创建deleteUserbatchDeleteUsers突变的方法。

const [deleteUser, deleteUserResponse] = useMutation(DELETE_USER, {
  update: (cache, { data: { deleteUser } }) => {
    const cachedUsers = cache.readQuery({ query: LIST_USERS });

    const newUsers = cachedUsers.getUsers.filter(
      ({ id }) => id !== deleteUser.id
    );

    cache.writeQuery({
      query: LIST_USERS,
      data: {
        getUsers: newUsers
      }
    });
  }
});
const [batchDeleteUsers, batchDeleteUsersResponse] = useMutation(
  BATCH_DELETE_USERS,
  {
    update: (cache, { data: { batchDeleteUsers } }) => {
      const cachedUsers = cache.readQuery({ query: LIST_USERS });

      const newUsers = cachedUsers.getUsers.filter(({ id }) => {
        return !batchDeleteUsers.map(({ id }) => id).includes(id);
      });

      cache.writeQuery({
        query: LIST_USERS,
        data: {
          getUsers: newUsers
        }
      });
    }
  }
);


更新应用程序3

这就是我处理删除用户的突变生命周期的方式。

useEffect(() => {
  if (!deleteUserResponse.called) {
    return;
  }

  if (deleteUserResponse.loading) {
    enqueueSnackbar("Deleting user");
    return;
  }

  if (deleteUserResponse.error) {
    deleteUserResponse.error.graphQLErrors.map(exception => {
      const error = exception.extensions.exception;
      const messages = Object.values(error);
      enqueueSnackbar(messages[0], { variant: "error" });
    });
    return;
  }

  enqueueSnackbar("user deleted", { variant: "success" });
}, [deleteUserResponse.called, deleteUserResponse.loading]);

useEffect(() => {
  if (!batchDeleteUsersResponse.called) {
    return;
  }

  if (batchDeleteUsersResponse.loading) {
    enqueueSnackbar("Deleting users");
    return;
  }

  if (batchDeleteUsersResponse.error) {
    batchDeleteUsersResponse.error.graphQLErrors.map(exception => {
      const error = exception.extensions.exception;
      const messages = Object.values(error);
      enqueueSnackbar(messages[0], { variant: "error" });
    });
    return;
  }

  enqueueSnackbar("users deleted", { variant: "success" });
}, [batchDeleteUsersResponse.called, batchDeleteUsersResponse.loading]);


更新应用程序4

最后,这就是我处理删除用户的方式。

function handleDelete(data) {
  if (typeof data === "object") {
    batchDeleteUsers({ variables: { ids: data } });
  } else {
    deleteUser({ variables: { id: data } });
  }
}




我的服务器沙箱:code

我的前端沙箱:code

关于javascript - React组件和Apollo Client无法“实时”运行,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58087426/

10-16 19:22