使用 React Hooks 和上下文构建一个 CRUD 应用 API
Build a CRUD App with React Hooks and the Context API
我想用 React Hooks 和上下文构建一个 CRUD 应用 API。我的问题出在 EditUser
组件中。当我点击编辑按钮来编辑用户时,它没有在页面上显示任何内容,在控制台中我有这个错误:
selectedUser
未定义此部分 value={selectedUser.name} name="name"
<Input type='text' palaceholder="Enter Name"
value={selectedUser.name} name="name"
onChange={(e) => handleOnChange("name", e.target.value)}
></Input>
感谢您的帮助!
这是我的组件:
GlobalState
分量:
import React, { createContext, useReducer } from 'react'
import AppReducer from './AppReducer'
//Initial State
const initialState = {
users: [
]
}
//Create Context
export const GlobalContext = createContext(initialState);
//Provider Component
export const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(AppReducer, initialState);
//Actions
const removeUser = (id) => {
dispatch({
type: 'REMOVE_USER',
payload: id
})
}
const addUser = (user) => {
dispatch({
type: 'ADD_USER',
payload: user
})
}
const editUser = (user) => {
dispatch({
type: 'EDIT_USER',
payload: user
})
}
return (
<GlobalContext.Provider value={{ users: state.users, removeUser, addUser, editUser }}>
{children}
</GlobalContext.Provider>
)
}
AppReducer
分量:
export default (state, action) => {
switch (action.type) {
case 'REMOVE_USER':
return {
users: state.users.filter(user => {
return user.id !== action.payload
})
}
case 'ADD_USER':
return {
users: [action.payload, ...state.users]
}
case 'EDIT_USER':
const updateUser = action.payload;
const editUsers = state.users.map(user => {
if (user.id === updateUser.id) {
return updateUser
}
return user
});
return {
users: editUsers
}
default:
return state
}
}
最后EditUser
组件:
import { Link, useNavigate, useParams } from 'react-router-dom';
import { GlobalContext } from '../context/GlobalState';
import React,{ useContext, useState, useEffect } from 'react';
import {
Form,
FormGroup,
Label,
Input,
Button
} from 'reactstrap'
const EditUser = () => {
const [selectedUser, setSelectedUser] = useState({
id: "",
name:""
})
const { users, editUser } = useContext(GlobalContext)
const navigate = useNavigate()
const { currentUserId } = useParams();
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find(user => user.id === parseInt(userId));
setSelectedUser(selectedUser);
}, [currentUserId, users])
const handleSubmit = (e) => {
e.preventDefault();
editUser(selectedUser);
navigate('/');
};
const handleOnChange = (userKey, newValue) => {
setSelectedUser({ ...selectedUser, [userKey]: newValue })
};
return (
<Form onSubmit={handleSubmit}>
<FormGroup>
<Label>Name</Label>
<Input type='text' palaceholder="Enter Name"
value={selectedUser.name} name="name"
onChange={(e) => handleOnChange("name", e.target.value)}
></Input>
</FormGroup>
<Button type='submit' className='bg-success '>Edit Name</Button>
<Link to="/" className="btn btn-danger m-2">Cancel</Link>
</Form>
);
}
export default EditUser;
问题
Array.prototype.find
returns 数组中的第一个匹配项,如果没有匹配项则为未定义。如果在数组中找不到匹配项,您可能不想将 selectedUser
状态更新为未定义。
- If/when
selectedUser
state is undefined 试图访问未定义的属性将抛出您看到的错误。使用 null-check/guard-clause 或可选链接运算符来防止意外访问可能为空或未定义的对象。
解决方案
如果有匹配的用户可以更新,则仅将 selectedUser
更新加入队列。
const { currentUserId } = useParams();
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find(user => String(user.id) === userId);
if (selectedUser) {
setSelectedUser(selectedUser);
}
}, [currentUserId, users]);
保护 currentUser
嵌套的 属性 访问,并为 input
提供有效定义的回退值,因此它不会在受控和不受控之间切换时引发错误。重构 handleOnChange
以使用 onChange
事件并从中解构输入名称和值,并使用功能状态更新从以前的状态更新。
const handleOnChange = (e) => {
const { name, value } = e.target;
setSelectedUser(selectedUser => ({
...selectedUser,
[name]: value
}));
};
...
<FormGroup>
<Label>Name</Label>
<Input
type='text'
palaceholder="Enter Name"
value={selectedUser?.name ?? ""}
name="name"
onChange={handleOnChange}
/>
</FormGroup>
补充建议
这只是 reducer 函数逻辑的一个小问题。你所拥有的是好的,因为 userReducer
唯一的 属性 是一个 users
属性,但它是一个 reducer 函数约定,它也浅复制之前的 state
还有。
示例:
export default (state, action) => {
switch (action.type) {
case 'REMOVE_USER':
return {
...state,
users: state.users.filter(user => user.id !== action.payload),
}
case 'ADD_USER':
return {
...state,
users: [action.payload, ...state.users],
}
case 'EDIT_USER':
const updateUser = action.payload;
return {
...state,
users: state.users.map(user => user.id === updateUser.id
? updateUser
: user
),
}
default:
return state;
}
};
我想用 React Hooks 和上下文构建一个 CRUD 应用 API。我的问题出在 EditUser
组件中。当我点击编辑按钮来编辑用户时,它没有在页面上显示任何内容,在控制台中我有这个错误:
selectedUser
未定义此部分 value={selectedUser.name} name="name"
<Input type='text' palaceholder="Enter Name"
value={selectedUser.name} name="name"
onChange={(e) => handleOnChange("name", e.target.value)}
></Input>
感谢您的帮助!
这是我的组件:
GlobalState
分量:
import React, { createContext, useReducer } from 'react'
import AppReducer from './AppReducer'
//Initial State
const initialState = {
users: [
]
}
//Create Context
export const GlobalContext = createContext(initialState);
//Provider Component
export const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(AppReducer, initialState);
//Actions
const removeUser = (id) => {
dispatch({
type: 'REMOVE_USER',
payload: id
})
}
const addUser = (user) => {
dispatch({
type: 'ADD_USER',
payload: user
})
}
const editUser = (user) => {
dispatch({
type: 'EDIT_USER',
payload: user
})
}
return (
<GlobalContext.Provider value={{ users: state.users, removeUser, addUser, editUser }}>
{children}
</GlobalContext.Provider>
)
}
AppReducer
分量:
export default (state, action) => {
switch (action.type) {
case 'REMOVE_USER':
return {
users: state.users.filter(user => {
return user.id !== action.payload
})
}
case 'ADD_USER':
return {
users: [action.payload, ...state.users]
}
case 'EDIT_USER':
const updateUser = action.payload;
const editUsers = state.users.map(user => {
if (user.id === updateUser.id) {
return updateUser
}
return user
});
return {
users: editUsers
}
default:
return state
}
}
最后EditUser
组件:
import { Link, useNavigate, useParams } from 'react-router-dom';
import { GlobalContext } from '../context/GlobalState';
import React,{ useContext, useState, useEffect } from 'react';
import {
Form,
FormGroup,
Label,
Input,
Button
} from 'reactstrap'
const EditUser = () => {
const [selectedUser, setSelectedUser] = useState({
id: "",
name:""
})
const { users, editUser } = useContext(GlobalContext)
const navigate = useNavigate()
const { currentUserId } = useParams();
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find(user => user.id === parseInt(userId));
setSelectedUser(selectedUser);
}, [currentUserId, users])
const handleSubmit = (e) => {
e.preventDefault();
editUser(selectedUser);
navigate('/');
};
const handleOnChange = (userKey, newValue) => {
setSelectedUser({ ...selectedUser, [userKey]: newValue })
};
return (
<Form onSubmit={handleSubmit}>
<FormGroup>
<Label>Name</Label>
<Input type='text' palaceholder="Enter Name"
value={selectedUser.name} name="name"
onChange={(e) => handleOnChange("name", e.target.value)}
></Input>
</FormGroup>
<Button type='submit' className='bg-success '>Edit Name</Button>
<Link to="/" className="btn btn-danger m-2">Cancel</Link>
</Form>
);
}
export default EditUser;
问题
Array.prototype.find
returns 数组中的第一个匹配项,如果没有匹配项则为未定义。如果在数组中找不到匹配项,您可能不想将selectedUser
状态更新为未定义。- If/when
selectedUser
state is undefined 试图访问未定义的属性将抛出您看到的错误。使用 null-check/guard-clause 或可选链接运算符来防止意外访问可能为空或未定义的对象。
解决方案
如果有匹配的用户可以更新,则仅将 selectedUser
更新加入队列。
const { currentUserId } = useParams();
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find(user => String(user.id) === userId);
if (selectedUser) {
setSelectedUser(selectedUser);
}
}, [currentUserId, users]);
保护 currentUser
嵌套的 属性 访问,并为 input
提供有效定义的回退值,因此它不会在受控和不受控之间切换时引发错误。重构 handleOnChange
以使用 onChange
事件并从中解构输入名称和值,并使用功能状态更新从以前的状态更新。
const handleOnChange = (e) => {
const { name, value } = e.target;
setSelectedUser(selectedUser => ({
...selectedUser,
[name]: value
}));
};
...
<FormGroup>
<Label>Name</Label>
<Input
type='text'
palaceholder="Enter Name"
value={selectedUser?.name ?? ""}
name="name"
onChange={handleOnChange}
/>
</FormGroup>
补充建议
这只是 reducer 函数逻辑的一个小问题。你所拥有的是好的,因为 userReducer
唯一的 属性 是一个 users
属性,但它是一个 reducer 函数约定,它也浅复制之前的 state
还有。
示例:
export default (state, action) => {
switch (action.type) {
case 'REMOVE_USER':
return {
...state,
users: state.users.filter(user => user.id !== action.payload),
}
case 'ADD_USER':
return {
...state,
users: [action.payload, ...state.users],
}
case 'EDIT_USER':
const updateUser = action.payload;
return {
...state,
users: state.users.map(user => user.id === updateUser.id
? updateUser
: user
),
}
default:
return state;
}
};