我正在学习react,现在我有一个react应用程序,里面有用户和电影,你可以在登录的时候看到电影的信息。如果你是以管理员身份登录的,你可以访问一个管理页面,在那里你可以看到一个用户卡列表,并注册另一个管理员。这个卡片列表不需要刷新就可以更新。
因为我写的代码不是很干净,所以我想加入自定义钩子。问题是,使用新的自定义钩子,除了渲染之外,一切都很好。每当我删除一个用户或添加一个新的管理员时,除非我刷新页面,否则cardlist不会更新。
我现在有一个定制的钩子useUsers
,但是我只在输入域和吐司通知中使用它,我试着在钩子中添加用户到useEffect,但是没有解决我的问题。useEffect(() => { refreshUserList(); }, [users]);
这是我的代码。
function useUsers() {
const [users, setUsers] = useState([])
const [showModal, setShowModal] = useState(false)
const notifyUserDeleted = () => toast.success('User deleted!', {
position: "top-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "colored",
});
const [adminUsername, setAdminUsername] = useState("")
const [adminPassword, setAdminPassword] = useState("")
const [showAdminModal, setShowAdminModal] = useState(false)
const notifyAddAdminSuccess = () =>
toast.success('Admin registered!', {
position: "top-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "colored",
})
const refreshUserList = () => {
UserAPI.getUsers()
.then(res => {
setUsers(res.data.users);
})
.catch(err => {
console.error(err);
});
};
useEffect(() => {
refreshUserList();
}, []);
const createAdmin = (adminUsername, adminPassword) => {
const registeredAdmin = {
"username": adminUsername,
"password": adminPassword
};
UserAPI.createAdmin(registeredAdmin)
.then(() => {
setAdminUsername("");
setAdminPassword("");
notifyAddAdminSuccess();
Adminpage.refreshUserList();
})
.catch(err => {
console.log(err);
alert("Failed to register admin!");
});
};
const handleRegisterAdmin = (e) => {
e.preventDefault();
createAdmin(adminUsername, adminPassword);
setShowAdminModal(false);
};
const deleteUser = (id) => {
UserAPI.deleteUser(id)
.then(() => {
notifyUserDeleted()
refreshUserList()
})
.catch(error => {
console.error(error);
});
};
const handleDelete = (e) => {
e.preventDefault();
deleteUser(e.target.value)
setShowModal(false)
}
return {
users, setUsers, showModal, setShowModal, notifyUserDeleted, notifyAddAdminSuccess, showAdminModal, setShowAdminModal,
adminPassword, setAdminPassword, adminUsername, setAdminUsername, refreshUserList, handleRegisterAdmin, handleDelete
}
}
export default useUsers;
function Adminpage() {
const { users, refreshUserList } = useUsers();
return (
<div className="container" style={{ display: "flex" }}>
<UserCardList users={users} refreshUserList={refreshUserList} />
<InputAdmin refreshUserList={refreshUserList} />
</div>
);
}
export default Adminpage;
function UserCardList(props) {
return (
<div className="container">
<h4 style={{ margin: "3% 0 2% 0" }}>User list:</h4>
<Bootstrap.Row>
{
props.users.map(user =>
<UserCard key={user.id} users={user} refreshUserList={props.refreshUserList} />
)
}
</Bootstrap.Row>
</div>
)
}
export default UserCardList;
function UserCard(props) {
const { showModal,
setShowModal,
handleDelete
} = useUsers()
return (
<Bootstrap.Col className="col-lg-4 col-12">
<Bootstrap.Card className='mb-1' style={{ height: "98%", }}>
<Bootstrap.Card.Body>
<Bootstrap.Card.Text><b>User ID: </b>{props.users.id}</Bootstrap.Card.Text>
<Bootstrap.Card.Text><b>Username: </b>{props.users.username}</Bootstrap.Card.Text>
<Bootstrap.Button style={{ backgroundColor: "red", borderColor: "gray" }} onClick={() => setShowModal(true)}><RiDeleteBin5Fill style={{ backgroundColor: "red" }} /></Bootstrap.Button>
</Bootstrap.Card.Body>
</Bootstrap.Card>
<Bootstrap.Modal centered show={showModal} onHide={() => setShowModal(false)}>
<Bootstrap.Modal.Header closeButton>
<Bootstrap.Modal.Title>Confirm Delete</Bootstrap.Modal.Title>
</Bootstrap.Modal.Header>
<Bootstrap.Modal.Body>Are you sure you want to delete this user?</Bootstrap.Modal.Body>
<Bootstrap.Modal.Footer>
<Bootstrap.Button variant="secondary" onClick={() => setShowModal(false)}>
Cancel
</Bootstrap.Button>
<Bootstrap.Button variant="danger" value={props.users.id} onClick={handleDelete}>
Delete
</Bootstrap.Button>
</Bootstrap.Modal.Footer>
</Bootstrap.Modal>
</Bootstrap.Col>
)
}
export default UserCard;
4条答案
按热度按时间pn9klfpd1#
问题
将
users
添加到useEffect
钩子可能会导致渲染循环,因为refreshUserList
最终会更新users
的状态。不要无条件地更新任何钩子的依赖项。React钩子也不共享状态。你有两个组件,
Adminpage
和UserCard
,每个都使用useUsers
钩子的单独示例,每个示例都有自己的状态。改变useUsers
的一个示例中的状态不会影响useUsers
的任何其他示例。溶液
将状态和逻辑从
useUsers
移到单个React上下文提供者,并允许useUsers
钩子的所有示例访问单个上下文值。示例:
x一个一个一个一个x一个一个二个x
用
UsersProvider
组件 Package 应用程序代码,以提供用户状态和回调。现在,使用
useUsers
钩子在UsersProvider
的子Reactree中呈现的所有组件都将访问和引用相同的状态和回调。fcipmucu2#
你所遇到的麻烦在React中非常非常常见:)每个钩子用法都创建了它自己的独立作用域,这意味着每个useUsers()用法都产生了单独的用户集合(从useEffect的后端中检索),并公开了单独的一组特定于用户操作的方法(如handleDelete)
首先,在回答第一个问题之前,让我们揭示一下你可能很快会面临的非常非常戏剧性的麻烦
只要每个“useUsers”执行都有自己的作用域,每次使用此钩子时,都会执行以下代码:
现在想象一下在Adminpage组件中调用这个钩子--用户从后端加载。加载用户后,用加载的用户呈现UserCardList。UserCardList为每个加载的用户呈现UserCard组件。最后,每个UserCard组件调用您创建的“useUsers”钩子,每个钩子调用上面的代码(useEffect -〉refreshUserList),这样每个UserCard组件都会从后端再加载一次用户:(
您看到了问题所在,您加载了20个用户,然后为每个用户再次加载所有这些用户。如果您有20个用户,则后端将有21个调用。如果您有100个用户,则后端将有101个调用,等等......后端的性能肯定会非常非常快地下降
为了证明这个假设请打开浏览器的网络标签,然后重新加载页面。你肯定会看到无休止的加载用户请求序列...
这是关于后端的,但是当用户被删除时前端不工作的最初问题仍然是开放的
情况如下:在Adminpage组件中使用“useUsers”呈现用户列表,并在每个UserCard组件中使用“useUsers”在需要时调用“handleDelete
这些组件中的每一个都独立加载“用户”,当用户通过UserCard组件中的handleDelete删除时-是的,您可以通过以下方式从存储中“物理”删除此用户
但是,在前端执行“refreshUserList”:
仅在当前UserCard组件范围内,并且仅在UserCard组件范围内刷新“users”集合
因此,父Adminpage组件不知道其中一个用户被删除,用户列表也不会更新
这里最简单的“工作”解决方案不是多次调用useUsers()钩子,而是将handleDelete从Adminpage组件直接传递给每个UserCard组件(作为输入参数):
这种技术可以用最少的工作量修复您面临的所有后端/前端问题,但这种修复方法也有一些缺点:
第一个麻烦(只有一个钩子用法)只有当你以某种方式连接你的“useUsers”用法时才能修复。也许,你可以利用一些类似rxjs的方法,在useUsers中使用订阅者/通知,在他们之间共享相同的“users”集合,并且每当“useUsers”中发生什么事情时-通知其他人,等等...其他可能的解决方案-利用一些应用程序范围的存储,我喜欢REDUX,并在那里存储用户。无论如何,这肯定是一个复杂得多的问题,然后你已经面临,这是“更好的”跳过它,如果不紧急
要解决第二个问题(通过层次结构中的每个组件传递callback作为输入参数),React的useContext()钩子可能非常方便https://beta.reactjs.org/reference/react/useContext
gdrx4gfi3#
第二个参数
[users]
告诉useEffect什么时候触发,所以你实际上是在用户改变的时候刷新用户,这意味着永远不会,因为用户在刷新的时候也会改变。vc9ivgsu4#
Uladzimir提出的解决方案奏效了。我必须将所有内容从管理页面传递到组件中,如下所示: