'React app throws TypeError: Cannot read properties of undefined (reading 'name') when compiling
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
Solution 1:[1]
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
selectedUserstate invariant. In other words, don't update it an undefined value unless you have a specific need to. Check the result ofusers.findto 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' />
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 |
