λͺ¨μœΌμž‘-check box μˆ˜μ •, react-query μ»€μŠ€ν…€ ν›…, 크둀링 이슈

@choi2021 Β· December 03, 2022 Β· 10 min read

πŸ›  λ¦¬νŒ©ν† λ§

ν”„λ‘œν† νƒ€μž…μ„ λ§Œλ“€κ³ , ν”„λ‘œν† νƒ€μž…μ— λŒ€ν•œ λ¦¬λ“œλ―Έ μž‘μ„±κΉŒμ§€λŠ” 끝났닀. ν•˜μ§€λ§Œ κΈ‰ν•˜κ²Œ μ§„ν–‰ν•˜λ‹€ λ³΄λ‹ˆ 아직 손봐야 ν•  곳이 많이 λ³΄μ˜€λ‹€. 주변에 ν”Όλ“œλ°±μ„ λ°›μ•„μ„œ 고쳐야 ν•  뢀뢄듀을 μˆ˜μ •ν•˜κ³  μˆ˜μ • 과정을 담아보렀 ν•œλ‹€.

πŸŽƒ 느린 check λ°•μŠ€

checkbox κΈ°λŠ₯은 ν•΄λ‹Ή 곡고의 자격 쑰건과 μš°λŒ€ 사항에 μ–Όλ§ˆλ‚˜ ν•΄λ‹Ήλ˜λŠ”μ§€ κΈ°λ‘ν•˜κ³ , 50%이상일 경우 합격 κ°€λŠ₯성이 높은 κ³΅κ³ μ΄λ―€λ‘œ λ”°λ‘œ ν‘œμ‹œν•˜κΈ° μœ„ν•΄ μΆ”κ°€ν•œ κΈ°λŠ₯μ΄μ—ˆλ‹€. 각 체크 μƒνƒœλ₯Ό λ°˜μ˜ν•˜κΈ° μœ„ν•΄μ„œ 직접 λ°μ΄ν„°λ² μ΄μŠ€μ— μ—…λ°μ΄νŠΈκ°€ 될 수 있게 λ‘œμ§μ„ κ΅¬μ„±ν–ˆλ‹€. λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯된 μƒνƒœλ₯Ό λ°›κ³  react-query둜 UI와 μƒνƒœλ₯Ό 동기화 μ‹œν‚€λŠ” λ°©μ‹μœΌλ‘œ μ§œμ„œ, λ³„λ„μ˜ μƒνƒœλ₯Ό 두지 μ•ŠμœΌλ € λ…Έλ ₯ν–ˆλ‹€. ν•˜μ§€λ§Œ μ£Όλ³€μ˜ ν”Όλ“œλ°±μ„ λ°›μ•˜μ„ λ•Œ ν΄λ¦­ν•˜κ³  μ•½ 0.5초 뒀에 λ°˜μ‘ν•΄μ„œ 닡닡함을 λŠκΌˆλ‹€λŠ” 의견이 μžˆμ—ˆλ‹€. 이λ₯Ό ν•΄κ²° μœ„ν•œ 과정을 정리해 보렀 ν•œλ‹€.

μš°μ„  handleChange의 λ‚΄λΆ€ 둜직이 같은 둜직이 μ€‘λ³΅λ˜λŠ” λ¬Έμ œκ°€ μžˆμ–΄ 쀑볡을 μ œκ±°ν•˜λ € ν–ˆλ‹€. kind 자체λ₯Ό key둜 μ „λ‹¬ν•΄μ„œ ν•΄λ‹Ήν•˜λŠ” 값에 λ§žλŠ” 배열을 μˆ˜μ •ν•˜λŠ” λ°©μ‹μœΌλ‘œ λ³€κ²½ν–ˆλ‹€. ν•˜λŠ” 과정에 kind νƒ€μž… 자체의 문제둜 μΈν•΄μ„œ λ‚΄λΆ€ 곳곳에 μˆ˜μ •μ„ ν–ˆμ§€λ§Œ, μ „λ°˜μ μœΌλ‘œ 훨씬 깔끔해진 κ²°κ³Όλ₯Ό 얻을 수 μžˆμ—ˆλ‹€.

// μˆ˜μ • μ „

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const { name } = e.currentTarget
  let modifiedJob
  if (job) {
    if (kind === Kinds.qualification) {
      const qualification = [...job?.qualification].map(item => {
        if (item.text === name) {
          return { ...item, checked: !item.checked }
        }
        return item
      })
      modifiedJob = { ...job, qualification }
    } else {
      const preferential = [...job?.preferential].map(item => {
        if (item.text === name) {
          return { ...item, checked: !item.checked }
        }
        return item
      })
      modifiedJob = { ...job, preferential }
    }
    mutate(calculateChecks(modifiedJob))
  }
}

