[๐Ÿšจ Error ๐Ÿšจ] addEventListener ์ฝœ๋ฐฑ ํ•จ์ˆ˜ state ๊ฐ’ ์ฐธ์กฐ ์—๋Ÿฌ

์šฐํ˜ยท2024๋…„ 3์›” 22์ผ

๐Ÿšจ Error ๐Ÿšจ

๋ชฉ๋ก ๋ณด๊ธฐ
4/11

๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ๋ฐฐ๊ฒฝ

์นดํ…Œ๊ณ ๋ฆฌ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด๊ฐ€ ์—ด๋ฆฌ๊ณ  ๋‹ค์‹œ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด๋ฅผ ๋‹ซ๊ณ  ์‹ถ์—ˆ์ง€๋งŒ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์•˜๋‹ค.

  • ๋ฌธ์ œ ์ƒํ™ฉ
  • ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๋™์ž‘

์ „์ฒด ์ฝ”๋“œ

import { useEffect, useState } from 'react'
import styles from './Category.module.css'
import TECH_IMAGE from '../../utils/constant/techImage'
import ARROW_ICON from '../../utils/constant/arrowIcon'

const ALL = 'ALL'
const CATEGORY = 'category'
const CATEGORY_ARRAY = [
  'ALL',
  'HTML',
  'CSS',
  'JAVASCRIPT',
  'REACT',
  'TYPESCRIPT',
  'NEXT.JS',
  'GIT',
  'CS'
]
function Category() {
  const [isOpen, setIsOpen] = useState(false)
  const [currentCategory, setCurrentCategory] = useState(ALL)

  useEffect(() => {
    document.addEventListener('click', handleClick)

    return () => {
      document.removeEventListener('click', handleClick)
    }
  }, [])

  const handleClick = (e) => {
    if (e.target.dataset.status === CATEGORY) {
      setIsOpen(!isOpen)
      return
    }
    setIsOpen(false)
  }

  const handleCurrentCategory = (e) => {
    setCurrentCategory(e.target.textContent)
  }

  return (
    <>
      <div className={styles.category}>
        <div data-status={CATEGORY} className={styles.buttonBox}>
          <button data-status={CATEGORY} className={styles.button}>
            <img
              className={styles.categoryIcon}
              src={TECH_IMAGE[currentCategory]}
              alt={`์นดํ…Œ๊ณ ๋ฆฌ ${currentCategory} ์•„์ด์ฝ˜`}
            />
            {currentCategory}
          </button>
          <img
            data-status={CATEGORY}
            className={styles.arrowIcon}
            src={ARROW_ICON[isOpen]}
            alt="์นดํ…Œ๊ณ ๋ฆฌ ํ† ๊ธ€ ๋ฒ„ํŠผ"
          />
        </div>
        {isOpen && (
          <ul className={styles.listBox}>
            {CATEGORY_ARRAY.map((category, idx) => {
              return (
                <li
                  onClick={(e) => handleCurrentCategory(e)}
                  key={idx}
                  className={styles.list}
                >
                  <img
                    className={styles.categoryIcon}
                    src={TECH_IMAGE[category]}
                    alt={`์นดํ…Œ๊ณ ๋ฆฌ ${category} ์•„์ด์ฝ˜`}
                  />
                  {category}
                </li>
              )
            })}
          </ul>
        )}
      </div>
    </>
  )
}

export default Category

๋ฌธ์ œ์˜ ์›์ธ ์•Œ์•„๋ณด๊ธฐ

์•„๋ž˜ ์ฝ”๋“œ์™€ ๊ฐ™์ด isOpen์„ ์ฝ˜์†”์— ์ถœ๋ ฅํ•ด๋ณด๋ฉด ๋“œ๋กญ๋‹ค์šด์ด ์—ด๋ ค์žˆ์Œ์—๋„ false๋กœ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ๋‹ค.

 useEffect(() => {
    document.addEventListener('click', handleClick)

    return () => {
      document.removeEventListener('click', handleClick)
    }
  }, [])
                                                                               
const handleClick = (e) => {
  	console.log(isOpen)
    if (e.target.dataset.status === CATEGORY) {
      setIsOpen(!isOpen)
      return
    }
    setIsOpen(false)
}

๋“œ๋กญ๋‹ค์šด์ด ์—ด๋ ค์žˆ์œผ๋ฉด isOpen์ด true์ƒํƒœ์ธ๋ฐ ์™œ false๊ฐ€ ์ถœ๋ ฅ๋ ๊นŒ?

์ด ๋ฌธ์ œ๋Š” JavaScript์˜ ํด๋กœ์ €(Closure) ๊ฐœ๋…๊ณผ ๊ด€๋ จ์ด ์žˆ๋‹ค.

ํด๋กœ์ €(Closure)๋ž€?
ํ•จ์ˆ˜๊ฐ€ ์ƒ์„ค๋  ๋•Œ ํ•ด๋‹น ํ•จ์ˆ˜๊ฐ€ ์†ํ•œ ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ์„ ๊ธฐ์–ตํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ค‘์š”ํ•œ ์ ์€ ํ•จ์ˆ˜๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ ์™ธ๋ถ€ ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ๊ธฐ์–ตํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

