I am working on a contact list application . Basically using material-ui list of several individuals/persons. Here is a rough outline :
PersonListItem --- Child Component : contains all the person's detail displayed on a card PersonList -- Parent Component : basically maps through all persons and displays all the cards as a list
Recently, I added a star component to each card , the idea being if I want to mark a person as favorite then I would click the star and it turns yellow.
To achieve this I learned and implemented React's lift-state-up with hooks pattern. So now the flow is like so:
Child Component => onClick = {someMethod} => someMethod passed as props to Parent => Parent updates prop component => Child re-renders the component
But now when I click on any single contact's star to mark favorite, all contacts get marked as favorite.
I was thinking to use event.target to get the id of individual star but how do I pass and access the event.target.id in the props ?
Here is my current code:
const PersonListItem = (props) {
const{person, onSelectChanged, favorite, FavColorColor, markFavorite} = props;
return(
<ListItem button
gender = {undefined}
onClick = {onSelectChanged}
<Avatar className = {classes.avatar} src={person.imageLocation === NO_USER_IMAGE : null : person.imageLocation}>
<IconButton StarOutLinedIcon onClick = {markFavorite} style={{backgroundColor: `${FavColor}`}/>
<ListItemText primary =
{
{person.name}
}
secondary = {<div className={classes.smallText}>{person.company}{person.jobTitle}</div>/>
</ListItem>
)
}
export default PersonListItem;
const PersonList = (props) => {
const[favorite, setFavorite] = useState(0);
const[FavColor, setColor] = useState(' ');
const markFavorite = (value) => {
setFavorite(value = !value);
{!favorite ? setColor("yellow") : setColor(" ")}
}
return(
<div className={classes.root}>
<MuiList className={props.className} isLoading={isLoading}>
{
!isLoading && person
.map((person, index) => {
return <PersonListItem key={index}
person = {person}
onSelectChanged = () =>{
HideAdd();
}
markFavorite = {markFavorite} FavColorColor = {FavColor}/>
}
}
</MuiList>
</div>
)
}
export default PersonList;
//Material UI ListItem
classs MuiList extends React.Component{
componentDidMount() {
i(this.props.fetch)
{
this.props.fetch();
}
}
render() {
const attributes = {...this.props};
delete attributes.isLoading;
delete attributes.fetch;
delete attributes.fetchData;
const items = this.props.isLoading;
? <ListItem key={0}>
<CircularProgress/>
<ListItemText primary='Loading...'/>
</ListItem>
: this.props.children;
return <List {...attributes>{items}</List>
}
}
export default MuiList;
I would replace favorite state for an object with person ids that will map if they are true or false. also remove favColor
state. it's a derived state from favorite, you don't need this state.
now, I would change the markFavorite function(maybe change name to toggleFavorite
). it would receive an id from that person. it checks if it's in favorites
; if it's undefined then key doesn't exist yet, so it's been marked as favorite), otherwise only toggle its value:
const[favorites, setFavorites] = useState({});
const markFavorite = (id) => {
setFavorites(prevFavorites => {
const favorites = { ...prevFavorites }
favorites[id] = favorites[id] === undefined || !favorites[id]
return favorites
})
}
then at your person mapping I would pass markFavorite
as an arrow function passing person.id
(here I assume person has an id, it's better than passing the index from your map).
also pass isFavorite
with the boolean value of favorites[person.id]
(!!
returns the boolean value. it's not actually necessary the !!
since undefined
resolves to false, it's more for consistency type)
markFavorite={() => markFavorite(person.id)} isFavorite={!!favorites[person.id]}/>
at your PersonListItem
you would have to change the IconButton style definition to a ternary condition:
<IconButton StarOutLinedIcon onClick = {markFavorite} style={{backgroundColor: isFavorite ? 'yellow' : '' }}/>
this way each person can be marked independently, not affecting others.