Search code examples
javascriptreactjsbuttonreact-hookscolors

How to make only one button disabled on click in map method and how to change className after the timer expires in React?


How to make only one button disabled on click in map method? With the help of the disabled hook, I have all pressed buttons . And how to make it so that after the timer expires the 'current__events__hot-price disabled' className changes back to 'current__events__hot-price'?

import { useEffect, useState } from 'react'
import './CurrentEventsItem.scss'

const CurrentEventsItem = () => {

   const [timeLeft, setTimeLeft] = useState(5*60)

   const getPadTime = (time) => time.toString().padStart(2, '0')

   const minutes = getPadTime(Math.floor(timeLeft / 60))
   const seconds = getPadTime(timeLeft - minutes * 60)

    useEffect(() => {
        const interval = setInterval(() => {
            setTimeLeft((timeLeft) => (timeLeft >= 1 ? timeLeft - 1 : setDisabled(false) || 5*60))
        }, 1000)
        return () => clearInterval(interval) 
    }, [])

    const [appState, changeState] = useState({
        objects: [
            {id: 1, title: 'Apple iPhone 13 Pro Max 256Gb (небесно-голубой)', avatar: 'https://cdn-icons-png.flaticon.com/512/147/147144.png', statusItem: false},
            {id: 2, title: '500 Stars', avatar: 'https://cdn-icons-png.flaticon.com/512/147/147144.png', statusItem: false},
            {id: 3, title: 'Sony PlayStation 5 Digital Edition  ', avatar: 'https://cdn-icons-png.flaticon.com/512/147/147144.png', statusItem: false}
        ]
    })


    const toggleActive = (index) => {
        let arrayCopy = [...appState.objects]

        arrayCopy[index].statusItem 
            ? (arrayCopy[index].statusItem = false) 
            : (arrayCopy[index].statusItem = true)
            setDisabled(true)

            changeState({...appState, objects: arrayCopy})
    }

    const toggleActiveStyles = (index) => {
        if (appState.objects[index].statusItem) {
            return 'current__events__hot-price disabled'
        } else {
            return 'current__events__hot-price'
        }
    }

    const toggleActiveStylesBtns = (index) => {
        if (appState.objects[index].statusItem) {
            return 'current__events__btn-green disabled'
        } else {
            return 'current__events__btn-green'
        }
    }

    const [disabled, setDisabled] = useState(false)

    return (
        <>
        <div className='current__events__wrapper'>
            {appState.objects.map((item, index) => 
                <div className="current__events__hot-price__item" key={index}>
                    <div className={toggleActiveStyles(index)}>
                        <h5 className="current__events__card-title__large">Hot Price</h5>
                    </div>
                    <div className="current__events__image">
                        <img src={item.avatar} alt='user' className="rounded-circle" width='75' height='75'/>
                    </div>
                    <div className="current__events__info">
                        <h4 className="current__events__title__middle">{item.title}</h4>
                    </div>
                    
                    <div className="current__events__timer">
                        <span>{minutes}</span>
                        <span>:</span>
                        <span>{seconds}</span>
                    </div>
                    
                    
                    <button className={toggleActiveStylesBtns(index)} onClick={() => toggleActive(index)} disabled={disabled}>СДЕЛАТЬ ХОД</button>
                        
                </div>
                
            )}
            </div>
        </>
    )
}

export default CurrentEventsItem

Solution

  • From what I can gather, you toggle one element's status by index and disable the button at that specific index. Instead of toggling the statusItem property (a state mutation, BTW) you should just store the index of the "active" item.

    Add an activeIndex state that is null in its "inactive" state, and equal to an index when it is "active". The useEffect hook should just instantiate the interval and the interval callback should just decrement the time left. Use a separate useEffect hook with a dependency on the timeLeft state to reset the activeIndex and timeLeft states. Disable the button element when the activeIndex equals the mapped index. Pass the activeIndex state to the CSS classname utility functions.

    Example:

    const CurrentEventsItem = () => {
      const [activeIndex, setActiveIndex] = useState(null);
      const [timeLeft, setTimeLeft] = useState(5*60);
    
      const getPadTime = (time) => time.toString().padStart(2, '0');
    
      const minutes = getPadTime(Math.floor(timeLeft / 60));
      const seconds = getPadTime(timeLeft - minutes * 60);
    
      useEffect(() => {
        const interval = setInterval(() => {
          setTimeLeft((timeLeft) => timeLeft - 1);
        }, 1000);
    
        return () => clearInterval(interval);
      }, []);
    
      const [appState, changeState] = useState({
        objects: [
          {id: 1, title: 'Apple iPhone 13 Pro Max 256Gb (небесно-голубой)', avatar: 'https://cdn-icons-png.flaticon.com/512/147/147144.png', statusItem: false},
          {id: 2, title: '500 Stars', avatar: 'https://cdn-icons-png.flaticon.com/512/147/147144.png', statusItem: false},
          {id: 3, title: 'Sony PlayStation 5 Digital Edition  ', avatar: 'https://cdn-icons-png.flaticon.com/512/147/147144.png', statusItem: false}
        ]
      });
    
      const toggleActive = (index) => {
        setActiveIndex(index);
      };
    
      useEffect(() => {
        if (timeLeft <= 0) {
          setActiveIndex(null);
          setTimeLeft(5 * 60);
        }
      }, [setActiveIndex, setTimeLeft, timeLeft]);
    
      const toggleActiveStyles = (index) => {
        if (index === activeIndex) {
          return 'current__events__hot-price disabled';
        } else {
          return 'current__events__hot-price';
        }
      };
    
      const toggleActiveStylesBtns = (index) => {
        if (index === activeIndex) {
          return 'current__events__btn-green disabled';
        } else {
          return 'current__events__btn-green';
        }
      };
    
      return (
        <>
          <div className='current__events__wrapper'>
            {appState.objects.map((item, index) => 
              <div className="current__events__hot-price__item" key={index}>
                <div className={toggleActiveStyles(activeIndex)}>
                  <h5 className="current__events__card-title__large">Hot Price</h5>
                </div>
                <div className="current__events__image">
                  <img src={item.avatar} alt='user' className="rounded-circle" width='75' height='75'/>
                </div>
                <div className="current__events__info">
                  <h4 className="current__events__title__middle">{item.title}</h4>
                </div>
                        
                <div className="current__events__timer">
                  <span>{minutes}</span>
                  <span>:</span>
                  <span>{seconds}</span>
                </div>  
                        
                <button
                  className={toggleActiveStylesBtns(activeIndex)}
                  onClick={() => toggleActive(index)}
                  disabled={activeIndex === index}
                >
                  СДЕЛАТЬ ХОД
                </button>   
              </div>
            )}
          </div>
        </>
      );
    }
    
    export default CurrentEventsItem