I am fetching a list of items from a local Data.js
file and displaying it using map
method in Header.js
. But when I click on user image then it should go to user detail page and should display all the properties of that object. But when I click it gives me error saying "Cannot read properties of undefined (reading 'name')
". I am using react-router-domv6
.
Users.js
import React from 'react';
import { Link } from 'react-router-dom';
import { list } from './Data';
function Header() {
console.log(list);
return (
<div>
<div className="bg-gradient-to-b from-white-300 to-[#6337c8] relative flex justify-center items-center h-screen w-screen">
<div className="absolute top-0 w-full z-[-1]">
<div className="h-[20vh] bg-[#6337c8]"></div>
<svg className="drop-shadow-[0px_17px_0px_#dcd6f3] z-[-1]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
<path
fill="#6337c8"
fillOpacity={1}
d="M 0 224 L 80 197.3 C 160 171 320 117 480 128 C 640 139 800 213 960 245.3 C 1120 277 1280 267 1360 261.3 L 1440 256 L 1440 0 L 1360 0 C 1280 0 1120 0 960 0 C 800 0 640 0 480 0 C 320 0 160 0 80 0 L 0 0 Z"
></path>
</svg>
</div>
<div className="h-[85vh] shadow-2xl w-2/5 rounded-[25px] bg-white">
<div className="w-full rounded-t-[25px] flex justify-center items-center h-[16vh] text-gray-600 bg-gray-100 text-xl">
{' '}
Select an account{' '}
</div>
<div className="h-[65vh] pb-4 px7 overflow-y-auto scrollbar scrollbar-thumb-gray-200">
{list &&
list.length > 0 &&
list.map((value) => (
<Link key={value.id} to={`/profile/${value.id}`}>
<div className="cursor-pointer flex gap-3 items-center border-b py-2">
<img className="rounded-full w-[7%]" src={value.profilepicture} alt="" />
<p>City: {value.address.city}</p>
<p>Company Name: {value.company.name}</p>
<div className="text-md text-gray-700 text-center font-[400] ">
<button>{value.name}</button>
</div>
</div>
</Link>
))}
</div>
</div>
</div>
</div>
);
}
export default Header;
UserDetail.js
import React from 'react';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import { list } from './Data';
function Profile() {
const [item, setItem] = useState({});
const [show, setShow] = useState(false);
const { id } = useParams();
const navigate = useNavigate();
useEffect(() => {
getItem();
}, []);
const getItem = () => {
const newItem = list.find((value) => value.id === parseInt(id));
setItem(newItem);
};
const Logout = () => {
navigate('/');
};
return (
<div>
<div className="w-screen h-screen flex gap-10 px-8 pt-3 overflow-x-hidden">
<div className="flex flex-col w-full">
<div className="h-[13vh] w-[100%] border-b border-gray-300">
<div className="flex h-full items-center justify-between">
<div className="capitalize text-[20px] text-gray-500">
<p>profile</p>
</div>
<div className="relative inline-block text-left mt-5 ml-40" data-headlessui-state>
{
<div>
<button
className="inline-flex w-full justify-center rounded-md bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm focus:outline-none "
id="headlessui-menu-button-:r0:"
type="button"
aria-haspopup="menu"
aria-expanded="false"
data-headlessui-state="open"
aria-controls="headlessui-menu-items-:r1:"
onClick={() => setShow(!show)}
>
<div className="cursor-pointer flex justify-end gap-3 items-center">
<img className="rounded-full w-[7%]" src={item.profilepicture} alt={item.id} />
<p>{item.name}</p>
</div>
</button>
</div>
}
{show && (
<div
className="absolute right-0 z-10 mt-2 w-[20vw] origin-top-right rounded-md bg-white shadow-lg focus:outline-none transform opacity-100 scale-100"
aria-labelledby="headlessui-menu-button-:r0:"
id="headlessui-menu-items-:r7:"
role="menu"
tabIndex={0}
data-headlessui-state="open"
>
<div className=" py-1" role="none">
<div className="flex flex-col justify-center items-center gap-1 pb-2 " role="none">
<img className="rounded-full" src={item.profilepicture} alt="" width="50%" role="none"></img>
<p role="none">{item.name}</p>
<p className="text-[14px] text-gray-400" role="none">
{item.email}
</p>
<div className="border-t border-gray-300 ">
<button className="mt-2 rounded-[20px] px-2 py-1 text-white bg-[#e15b22]" role="none" onClick={Logout}>
Sign out
</button>
</div>
</div>
</div>
</div>
)}
</div>
</div>
</div>
<div className="block">
<div className="w-full h-full flex mt-5">
<div className="border-r border-gray-300 h-full w-[40%]">
{
<div className="flex flex-col w-full items-center h-full justify-center">
<img className="rounded-full w-[48%]" src={item.profilepicture} alt="" />
<p className="text-[16px] font-[400] text-gray-800">{item.name}</p>
<div className="flex flex-col gap-2 border-b border-gray-300 py-3">
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">Username:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.username}</p>
</div>
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">email:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.email}</p>
</div>
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">phoneNumber:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.phone}</p>
</div>
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">website:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.website}</p>
</div>
</div>
<div className="flex flex-col gap-2 py-3">
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">company:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.company.name}</p>
</div>
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">bs:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.company.bs}</p>
</div>
</div>
</div>
}
</div>
<div className="h-full w-[60%]">
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">address:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.address.city}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default Profile;
Data.js
export const list = [
{
id: 1,
name: ' Leanne Graham',
username: 'Bret',
email: '[email protected]',
profilepicture: 'https://panorbit.in/wp-content/uploads/2019/hotlink-ok/1001.jpeg',
address: {
street: 'Kulas Light',
suite: 'Apt. 556',
city: 'Gwenborough',
zipcode: '92998-3874',
geo: {
lat: '-37.3159',
lng: '81.1496',
},
},
phone: '1-770-736-8031 x56442',
website: 'hildegard.org',
company: {
name: 'Romaguera-Crona',
catchPhrase: 'Multi-layered client-server neural-net',
bs: 'harness real-time e-markets',
},
},
{
id: 2,
name: ' Ervin Howell',
username: 'Antonette',
email: '[email protected]',
profilepicture: 'https://panorbit.in/wp-content/uploads/2019/hotlink-ok/1002.jpeg',
address: {
street: 'Victor Plains',
suite: 'Suite 879',
city: 'Wisokyburgh',
zipcode: '90566-7771',
geo: {
lat: '-43.9509',
lng: '-34.4618',
},
},
phone: '010-692-6593 x09hotlink-ok5',
website: 'anastasia.net',
company: {
name: 'Deckow-Crist',
catchPhrase: 'Proactive didactic contingency',
bs: 'synergize scalable supply-chains',
},
}
]
3
Answers
You just need to change the initial state value to
const [item, getItem] = useState(null);
and then check if the item exists, render the data otherwise not.Please replace you
UserDetail.js
file that file which I updated in my answer.Your initial state is a empty object
{}
which means the propertyname
is not defined yet.To resolve this you can use optional chaining.
Same for all the other expression which use
item
.Now for the
useEffect
, this will currently only ran the very first render. But will not update once you revisit to check another user. To fix this you can pass theid
as a dependency.The issue is that in the
Profile
component the initialitem
state is an empty object{}
which is fine when only accessing root-level properties, i.e.item.name
,item.company
, etc, but the issue is that during the initial render cycle and any subsequent render cycle until theitem
state is populated all the root-level properties will be undefined, so an error is thrown when attempting to access a second-level property likeitem.company.name
.item.company
is undefined and attempting to accessitem.company.name
will throw the exception.The
item
"state" is completely unnecessary as it’s what we would call derived state, in that it is derived from the currentid
route path parameter and the importedlist
array. Derived state shouldn’t be stored in local state unless absolutely necessary.To resolve the issue simply compute the
item
value from thelist
andid
values. I recommend also converting the data’sid
property to a String instead of parsing the route path parameter to an integer as it’s generally easier to compare number-like strings.Example:
If the "cost" of computing the
item
value is "expensive" you can memoize the value with theuseMemo
hook.In fact, just about any time you see you’ve implemented the pattern
useState
+useEffect
you should really be usinguseMemo
.