React Hooks, Conditionally change state inside onClick function, updates on the next click

Viewed 439

Hello I have a single list component which render some <Card> components that has a prop isSelected . Because alot of things happens when a <Card> Component has isSelected === true I added the state on the <List> component, and I want when someone clicks a card to check:

1) If there are no previously selected items ( state===null to add that item's id to state )

2) If someone clicks the same item or another item while there is already an item selected in state, to just unselected the active item.

import {Card} from "./Card";
import cloneDeep from 'lodash/cloneDeep';

const List = () => {
    const [selectedCard, setSelectedCard] = useState(null);

    const onCardClick = id => {
        console.debug(selectedCard, id)
        const newSelectedCard = cloneDeep(selectedCard);
        // if he clicks another item while an item is active
        // or if he clicks the same item while active
        // should just make it inactive
        if (newSelectedCard !== null || newSelectedCard === id) {
            setSelectedCard(null)
        } else {
            setSelectedCard(id)
        }
        console.debug(selectedCard, id)
    }

    return (
        <ul className="card-list">
            {cardData.map(card => (
                <Card
                    onClick={() => onCardClick(card.id)}
                    key={card.id}
                    isSelected={selectedCard === card.id}
                    {...card}
                />
            ))}
        </ul>
    )
}


export const CardList = () => (
    <List/>
);

The issue is that the 2 console.debugs print the same values which means that the state doesnt update imediately and Im experiencing some strange behaviours here and there. Am I missing something here?

2 Answers

Basically you need to follow 3 condition as below

if(newSelectedCard === null){
        setSelectedCard(id)
    }
    else if(newSelectedCard === id){
        setSelectedCard(null);
    }
    else{
        setSelectedCard(id)
    }

Here is the Complete example:

import cloneDeep from 'lodash/cloneDeep';
import React, {useState} from "react";

const List = () => {
    const [cardData, setCardData] = useState([
        {id: 1, title: 'First Card'},
        {id: 2, title: 'Second Card'},
        {id: 3, title: 'Third Card'},
        {id: 4, title: 'Fourth Card'},
    ]);
    const [selectedCard, setSelectedCard] = useState(null);

    const onCardClick = id => {
        console.log(selectedCard, id);
        const newSelectedCard = cloneDeep(selectedCard);
        // if he clicks another item while an item is active
        // or if he clicks the same item while active
        // should just make it inactive
        if(newSelectedCard === null){
            setSelectedCard(id)
        }
        else if(newSelectedCard === id){
            setSelectedCard(null);
        }
        else{
            setSelectedCard(id)
        }
        console.log(selectedCard, id)
    };

    return (
        <ul className="card-list">
            {cardData.map(card => (
                <Card
                    onClick={() => onCardClick(card.id)}
                    key={card.id}
                    isSelected={selectedCard === card.id}
                    {...card}
                />
            ))}
        </ul>
    )
};

export const CardList = () => (
    <List/>
);

const Card = (props) => {
    const backColor = props.isSelected? '#F9740E' : '#3FB566';
    return (
        <div onClick={() => props.onClick()}>
            <div style={{backgroundColor: backColor, border: '1px solid darkgreen', color: 'white', padding: 10, marginBottom: 10}}>
                <h3>{props.id}</h3>
                <h4>{props.title}</h4>
            </div>
        </div>
    );
};

Update Here is Code SandBox

Not sure why you need to use cloneDeep.

const onCardClick = id => {
   if (selectedCard === id) {
       setSelectedCard(null);
   } else {
       setSelectedCard(id);
   }
}
Related