使用 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;

问题

  1. Array.prototype.find returns 数组中的第一个匹配项,如果没有匹配项则为未定义。如果在数组中找不到匹配项,您可能不想将 selectedUser 状态更新为未定义。
  2. 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;
  }
};