// μˆ˜μ • ν›„
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const { name } = e.currentTarget
  if (job && !isMainJob) {
    const targetList = [...job[kind]].map(item => {
      if (item.text === name) {
        return { ...item, checked: !item.checked }
      }
      return item
    })
    const modifiedJob = { ...job, [kind]: targetList }
    mutate(calculateChecks(modifiedJob))
  }
}

λ‹€μŒμœΌλ‘œλŠ” 메인 μ΄μŠˆμ˜€λ˜ 느린 μ²΄ν¬λ°•μŠ€ λ°˜μ‘μ„ ν•΄κ²°ν•˜κΈ° μœ„ν•΄μ„œ UIλ₯Ό μœ„ν•œ μƒνƒœλ₯Ό λ”°λ‘œ λ†”λ‘¬μ„œ UI μƒνƒœμ™€ DBλ₯Ό λ™μ‹œμ— μ—…λ°μ΄νŠΈν•˜λŠ” λ°©μ‹μœΌλ‘œ μ‚¬μš©μžκ°€ λŠλ‚„ 수 μžˆλŠ” 닡닡함을 ν•΄κ²°ν•  수 μžˆμ—ˆλ‹€. ν•˜μ§€λ§Œ 쑰금 더 λ‚˜μ•„κ°€μ„œ, μ΄λ ‡κ²Œ UIμƒνƒœλ‘œ κ΄€λ¦¬ν•œλ‹€λ©΄ DBλ₯Ό μ—…λ°μ΄νŠΈν•˜λŠ” 횟수λ₯Ό νŽ˜μ΄μ§€λ₯Ό λ– λ‚  λ•Œ ν•΄μ„œ API 호좜 λΉ„μš©μ„ μ€„μ΄λŠ” λ°©ν–₯으둜 κ°œμ„ ν•˜λ©΄ μ–΄λ–¨κΉŒλΌλŠ” μΆ”ν›„ λ°©ν–₯도 κ³ λ―Όν–ˆλ‹€.

export default function DescriptionItem({
  text,
  isMainJob,
  checked,
  kind,
}: DescriptionItemProps) {
  const [isChecked, setIsChecked] = useState(checked)
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name } = e.currentTarget
    if (job && !isMainJob) {
      const targetList = [...job[kind]].map(item => {
        if (item.text === name) {
          return { ...item, checked: !item.checked }
        }
        return item
      })
      const modifiedJob = { ...job, [kind]: targetList }
      mutate(calculateChecks(modifiedJob))
    }
    setIsChecked(!isChecked)
  }
  return (
    <S.Wrapper>
      {isMainJob && <RiCheckboxBlankCircleFill />}
      {!isMainJob && (
        <input
          type="checkbox"
          name={text}
          checked={isChecked}
          onChange={handleChange}
        />
      )}
      <span>{text}</span>
    </S.Wrapper>
  )
}

Custom Hook

react-queryλ₯Ό μ΄μš©ν•΄ μ „μ—­ μƒνƒœλ‘œ μΊμ‰¬λœ μ„œλ²„λ°μ΄ν„°λ₯Ό νŽΈν•˜κ²Œ λ°›μ•„μ˜¬ 수 μžˆμ§€λ§Œ, 데이터λ₯Ό 뢈러였기 μœ„ν•œ 둜직이 κ³„μ†ν•΄μ„œ 반볡되기 λ•Œλ¬Έμ— λ”°λ‘œ custom hook으둜 λΆ„λ¦¬ν•˜λŠ” 게 μ–΄λ–¨κΉŒλΌλŠ” 생각에 λ”°λ‘œ λͺ¨μ•„λ‘κΈ°λ‘œ ν–ˆλ‹€. λΆˆλŸ¬μ˜€λŠ” ν‚€λ₯Ό μƒμˆ˜λ‘œ μ •ν•΄μ„œ μ•ˆμ „ν•˜κ²Œ 관리가 κ°€λŠ₯ν–ˆκ³ , μ»΄ν¬λ„ŒνŠΈ λ‚΄λΆ€ 둜직이 κΉ”λ”ν•΄μ Έμ„œ μ’‹μ•˜λ‹€. ν•˜μ§€λ§Œ μ•„μ‰¬μš΄ 점은 hook은 hookλ‚΄λΆ€μ—μ„œλ§Œ 정리할 수 있기 λ•Œλ¬Έμ— hookμ•ˆμ—μ„œ dbServiceλ‚˜ useRouter와 같은 λ°˜λ³΅λ˜λŠ” λ‘œμ§μ€ μ–΄λ–»κ²Œ λ‹€μ‹œ 정리할 수 μžˆμ„κΉŒ 고민도 λ˜μ—ˆλ‹€. 이후에 μ’€ 더 λ¦¬νŒ©ν† λ§μ΄ ν•„μš”ν•˜λ‹€.

