본문 바로가기

배워서 따라하는 포플/포켓몬 도감 앱

상세 페이지 생성하기(2) - Damage Relations

Damage Relations 컴포넌트 생성하기

import React, {useEffect, useState} from 'react'

const DamageRelations = ({damages}) => {

  const [damagePokemonForm, setDamagePokemonForm] = useState()  

  useEffect(() => {
    const arrayDamage = damages.map((damage) => separateObjectBetweenToAndFrom(damage))

    // type이 2개인지 1개인지 구분해서 데이터 가공
    if(arrayDamage.length === 2) {
      const obj = joinDamageRelations(arrayDamage)
      setDamagePokemonForm(reduceDuplicateValues(postDamageValue(obj.from)))
    } else{
      setDamagePokemonForm(postDamageValue(arrayDamage[0].from))
    }
  }, [damages])
  
  const joinDamageRelations = (props) => {
    return {
      to: joinObjects(props, 'to'),
      from: joinObjects(props, 'from'),
    }
  }

  const reduceDuplicateValues = (props) => {
    const duplicateValues = {
      double_damage: '4x',
      half_damage: '1/4x',
      no_damage: '0x'
    }

    return Object.entries(props)
      .reduce((acc, [keyName, value]) => {
        const key = keyName
        const verifiedValue = filterForUniqueValues(
          value,
          duplicateValues[key]
        )

        return (acc = { [keyName]: verifiedValue, ...acc})
      }, {})
  }

  const filterForUniqueValues = (valueForFiltering, damageValue) => {
    return valueForFiltering.reduce((acc, currentValue) => {
      const {url, name} = currentValue

      const filterACC = acc.filter((a) => a.name !== name)

      return filterACC.length === acc.length
        ? (acc = [currentValue, ...acc])
        : (acc = [{damageValue: damageValue, name, url}, ...filterACC])
    }, [])
  }

  const joinObjects = (props, string) => {
    const key = string
    const firstArrayValue = props[0][key]
    const secondArrayValue = props[1][key]

    const result = Object.entries(secondArrayValue)
      .reduce((acc, [keyName, value]) => {
        const result = firstArrayValue[keyName]?.concat(value)
        return (acc =  {[keyName]: result, ...acc})
      }, {})
    
    return result
  }

  const postDamageValue = (props) => {
    const result = Object.entries(props)
      .reduce((acc, [keyName, value]) => {

        const valuesOfKeyName = {
          double_damage: '2x',
          half_damage: '1/2x',
          no_damange: '0x'
        }

        return (acc = {
          [keyName]: value.map(i => ({
            damageValue: valuesOfKeyName[keyName],
            ...i
          })),
          ...acc
        })
      }, {})

    return result
  }
  
  // 데미지 from과 to 분리
  const separateObjectBetweenToAndFrom = (damage) => {
    const from = filterDamageRelations('_from', damage)
    const to = filterDamageRelations('_to', damage)
    return {from, to}
  }

  const filterDamageRelations = (valueFilter, damage) => {
    const result = Object.entries(damage)
      .filter(([keyName, value]) => {
        return keyName.includes(valueFilter)
      })
      .reduce((acc, [keyName, value]) => {
        const keyWithValueFilterRemove = keyName.replace(valueFilter, '')
        return (acc = {[keyWithValueFilterRemove]: value, ...acc})
      }, {})
    return result
  } 

  return (
    <div>DamageRelations</div>
  )
}

export default DamageRelations

src폴더 안에 components폴더 안에 DamageRelations.jsx파일입니다.

 

Damage Relations UI 생성하기

return (
    <div className='flex gap-2 flex-col'>
      {damagePokemonForm ? (
        <>
          {Object.entries(damagePokemonForm)
            .map(([keyName, value]) => {
              const key = keyName
              const valuesOfKeyName = {
                double_damage: 'Weak',
                half_damage: 'Resistant',
                no_damage: 'Immune'
              }

              return (
                <div key={key}>
                  <h3 className='capitalize font-medium text-sm md:text-base text-slate-500 text-center'>
                    {valuesOfKeyName[key]}
                  </h3>
                  <div className="flex flex-wrap gap-1 justify-center">
                    {value.length > 0 ? (
                      value.map(({name, url, damageValue}) => {
                        return (
                          <Type type={name} key={url} damageValue={damageValue} />
                        )
                      })
                    ) : (
                      <Type type={'none'} key={'none'} />
                    )
                    }
                  </div>
                </div>
              )
            })}
        </>
      ) : <div></div>
      }
    </div>
  )

src폴더 안에 components폴더 안에 DamageRelations.jsx파일입니다.

 

Damage Relations 모달 생성하기

import React from 'react'
import DamageRelations from './DamageRelations'

const DamageModal = ({setIsModalOpen, damages}) => {
  return (
    <div className='flex items-center justify-center z-40 fixed left-0 bottom-0 w-full h-full bg-gray-800'>
      <div className="modal bg-white rounded-lg w-1/2">
        <div className="flex flex-col items-start p-4">
          <div className="flex items-center w-full justify-between">
            <div className="text-gray-900 font-medium text-lg">
              데미지 관계
            </div>
            <span 
              onClick={() => setIsModalOpen(false)}
              className="text-gray-900 font-medium text-lg cursor-pointer">
              X
            </span>
          </div>

          <DamageRelations damages={damages} />
        </div>
      </div>
    </div>
  )
}

export default DamageModal

src폴더 안에 components폴더 안에 DamageModal.jsx파일입니다.

const [isModalOpen, setIsModalOpen] = useState(false)

src폴더 안에 pages폴더 안에 DetailPage폴더 안에 index.jsx파일에 모달창에 관한 useState를 만듭니다.

 

onClick={() => setIsModalOpen(true)}

이미지에 클릭할 시 modal이 true로 되며 오픈할 수 있겠끔 작업합니다.

{isModalOpen && <DamageModal setIsModalOpen={setIsModalOpen} damages={pokemon.DamageRelations} />}

index.jsx UI부분에 모달창을 띄울 수 있게 작업합니다.

 

모달창 외부 클릭시 모달창 닫게 만드는 Custom Hooks생성하기

import { useEffect } from "react";

export default function useOnClickOutside(ref, handler) {
  useEffect(() => {
    const listener = (event) => {
      // 모달 안에 클릭 시 닫지 않고 유지
      if(!ref.current || ref.current.contains(event.target)){
        return
      }
      // 모달 밖을 클릭 시 닫게 해줌
      handler()
    }

    document.addEventListener('mousedown', listener)

    return () => {
      document.removeEventListener('mousedown', listener)
    }
  }, [])
  
}

src폴더 안에 hooks폴더 안에 useOnClickOutside.js파일의 내용입니다.

src폴더 안에 components폴더 안에 DamageModal.jsx파일 추가 내용입니다.