I keep running into an issue with my CRUD application when I go to edit a name from my list, it throws an error "TypeError: Cannot read properties of undefined (reading 'name')".
It seems to stem from the "value={selectedUser.name}" in my input field, but when I remove it the app compiles but I cannot alter the field at all - nothing happens when you type. Any help is appreciated!
import React, { useState, useContext, useEffect } from 'react'
import { GlobalContext } from '../context/GlobalState'
import { Link, useNavigate, useParams } from 'react-router-dom'
import { Form, FormGroup, Label, Input, Button } from 'reactstrap'
const EditUser = (props) => {
const [selectedUser, setSelectedUser] = useState({
id: '',
name: ''
});
const { users, editUser } = useContext(GlobalContext);
const navigate = useNavigate();
const currentUserId = useParams(props);
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find(user => user.id === userId)
setSelectedUser(selectedUser)
}, [currentUserId, users])
const onSubmit = (e) => {
e.preventDefault()
editUser(selectedUser)
navigate('/');
}
const onChange = (e) => {
setSelectedUser({...selectedUser, [e.target.name]: e.target.value})
}
return (
<div>
<Form onSubmit={onSubmit}>
<FormGroup>
<Label>Edit Name</Label>
<Input type='text' name='name' value={selectedUser.name} onChange={onChange} placeholder='Enter Name'></Input>
</FormGroup>
<Button type='submit'>Edit Name</Button>
<Link to='/' className='btn btn-danger m-2'>Back</Link>
</Form>
</div>
)
}
export default EditUser
CodePudding user response:
If currentUserId
is undefined
for any reason, or your users.find()
returns undefined
, then you're going to be storing undefined
in your selectedUser
state variable.
Given from your comments it looks like you're not able to use optional chaining (the foo?.bar
syntax) then you probably just want to prevent this at source:
useEffect(() => {
const userId = currentUserId;
const foundUser = users.find((user) => user.id === userId);
if (foundUser) {
setSelectedUser(foundUser);
}
}, [currentUserId, users]);
Also note I renamed your variable inside your useEffect
to avoid confusion with your selectedUser
at the higher level.
CodePudding user response:
Issue
Array.prototype.find
returns the first matching element, or undefined if no such match is found.
const selectedUser = users.find(user => user.id === userId); // potentially undefined
Additionally, the returned value from the useParams
hook is an object of the key-value pairs of any defined route path parameters. The code appears to incorrectly assume the returned value is the currentUserId
parameter from a path, and uses that to search the users
array.
const currentUserId = useParams(props);
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find(user => user.id === userId);
setSelectedUser(selectedUser);
}, [currentUserId, users]);
I doubt there could ever be a found match comparing user.id
against the returned params object.
Solution
I suspect that the actual path param is really userId
. Either destructure it immediately from the useParams
hook.
const { userId } = useParams();
or rename currentUserId
to params
and destructure userId
in the hook.
const params = useParams(props);
useEffect(() => {
const { userId } = params;
const selectedUser = users.find(user => user.id === userId);
setSelectedUser(selectedUser);
}, [params.userId, users]);
One last point about route params and id properties. The route params will always be a string type. Sometimes the id
is actually a number type, so you'll want to use a type-safe equality check. Convert ids to string types, which covers both string ids and will convert numbers to number-like strings.
useEffect(() => {
const { userId } = params;
const selectedUser = users.find(user => String(user.id) === userId);
setSelectedUser(selectedUser);
}, [params.userId, users]);
Guarding against null/undefined values.
First, try to maintain a stable
selectedUser
state invariant. In other words, don't update it an undefined value unless you have a specific need to. Check the result ofusers.find
to see if a match was found.const { userId } = useParams(props); useEffect(() => { const selectedUser = users.find(user => String(user.id) === userId); if (selectedUser) { setSelectedUser(selectedUser); } }, [userId, users]);
Second, defensively guard against potentially null/undefined accesses in your render function, just in case. You can use the Optional Chaining and Nullish Coalescing Operators to guard null access and provide a valid defined value for the controlled input.
<Input type='text' name='name' value={selectedUser?.name ?? ""} onChange={onChange} placeholder='Enter Name' />
or if you prefer older-school null-checks/guard-clauses
<Input type='text' name='name' value={(selectedUser && selectedUser.name) ?? ""} onChange={onChange} placeholder='Enter Name' />