const JOBS_KEY = "jobs"

export const useGetJobs = () => {
  const dbService = useDBService()
  const { data: jobs, isLoading } = useQuery([JOBS_KEY], () =>
    dbService.getJobs()
  )
  return { jobs, isLoading }
}

export const useGetFilteredJobs = () => {
  const { query } = useRouter()
  const { id } = query
  const dbService = useDBService()
  const { data: jobs, isLoading } = useQuery(
    [JOBS_KEY],
    () => dbService.getJobs(),
    {
      select: (data: ModifiedJobsType) => {
        return Object.values(data).filter(item => item.id !== id)
      },
    }
  )
  return { jobs, isLoading }
}

//binding이 κ°•ν•΄μ„œ 쒀더 연결성을 λ–¨μ–΄λœ¨λ €μ•Ό μž¬μ‚¬μš© κ°€λŠ₯
export const useCreateJob = (
  setMessage: React.Dispatch<React.SetStateAction<string>>,
  setUrl: React.Dispatch<React.SetStateAction<string>>
) => {
  const dbService = useDBService()
  const queryClient = useQueryClient()
  const { mutate, isLoading } = useMutation(
    async (url: string) => {
      const { data } = await axios.post(
        `${process.env.NEXT_PUBLIC_HOST}/api/job`,
        {
          url,
        }
      )
      const job = modifyJob(data)
      dbService.addJob(job)
    },
    {
      onSuccess: () => {
        queryClient.invalidateQueries([JOBS_KEY])
        setMessage("")
      },
      onError: error => {
        if (error instanceof AxiosError) {
          const { response } = error
          if (response) {
            setMessage(response.data.message)
          }
        }
      },
      onSettled: () => {
        setUrl("")
      },
    }
  )
  return { mutate, isLoading }
}

export const useUpdateJob = () => {
  const dbService = useDBService()
  const queryClient = useQueryClient()
  const { mutate } = useMutation(
    async (job: ModifiedJobType) => {
      return dbService.updateJob(job)
    },
    {
      onSuccess: () => {
        queryClient.invalidateQueries(["jobs"])
      },
      onError: error => {
        if (error instanceof AxiosError) {
          const { response } = error
          if (response) {
            console.log(response)
          }
        }
      },
    }
  )
  return mutate
}

export const useDeleteJob = () => {
  const queryClient = useQueryClient()
  const dbService = useDBService()
  const { mutate } = useMutation(
    async (job: ModifiedJobType) => {
      return dbService.removeJob(job)
    },
    {
      onSuccess: () => {
        queryClient.invalidateQueries(["jobs"])
      },
      onError: error => {
        if (error instanceof AxiosError) {
          const { response } = error
          if (response) {
            console.log(response)
          }
        }
      },
    }
  )
  return mutate
}

export const useGetJobById = () => {
  const { query } = useRouter()
  const dbService = useDBService()
  const { id } = query
  const jobId = typeof id === "string" ? id : id?.join() || ""
  const { data, isLoading } = useQuery(
    [JOBS_KEY],
    () => {
      return dbService.getJobs()
    },
    {
      select: (data: ModifiedJobsType) => {
        return data[jobId]
      },
    }
  )
  return { data, isLoading }
}

πŸ˜₯ 크둀링의 문제

이전에 크둀링에 λŒ€ν•΄ μ‘°μ‚¬ν•˜λ©΄μ„œ μ €μž‘κΆŒμ˜ λ¬Έμ œκ°€ 생길 수 μžˆλ‹€λŠ” 것을 μΈμ§€ν•˜κ³ λŠ” μžˆμ—ˆλ‹€. 웹에 μ‘΄μž¬ν•˜λŠ” 정보λ₯Ό λ°›μ•„μ˜€κ³  그것을 μ •μ œν•˜λŠ” λ°©λ²•μ΄μ§€λ§Œ 데이터가 μž¬μ‚°μΈ κΈ°μ—…λ“€μ˜ μž…μž₯μ—μ„œλŠ” μ˜ˆλ―Όν•˜κ³  μ€‘μš”ν•œ λ¬Έμ œμž„μ€ λΆ„λͺ…ν–ˆλ‹€. μ΄λ²ˆμ— μ›ν‹°λ“œμ—μ„œ μ±„μš©κ³΅κ³ λ“€μ„ λ³΄λ©΄μ„œ μ±„μš©κ³΅κ³  μ•„λž˜ λ‹€μŒκ³Ό 같은 문ꡬ가 μ ν˜€μžˆλŠ” 것을 ν™•μΈν–ˆλ‹€.

