π 리ν©ν λ§
νλ‘ν νμ μ λ§λ€κ³ , νλ‘ν νμ μ λν 리λλ―Έ μμ±κΉμ§λ λλ¬λ€. νμ§λ§ κΈνκ² μ§ννλ€ λ³΄λ μμ§ μλ΄μΌ ν κ³³μ΄ λ§μ΄ 보μλ€. μ£Όλ³μ νΌλλ°±μ λ°μμ κ³ μ³μΌ ν λΆλΆλ€μ μμ νκ³ μμ κ³Όμ μ λ΄μλ³΄λ € νλ€.
π λλ¦° 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 λ΄μ€κΈ°μ¬]
μ§κΈκΉμ§ ν΄μλ λ°©μμ κΈ°λ³Έμ ν¬λ‘€λ§μ μ΄μ©ν΄ λ°μ΄ν°λ₯Ό μλνν΄μ μ 리νλ λ°©μμ΄μλ€. μμ μ κ³μνλ©΄μ μλνλΌλ λ°©μμ΄ μ’κΈ°λ νμ§λ§ ν¬λ‘€λ§ μ체λ₯Ό μ ννκ² νλ λ°©λ²μ νκ³λ₯Ό λλΌκΈ°λ νκ³ , νλ«νΌμ μλ νμ¬λ€λ μ 리ν μ μκ² νκ³ μΆκΈ°λ νλ€.
μ΄λ² κ³κΈ°λ‘ λ€λ₯Έ λ°©ν₯μΌλ‘ μλΉμ€λ₯Ό μμ ν΄μΌ νκ³ , λ³΄λ€ μ¬μ©μκ° μ½κ² μ 리ν μ μλ λ°©λ²μ΄ μ΄λ€ κ² μμκΉ λΌλ κ³ λ―Όκ³Ό μ±μ© νλ«νΌ μμ₯μ λν μ‘°μ¬κ° λΆμ‘±νλ€λ κ²λ λκΌλ€. νλ‘μ νΈλ₯Ό μ΄λ»κ² μμ ν μ§ μμΌλ‘ μ΄λ»κ² λ°μ μμΌλκ°μ§ μ’ λ κ³ λ―Όν΄μ μμ μ΄ νμνλ€.