addEventListener์— ๋“ฑ๋ก๋œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๊ฐ€ ์ตœ์ดˆ์— state ๋ณ€์ˆ˜์˜ ๊ฐ’์„ ์ฐธ์กฐํ•  ๋•Œ์˜ ๊ฐ’์„ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ๊ณ 
์ด ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋Š” ์ดํ›„์—๋„ state์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์•Œ์ง€ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฆฌ๋ Œ๋”๋ง ํ›„์—๋„ ์ด์ „ ์ฐธ์กฐ ๊ฐ’์„ ์œ ์ง€ํ•œ๋‹ค.

๋‚ด ์ฝ”๋“œ์—์„œ isOpen์€ false๋กœ ์ดˆ๊ธฐ ๊ฐ’์„ ์„ค์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— handleClickํ•จ์ˆ˜์—์„œ isOpen์˜ ์ฐธ์กฐ ๊ฐ’์€ ์–ธ์ œ๋‚˜ false๋กœ ๊ธฐ์–ต๋˜์–ด์„œ setIsOpen(!isOpen)์„ ํ•ด๋„ ๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด๊ฐ€ false๊ฐ€ ๋˜์ง€ ์•Š์•˜๋˜ ๊ฒƒ์ด๋‹ค.


๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

1. useEffect๋ฅผ ๋ Œ๋”๋ง ํ•  ๋•Œ ๋งˆ๋‹ค ์‹คํ–‰
โ†ช ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ Œ๋”๋ง์ด ๋  ๋•Œ ๋งˆ๋‹ค useEffect๋ฅผ ์‹คํ–‰ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— state์˜ ๊ฐ’์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋‹ค.

// ๋ Œ๋”๋ง ๋  ๋•Œ ๋งˆ๋‹ค ์‹คํ–‰
 useEffect(() => {
    document.addEventListener('click', handleClick)

    return () => {
      document.removeEventListener('click', handleClick)
    }
  })
                                                                               
const handleClick = (e) => {
  	console.log(isOpen)
    if (e.target.dataset.status === CATEGORY) {
      setIsOpen(!isOpen)
      return
    }
    setIsOpen(false)
}

2. useEffect์˜ dependency array(์˜์กด์„ฑ ๋ฐฐ์—ด)์— state ๊ฐ’ ์ถ”๊ฐ€ํ•˜๊ธฐ
โ†ช ์ด ๋ฐฉ๋ฒ•๋„ 1๋ฒˆ ๋ฐฉ๋ฒ•๊ณผ ๋น„์Šทํ•˜๊ฒŒ state ๊ฐ’์ด ๋ณ€ํ•  ๋•Œ ๋งˆ๋‹ค useEffect๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๋งค๋ฒˆ ์ตœ์‹  state ๊ฐ’์„ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

// isOpen(state) ๊ฐ’์ด ๋ณ€ํ•  ๋•Œ ๋งˆ๋‹ค ์‹คํ–‰
 useEffect(() => {
    document.addEventListener('click', handleClick)

    return () => {
      document.removeEventListener('click', handleClick)
    }
  }, [isOpen])
                                                                               
const handleClick = (e) => {
  	console.log(isOpen)
    if (e.target.dataset.status === CATEGORY) {
      setIsOpen(!isOpen)
      return
    }
    setIsOpen(false)
}

3. setterํ•จ์ˆ˜์˜ ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ ์‚ฌ์šฉ
โ†ช ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด์ „ ์ƒํƒœ ๊ฐ’์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„์™€์„œ ์ƒˆ๋กœ์šด ์ƒํƒœ ๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

 useEffect(() => {
    document.addEventListener('click', handleClick)

    return () => {
      document.removeEventListener('click', handleClick)
    }
  }, [])
                                                                               
const handleClick = (e) => {
  	console.log(isOpen)
    if (e.target.dataset.status === CATEGORY) {
      // ์ด์ „ state ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ
      setIsOpen((prev) => !prev)
      return
    }
    setIsOpen(false)
}

๋‚ด๊ฐ€ ์„ ํƒํ•œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๋‚˜๋Š” 2๋ฒˆ ๋ฐฉ๋ฒ•์ด ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์ด๋‚˜ ์˜๋„ ํŒŒ์•…์ด ์ž˜ ๋œ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์—ˆ๋Š”๋ฐ ๋ฉ˜ํ† ๋‹˜๊ป˜ ์—ฌ์ญค๋ณด๋‹ˆ๊น 2๋ฒˆ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋„ ๊ดœ์ฐฎ์ง€๋งŒ 3๋ฒˆ ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ •์„ฑ์ด๋‚˜ ๋ฉ”๋ชจ๋ฆฌ์ ์ธ ์ธก๋ฉด์—์„œ ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ๋ง์”€ํ•ด์ฃผ์…”์„œ 3๋ฒˆ ๋ฐฉ๋ฒ•์„ ํ†ตํ•ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์˜€๋‹ค!


๐Ÿ™ƒ ๋„์›€์ด ๋˜์—ˆ๋˜ ์ž๋ฃŒ

Web: Event์™€ addEventListner ์•Œ์•„๋ณด๊ธฐ (๊ฐœ๋…, React์—์„œ ์ฃผ์˜ํ•  ์ )
โ†ช ๐Ÿ‘ ์ฝ”๋“œ์ž‡ ์Šคํ”„๋ฆฐํŠธ ๊ฐ™์€ ํŒ€์ธ ๋ด‰์ฐฌ๋‹˜์˜ ์ถ”์ฒœ

0๊ฐœ์˜ ๋Œ“๊ธ€