μ›ν‹°λ“œ
μ›ν‹°λ“œ
μ‹œμž‘ 전에도 ν•™μŠ΅ μš©λ„λ‘œ λ§Œλ“€λ €λŠ” μ„œλΉ„μŠ€μ˜€κΈ°μ— 크게 λ¬Έμ œκ°€ λ˜μ§€ μ•Šμ„ 것이라 μƒκ°ν•˜κ³  μ œμž‘ν–ˆμ§€λ§Œ, λ¨Όμ € 확인이 ν•„μš”ν•  것 κ°™μ•„ μ›ν‹°λ“œ 츑에 직접 λ¬Έμ˜ν•΄λ³΄μ•˜λ‹€. 문의 κ²°κ³ΌλŠ” 크둀링 자체λ₯Ό κΈˆμ§€ν•˜κ³  μžˆλ‹€λŠ” 닡변을 받을 수 μžˆμ—ˆλ‹€.

μ›ν‹°λ“œ λ‹΅λ³€
μ›ν‹°λ“œ λ‹΅λ³€
μ‹€μ œλ‘œ 크둀링은 μ €μž‘κΆŒ 문제둜 λ§Žμ€ 법적 곡방이 μ§„ν–‰ 쀑이닀. μ±„μš©κ³΅κ³  ν”Œλž«νΌ μ‚¬μ΄μ—μ„œ 법적 곡방도 μžˆμ—ˆλ‹€λŠ” 것을 μ°Ύμ•„λ³΄λ©΄μ„œ, μ–΄λ–€ λ°©ν–₯으둜 μˆ˜μ •ν•˜λ©΄ μ’‹μ„κΉŒ λΌλŠ” 고민이 λ˜μ—ˆλ‹€.

[μ‹€μ œ 법적 곡방에 λŒ€ν•œ ZDNET Korea λ‰΄μŠ€κΈ°μ‚¬]

법적 곡방
법적 곡방

μ§€κΈˆκΉŒμ§€ ν•΄μ™”λ˜ λ°©μ‹μ˜ 기본은 크둀링을 μ΄μš©ν•΄ 데이터λ₯Ό μžλ™ν™”ν•΄μ„œ μ •λ¦¬ν•˜λŠ” λ°©μ‹μ΄μ—ˆλ‹€. μž‘μ—…μ„ κ³„μ†ν•˜λ©΄μ„œ μžλ™ν™”λΌλŠ” 방식이 μ’‹κΈ°λŠ” ν•˜μ§€λ§Œ 크둀링 자체λ₯Ό μ •ν™•ν•˜κ²Œ ν•˜λŠ” λ°©λ²•μ˜ ν•œκ³„λ₯Ό λŠλΌκΈ°λ„ ν–ˆκ³ , ν”Œλž«νΌμ— μ—†λŠ” νšŒμ‚¬λ“€λ„ 정리할 수 있게 ν•˜κ³  싢기도 ν–ˆλ‹€.

이번 κ³„κΈ°λ‘œ λ‹€λ₯Έ λ°©ν–₯으둜 μ„œλΉ„μŠ€λ₯Ό μˆ˜μ •ν•΄μ•Ό ν–ˆκ³ , 보닀 μ‚¬μš©μžκ°€ μ‰½κ²Œ 정리할 수 μžˆλŠ” 방법이 μ–΄λ–€ 게 μžˆμ„κΉŒ λΌλŠ” κ³ λ―Όκ³Ό μ±„μš© ν”Œλž«νΌ μ‹œμž₯에 λŒ€ν•œ 쑰사가 λΆ€μ‘±ν–ˆλ‹€λŠ” 것도 λŠκΌˆλ‹€. ν”„λ‘œμ νŠΈλ₯Ό μ–΄λ–»κ²Œ μˆ˜μ •ν•  μ§€ μ•žμœΌλ‘œ μ–΄λ–»κ²Œ λ°œμ „μ‹œμΌœλ‚˜κ°ˆμ§€ μ’€ 더 κ³ λ―Όν•΄μ„œ μˆ˜μ •μ΄ ν•„μš”ν•˜λ‹€.

@choi2021
맀일의 μ‹œν–‰μ°©μ˜€λ₯Ό κΈ°λ‘ν•˜λŠ” κ°œλ°œμΌμ§€μž…λ‹ˆλ‹€.