我正在尝试构建一个用户系统,但是由于它不能实时工作,我对此感到困惑。
我创建了一个示例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
以下是我创建
deleteUser
和batchDeleteUsers
突变的方法。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/