'Build a CRUD App with React Hooks and the Context API

I want to build a CRUD App with React Hooks and the Context API. my problem is in the EditUser component. when I click on edit button to edit user it doesn't show anything on the page and in the console I have this error:

selectedUser is undefined for this part value={selectedUser.name} name="name"

<Input type='text' palaceholder="Enter Name"
    value={selectedUser.name} name="name"
    onChange={(e) => handleOnChange("name", e.target.value)}
></Input>

thank you for your help!

Here is my components:

GlobalState component:

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 component:

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
    }


} 

and finallyEditUsercomponent:

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;


Solution 1:[1]

Issues

  1. Array.prototype.find returns either the first match in the array or undefined if there are no matches. If there is no match found in the array you probably don't want to update the selectedUser state to undefined.
  2. If/when the selectedUser state is undefined attempting to access properties of undefined will throw the error you see. Use a null-check/guard-clause or the Optional Chaining Operator to prevent accidental accesses into potentially null or undefined objects.

Solution

Only enqueue selectedUser updates if there is a matching user to update with.

const { currentUserId } = useParams();

useEffect(() => {
  const userId = currentUserId;
  const selectedUser = users.find(user => String(user.id) === userId);
  if (selectedUser) {
    setSelectedUser(selectedUser);
  }
}, [currentUserId, users]);

Protect the currentUser nested property accesses, and provide a valid defined fallback value for the input so it doesn't throw errors switching between controlled and uncontrolled. Refactor the handleOnChange to consume the onChange event and destructure the input name and value from it, and use a functional state update to update from the previous state.

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>

Additional Suggestion

This is only a minor point about the reducer function logic. What you have is ok since the only property the userReducer has is a users property, but it's a reducer function convention to also shallow copy the previous state as well.

Example:

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;
  }
};

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Drew Reese