๐Ÿช [React] react-hook-form์— ๋Œ€ํ•œ ์–•์€ ๊ณ ์ฐฐ


FilterDropdown์„ ๋งŒ๋“ค๋˜ ์ค‘์˜ ์ƒ๊ฐ ๋ณ€ํ™”์˜ ํ๋ฆ„

๊ฐœ์š”

์ฐธ๊ณ ) FilterDropdown ์ปดํฌ๋„ŒํŠธ๋Š” input ๋‘ ๊ฐœ์™€ checkbox ํ•˜๋‚˜๋ฅผ ๊ฐ€์ง„ ๋“œ๋กญ๋‹ค์šด ์ปดํฌ๋„ŒํŠธ์ด๋‹ค.

filter

  1. ์ฒ˜์Œ์—๋Š” ๊ทธ๋ƒฅ ๋‹ค ๋•Œ๋ ค๋ฐ•์•„์„œ ๋งŒ๋“ค์—ˆ๋‹ค๊ฐ€
  2. ์ง„๋„์œจ input์„ ์ปดํฌ๋„ŒํŠธ๋กœ ๋นผ์ž (invalid ํ”ผ๋“œ๋ฐฑ ๋“ฑ์„ ํ•จ๊ป˜ ๊ด€๋ฆฌํ•˜๋ ค๊ณ )
  3. ์ด๋Ÿฌ๋‹ˆ๊นŒ ๊ฐ’์ด๋ž‘ ์—๋Ÿฌ๋ฅผ ๋‹ค props๋กœ ๋ฐ›์•„์™€์•ผํ•˜๋„ค
  4. ์ ๋‹นํžˆ ๋ฆฌํŒฉํ† ๋ง
  5. ๊ธฐํšŒ ๋˜๋ฉด input ๊ด€๋ จ valid ์ฒดํฌํ•˜๋Š”๊ฑฐ๋ž‘ ๋‹ค ์ปค์Šคํ…€ ํ›…์œผ๋กœ ์ œ๊ณตํ•ด์•ผ์ง€
  6. ์–ด? ๊ทธ๊ฒŒ ๋ฐ”๋กœ react-hook-form..?

ํ•ด์„œ ๋ฆฌํŒฉํ† ๋ง์„ ์ง„ํ–‰ํ•˜๊ฒŒ ๋œ ๊ฒƒ์ด์—ˆ๋‹ค.

๋ณธ๋ก 

๊ทธ๋ž˜์„œ,

์—ด์‹ฌํžˆ react-hook-form์œผ๋กœ ๊ฐˆ์•„์น˜์› ๋‹ค.

Custom Validation with dependency

์•„๋ž˜์ฒ˜๋Ÿผ ์ง„๋„์œจ ๊ตฌ๊ฐ„ ๊ด€๋ จํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋กœ์ง์ด ํ•„์š”ํ–ˆ๋‹ค. validate๋ผ๋Š” ํ•ญ๋ชฉ ์ถ”๊ฐ€๋กœ ๊ฐ€๋Šฅํ–ˆ๋Š”๋ฐ, ์—๋Ÿฌ ๋ฉ”์„ธ์ง€ ๋„˜๊ฒจ์ฃผ๋Š” ๋ถ€๋ถ„์—์„œ๋„ ์‚ด์ง ์• ๋ฅผ ๋จน์—ˆ์—ˆ๋‹ค. (๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ž˜ ์ฐพ์•„๋ณด๋ฉด ์นœ์ ˆํ•˜๊ฒŒ ๋‚˜์™€์žˆ๋Š”๋ฐ ์ž˜ ์•ˆ์ฐพ์•„๋ณธ ์ž์˜ ์ตœํ›„..)

filter-error

// ๋ณดํ†ต์€ ์ด๋ ‡๊ฒŒ,
{
	max: {
		value: 100,
		message: '100๋ณด๋‹ค ์ž‘์€ ์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.'
	}
}

// ๊ทผ๋ฐ ์ปค์Šคํ…€ํ•˜๊ฒŒ ์ฃผ๋Š” ๊ฒฝ์šฐ๋Š” ์ด๋ ‡๊ฒŒ..!
{
	validate: (v) => (v < max) || '์—๋Ÿฌ ๋ฉ”์„ธ์ง€...'
}

input์ด ๋ณ€๊ฒฝ ๋  ๋•Œ๋งˆ๋‹ค ์žฌ๊ฒ€์‚ฌ๊ฐ€ ํ•„์š”ํ•œ๊ฑธ?

๊ทธ๋ž˜, ์ด๊ฑด ํ•ด๊ฒฐํ–ˆ๋Š”๋ฐโ€ฆ

์ง„๋„์œจ ๊ตฌ๊ฐ„ ๊ด€๋ จ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋Š” ์‹œ์ž‘ ๊ตฌ๊ฐ„(min)์—๋งŒ ์ถ”๊ฐ€ํ•˜๋˜, ๋ ๊ตฌ๊ฐ„(max)์ด ๋ณ€๊ฒฝ๋˜๋ฉด ๊ฒ€์‚ฌ๋ฅผ ๋‹ค์‹œ ํ•ด์ฃผ์–ด์•ผ ํ–ˆ๋‹ค. ([20, 10]์—์„œ [20, 100]์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด invalid๋ฅผ ๋นผ์ฃผ์–ด์•ผํ•˜๋‹ˆ๊นŒ)

๊ทธ๋ž˜์„œ ์ƒ๊ฐํ•œ ๋ฐฉ๋ฒ•์€ validate๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ณ  setErrors๋ฅผ ํ†ตํ•ด min, max ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค error๋ฅผ set, reset ํ•ด์ฃผ๊ธฐ์˜€๋‹ค.

useEffect(() => {
		if (inputKey === MIN) {
			if (minValue > maxValue) {
				setError(MIN, {
					type: 'range',
					message: '์ง„๋„์œจ ์‹œ์ž‘ ๊ตฌ๊ฐ„์€ ๋ ๊ตฌ๊ฐ„๋ณด๋‹ค ํฌ์ง€ ์•Š๊ฒŒ ์„ค์ •ํ•ด ์ฃผ์„ธ์š”.',
				});
			} else if (minValue < 100) {
				clearErrors(MIN);
			}
		}
	}, [minValue, maxValue]);

์•„ ์ง€๊ธˆ ์ƒ๊ฐ์—” ์ € ์œ ํšจ์„ฑ ๋ฌธ๊ตฌ๋ฅผ min, max ๋‘˜ ๋‹ค์—๊ฒŒ ์ฃผ๊ณ  ์œ ํšจ์„ฑ ๊ฒ€์‚ฌํ•  ๋•Œ๋„ v๊ฐ€ ์•„๋‹ˆ๋ผ min, max๋กœ ํ–ˆ์œผ๋ฉด..?

๊ทธ๋Ÿฐ๋ฐโ€ฆ hook-form-valid

validate ์ฒดํฌ๊ฐ€ input์ด ๋ณ€ํ•  ๋•Œ ๋งˆ๋‹ค ์‹œํ–‰ ํ•˜๋Š”๊ฒŒ ์•„๋‹Œ๊ฐ€๋ณด๋‹ค..?

์•„๋ž˜์ฒ˜๋Ÿผ validate callback ๋‚ด๋ถ€์— console์„ โ€˜innerโ€™๋กœ ์ฐ์–ด๋ณด์•˜๋Š”๋ฐ ๋งค๋ฒˆ ์ฐํžˆ์ง€๊ฐ€ ์•Š์Œ.

validate๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ ๋˜๋Š” ์‹œ์ (max: 100์ด๋ฉด input์ด 100์ด ๋˜๋Š” ๊ทธ ์‹œ์ )์—๋งŒ ๋ถˆ๋ฆฌ๊ณ , ํ™•์ธํ•˜๋Š” ๋ฐฉ์‹์ธ ๊ฒƒ ๊ฐ™์Œ(๊ทธ ์™ธ์—๋Š” ์œ ์ง€ - ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๊ฐ€ ๊ณ„์† ๋ณด์ด๊ฑฐ๋‚˜, ์‚ฌ๋ผ์ง„ ์ฑ„๋กœ)

๋”ฐ๋ผ์„œ ๋‚˜๋Š” ์ปค์Šคํ…€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋กœ, watch๋กœ ๋ณ€๊ฒฝ๋˜๊ณ  ์žˆ๋Š” min ์ด max ๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์€์ง€ ํ™•์ธํ•˜๋„๋ก ํ–ˆ์œผ๋‹ˆ๊นŒ, ์ฒ˜์Œ์œผ๋กœ min > max ๊ฐ€ ๋˜๋Š” ๊ทธ ์‹œ์ ์— ํ•œ ๋ฒˆ ๋ถˆ๋ ค์„œ ์—๋Ÿฌ๊ฐ€ ์ฐํžˆ๊ณ , ๊ทธ ์ดํ›„์—๋Š” ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๊ฐ€ ์‚ฌ๋ผ์ง€๋Š” ๋“ฏ?

๋ง์„ ๊ต‰์žฅํžˆ ์ •์‹  ์—†๊ฒŒ ์จ๋‘์—ˆ๋Š”๋ฐ, ํ•œ ๋งˆ๋””๋กœ ์ง€๊ธˆ ๋‚ด๊ฐ€ ์งœ๋‘” ์ฝ”๋“œ์—์„œ๋Š” input ๊ฐ’์ด ๋งค๋ฒˆ ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค๊ฐ€ ํฌ์ธํŠธ. ๋”ฐ๋ผ์„œ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๋™์ž‘์„ ๋งŒ๋“ค์–ด๋‚ด๊ธฐ ์œ„ํ•ด์„œ๋Š” ์–ด์ฉ” ์ˆ˜ ์—†์ด useEffect ๋“ฑ์œผ๋กœ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ๊ฒ€์‚ฌ๋ฅผ ๋”ฐ๋กœ ์ˆ˜ํ–‰ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

reValidateMode: 'onChangeโ€™ ๋ผ๋Š” ๊ฑธ ์ฐพ์•˜๋Š”๋ฐ, ์ด๊ฒƒ๋„ ์™„์ „ํžˆ ๋‚ด๊ฐ€ ์ƒ๊ฐํ•œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค.. ใ…œ (์ฝ”๋“œ ์กฐ๊ฐ์€ ์•„๋ž˜์—)

{...register(inputKey, {
						max: {
							value: 100,
							message: '100 ์ดํ•˜์˜ ์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.',
						},
						validate: {
							range: () => {
								console.log('inner', minValue, maxValue);

								return (
									minValue <= maxValue ||
									'์ง„๋„์œจ ์‹œ์ž‘ ๊ตฌ๊ฐ„์€ ๋ ๊ตฌ๊ฐ„๋ณด๋‹ค ํฌ์ง€ ์•Š๊ฒŒ ์„ค์ •ํ•ด ์ฃผ์„ธ์š”.'
								);
							},
						},
						setValueAs: (v) => (v !== '' ? Number.parseInt(v, 10) : ''),
					})
}

์ดํ•ด ์•ˆ๋˜๋Š” ๊ฑฐ ํ•˜๋‚˜ ๋˜ ๋ฐœ๊ฒฌํ–ˆ๋‹ค.

์™œ isValid ๋Š” true ์ธ๋ฐ errors๋Š” ๋‚จ์•„์žˆ์ง€? (isValid ๊ฐ€ ๋งž๋Š” ์ƒํ™ฉ์ด๋‹ค.) hook-form-error-console

๋™์˜์ƒ ์ฒจ๋ถ€๋ฅผ ๋ชปํ–ˆ๋Š”๋ฐ, ์ƒํ™ฉ์„ ์ •๋ฆฌํ•˜์ž๋ฉด errors๊ฐ€ ํ•œ ๋ฐ•์ž ๋Š๋ฆฌ๊ฒŒ ๋ Œ๋”๋ง ๋˜๊ณ  ์žˆ์—ˆ๋‹ค. (๋‹ค๋ฅธ ์š”์†Œ์— ์˜ํ•ด ๋ Œ๋”๋ง์ด ํ•œ ๋ฒˆ ๋” ์ผ์–ด๋‚˜๋ฉด ๋งž๋Š” ๊ฐ’..)

triger

trigger-issue trigger

์•„๋ฌด๋ž˜๋„ trigger๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•  ๊ฒƒ ๊ฐ™์ง€?

์˜์กด์„ฑ์ด ์žˆ๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋Š” ๋‚ด๊ฐ€ ์œ„์—์„œ ์‹œ๋„ํ–ˆ๋˜ ์ฝ”๋“œ๋“ค๋กœ๋“  ์ œ๋Œ€๋กœ ๋™์ž‘ ์•ˆํ•˜๋Š”๊ฒŒ ๋งž์•˜๋‹ค. (์ข€ ๊ณ ์ณ์ฃผ์ง€.. ์™œ ์ด๋ ‡๊ฒŒ ๋”ฐ๋กœ trigger๋ฅผ ๋นผ๋†จ์„๊นŒ? ๋ Œ๋”๋ง ํผํฌ๋จผ์Šค ๋•Œ๋ฌธ์ธ๊ฐ€?)

๋ฌดํŠผ ์•„๋ž˜์™€ ๊ฐ™์ด ํ•ด๊ฒฐํ–ˆ๋‹ค. ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž์ถ”์–ด, ๋ฒ”์œ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋Š” min์—๋งŒ ํ”ผ๋“œ๋ฐฑ ๋ฌธ๊ตฌ๋ฅผ ๋„์šฐ๋„๋ก ํ•ด์•ผํ•˜๋ฏ€๋กœ validate๋„ min์ผ ๊ฒฝ์šฐ์—๋งŒ ์ถ”๊ฐ€ํ•ด์ฃผ๊ณ ,

useEffect๋ฅผ ํ†ตํ•ด max ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ๋งˆ๋‹ค min์„ trigger ํ•ด์ค€๋‹ค.

useEffect(() => {
		trigger(MIN);
	}, [maxValue]);

...

{...register(inputKey, {
						max: {
							value: 100,
							message: '100 ์ดํ•˜์˜ ์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.',
						},
						...(inputKey === MIN && {validate: {
							range: () =>
								minValue <= maxValue ||
								'์ง„๋„์œจ ์‹œ์ž‘ ๊ตฌ๊ฐ„์€ ๋ ๊ตฌ๊ฐ„๋ณด๋‹ค ํฌ์ง€ ์•Š๊ฒŒ ์„ค์ •ํ•ด ์ฃผ์„ธ์š”.',
						}}),
						setValueAs: (v) => (v !== '' ? Number.parseInt(v, 10) : ''),
					})}

๊ทธ ์™ธ ํ–ˆ๋˜ ๊ณ ๋ฏผ๋“ค

  • ํ•˜๋‚˜์˜ ํ•„๋“œ ์•ˆ์— ๋™์‹œ์— ๋‘๊ฐœ์˜ ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋ฅผ ๋„์šธ ์ˆœ ์—†๋‚˜?
    • ์— ๋Œ€ํ•ด์„  ์•„์ง ๋‹ต์„ ์ฐพ์ง€ ๋ชปํ–ˆ๋‹ค.
  • ์–ด๋– ํ•œ ์ด์œ ์—์„œ์ธ์ง€ input์— ์ž…๋ ฅํ•œ ๊ฐ’๋“ค์ด ๋”ฐ๋กœ state๋กœ ๊ด€๋ฆฌ๋˜์–ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์—ˆ์Œ
    • ๋‚ด๊ฐ€ ์ ์šฉํ•˜๊ณ  reset()์„ ํ˜ธ์ถœํ•˜๋Š” ๋ฐ”๋žŒ์— ๊ฐ’์ด ๋‹ค ์—‰๋ง์ง„์ฐฝ์ด ๋˜์–ด์„œ ๊ทธ๋žฌ์—ˆ๋‹คโ€ฆ
  • ์ ์šฉํ•˜๊ณ , reset ํ•˜๊ณ , ๊ฐ๊ฐ์˜ input์„ ์ž…๋ ฅ, ๋ณ€๊ฒฝํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ฌธ์ œ๊ฐ€ ์—†๋˜ ๋ถ€๋ถ„์ด ๋™์ž‘ํ•˜์ง€ ์•Š๊ฒŒ ๋˜๋ฉด์„œ ๊ธฐ์กด ์ฝ”๋“œ๋ฅผ ์œ ์ง€ํ•ด์•ผ๊ฒ ๋‹ค๋Š” ํŒ๋‹จ์„ ๋‚ด๋ ธ๋‹ค.
    • ์ž‘์—…์„ ๋นจ๋ฆฌ ๋งˆ๋ฌด๋ฆฌํ•ด์•ผํ•œ๋‹ค๋Š” ์•ฝ๊ฐ„์˜ ์••๋ฐ•๊ฐ๋„ ์žˆ์—ˆ๋‹ค.
    • ์ง€๊ธˆ ์ƒ๊ฐํ•ด๋ณด๋ฉด ๋‚ด๊ฐ€ ์ž˜๋ชป ์ฝ”๋”ฉํ–ˆ์—ˆ๋‹ค..
  • ๋˜ํ•œ ๋‹น์‹œ์—๋Š”, react-hook-form์„ ์ ์šฉํ•˜๊ฒŒ ๋˜๋ฉด์„œ ๋น„์ฆˆ๋‹ˆ์Šค ์š”๊ตฌ์‚ฌํ•ญ์„ ๋งŒ์กฑ์‹œํ‚ค๊ธฐ ์œ„ํ•œ ๋‹ค๋ฅธ ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ๋“ค์ด ๋” ์ถ”๊ฐ€๋˜์—ˆ๋‹ค๋Š” ์ƒ๊ฐ์— ์“ฐ์ง€ ๋ง์•„์•ผ๊ฒ ๋‹ค๋Š” ํŒ๋‹จ๋„ ํ–ˆ์—ˆ๋‹ค.
    • ex. react-query๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋” ๋งŽ์€ ์ฝ”๋“œ๋“ค
    • ๊ทผ๋ฐ, ์œ ์ง€ ๋ณด์ˆ˜ ์ธก๋ฉด์—์„œ ๋ชจ๋‘๊ฐ€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ๋„ ๊ฐ„๊ณผํ•ด์„œ๋Š” ์•ˆ๋˜๊ฒ ๋‹ค.
      • ์š”๊ตฌ์‚ฌํ•ญ์„ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ๋”์šฑ ์ถ”๊ฐ€์ ์ธ ์ฝ”๋“œ๋“ค vs ๋ชจ๋‘๊ฐ€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋น„๋™๊ธฐ ํŒจ์นญ์„ ํ•ด์˜จ๋‹ค (๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๊ฐ€ ์œ ์ง€๋ณด์ˆ˜ ํ•˜๋Ÿฌ ์™”์„ ๋•Œ ์ดˆ๊ธฐ ์ดํ•ด๊ฐ€ ์‰ฝ๋‹ค)
    • ๋˜ ํ•˜๋‚˜์˜ ์ƒ๊ฐ, ๊ผญ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์•ˆ์—์„œ ํ•ด๊ฒฐํ•  ํ•„์š”๋Š” ์—†๋‹ค?
      • ์—ฌ๊ธฐ์— ๋Œ€ํ•ด์„œ๋Š” ๋‚ด ์ƒ๊ฐ์— ์•„์ง ํ™•์‹ ์ด ์—†๊ธด ํ•œ๋ฐ, ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ๋„ ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ†ตํ•ด์„œ ๊น”๋”ํ•˜๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์—†๋Š” ๋ถ€๋ถ„์€ ์ž์ฒด์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ๋„ ๋ฐฉ๋ฒ•์ด๋ผ๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.
      • ๋˜ ์ด๋ฒˆ์—” ๊ทธ๋ ‡๊ฒŒ ํ•ด๊ฒฐํ–ˆ๋‹ค.

์ตœ์ข… ๊ตฌ์กฐ

trigger๋ฅผ ์“ฐ๋ฉด์„œ ๋งˆ๋ฌด๋ฆฌํ•˜๊ฒŒ๋  ์ค„ ์•Œ์•˜์œผ๋‚˜, ํ•ด๋‹น ๋กœ์ง์ด ์ถ”๊ฐ€๋˜๋ฉด์„œ ์ฒ˜์Œ ์„ค๊ณ„ํ–ˆ๋˜ ์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐ๊ฐ€ ๊ต‰์žฅํžˆ ๋ณต์žกํ•˜๊ฒŒ ๋ณ€ํ•ด๋ฒ„๋ ธ๋‹ค. ํผ๊ณผ ์ง„๋„์œจ ์ž…๋ ฅํ•˜๋Š” ์ธํ’‹์„ ๋ถ„๋ฆฌํ•ด์„œ ์„ค๊ณ„ํ–ˆ์—ˆ๋Š”๋ฐ, ์–‘์ชฝ ์ปดํฌ๋„ŒํŠธ์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ์ƒํƒœ ๋ณ€ํ™”์— ๋”ฐ๋ฅธ ๋‹ค๋ฅธ ์ƒํƒœ๊ฐ’ ๋ณ€๊ฒฝ ๋“ฑ์˜ ๋กœ์ง์ด ๋ถ„์‚ฐ๋˜์–ด ๋ฒ„๋ ค์„œ ๊ฐ ์ปดํฌ๋„ŒํŠธ์˜ ์—ญํ• ์ด ๋ชจํ˜ธํ•ด์ง€๊ณ  ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ์ดํ•ดํ•˜๊ธฐ๊ฐ€ ์–ด๋ ค์›Œ์ง„ ๊ฒƒ์ด๋‹ค.

  • react-hook-form์„ ์‚ฌ์šฉํ•˜๋˜, errors์™€ isValid๋Š”ย FilterDropdown์—์„œ ์ƒํƒœ๋กœ์จ ๊ด€๋ฆฌํ•œ๋‹ค.
    • ํ˜„ ๊ตฌ์กฐ์—์„œ๋Š” ๊ฐ’์ด ๋ณ€๊ฒฝ๋จ์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ input๋“ค์ด ์˜์กด์„ฑ์„ ๊ฐ€์ง€๊ณ  ์–ฝํ˜€์žˆ์–ด react-hook-form์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ด์ ์ด ์—†๋‹ค๊ณ  ํŒ๋‹จ
  • ์ด์— ๋”ฐ๋ผย ProgressInput์˜ ์—ญํ• ์„ defaultValue๋ฅผ ๋ฐ›์•„์™€ form์— register ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋‹จ์ˆœํ™”
    • ํ•„์š”ํ•œ ๋ฉ”์†Œ๋“œ๊ฐ€ register ํ•˜๋‚˜ ๋ฟ์ด๋ผ์„œย useFormContext๋ฅผ ์“ฐ์ง€ ์•Š๋Š” ํŽธ์ด ์ฝ”๋“œ๊ฐ€ ๋” ๊ฐ„๊ฒฐํ•  ๊ฒƒ ๊ฐ™์•„ ์ œ๊ฑฐ

FilterDropdown

์ž‘์„ฑํ•œ ์ฝ”๋“œ ์ค‘์—์„œ, ์ปจ์…‰์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ์ž˜๋ผ์˜ด

import React, { useState, useEffect } from 'react';
import { useForm, useWatch } from 'react-hook-form';

import { checkValidation, checkIsValid } from '../../utils/filterUtils';

const ProgressInput = ({ defaultValue, inputId, register, errors = [] }) => {
	const isValid = errors.length === 0;

	return (
		<div className="w-100">
			<input
				defaultValue={defaultValue}
				id={inputId}
				{...register(inputId, {
					setValueAs: (v) => (v !== '' ? Number.parseInt(v, 10) : ''),
				})}
			/>
		</div>
	);
};

function FilterDropdown() {
	const [errors, setErrors] = useState({});
	const [isValid, setIsValid] = useState(true);

	const { register, handleSubmit, reset, setValue, control } = useForm({
		mode: 'onChange',
		defaultValues: {
			minValue: minProgress ?? 0,
			maxValue: maxProgress ?? 100,
			showMaxPercent: minProgress === 100 && maxProgress === 100,
		},
		reValidateMode: 'onChange',
	});

	const min = useWatch({ control, name: MIN });
	const max = useWatch({ control, name: MAX });
	const showMax = useWatch({ control, name: SHOW_MAX });

	useEffect(() => {
		if (showMax) {
			setValue(MIN, 100);
			setValue(MAX, 100);
		} else {
			setValue(MIN, min);
			setValue(MAX, max);
		}
	}, [showMax]);

	useEffect(() => {
		// iunput์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ž์ฒด์ ์œผ๋กœ ๊ตฌํ˜„ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ˆ˜ํ–‰
		// ์œ„์—์„œ๋„ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด, input 3๊ฐœ๊ฐ€ ๋ชจ๋‘ ๊นŠ์€ ์˜์กด์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Œ
		// react-hook-form์—์„œ ๋‹จ์ผ input์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋Š” ์šฉ์ดํ•˜๋‚˜, ์œ„์™€ ๊ฐ™์€ ์ƒํ™ฉ์—์„  ๋งž์ง€ ์•Š๋‹ค๊ณ  ํŒ๋‹จ
		setErrors((prev) => ({
			...prev,
			[MIN]: checkValidation(min, max, errors[MIN]),
			[MAX]: checkValidation(max, max, errors[MAX]),
		}));

		if (min === 100 && max === 100) {
			setValue(SHOW_MAX, true);
		} else {
			setValue(SHOW_MAX, false);
		}
	}, [min, max]);

	useEffect(() => {
		setIsValid(checkIsValid(errors));
	}, [errors]);

	return (
		<Dropdown isOpen={isOpen} toggle={toggle}>
			<DropdownToggle>
				...
			</DropdownToggle>

			<DropdownMenu>
					<Form	id="progress-filter-form" onSubmit={handleSubmit(handleApply)}>
						<ProgressInput
							defaultValue={min}
							inputId={MIN}
							register={register}
							errors={errors[MIN]}
						/>
						<ProgressInput
							defaultValue={max}
							inputId={MAX}
							register={register}
							errors={errors[MAX]}
						/>

						<Label className="ml-3" check>
							<input
								className="form-check-input"
								name={SHOW_MAX}
								type="checkbox"
								{...register(SHOW_MAX)}
							/>
							<div className="ml-2">์ง„๋„์œจ 100%๋งŒ ๋ณด๊ธฐ</div>
						</Label>
					</Form>
					...
			</DropdownMenu>
		</Dropdown>
	);
}

export default FilterDropdown;

utils

import _find from 'lodash/find';
import _remove from 'lodash/remove';

/**
 * @param {*} value Number
 * @param {*} maxValue Number
 * @param {*} errorMessages Array of {type: '', message: ''}
 * @returns Array of {type: '', message: ''}
 */
const ERROR_TYPE_MIN = 'min';
const ERROR_TYPE_MAX = 'max';
const ERROR_TYPE_RANGE = 'range';
export const checkValidation = (value, maxValue, errors = []) => {
	const currentErrors = errors;

	const hasMinError = _find(currentErrors, { type: ERROR_TYPE_MIN });
	if (value < 0) {
		!hasMinError &&
			currentErrors.push({
				type: ERROR_TYPE_MIN,
				message: '0 ์ด์ƒ์˜ ์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.',
			});
	} else {
		_remove(currentErrors, { type: ERROR_TYPE_MIN });
	}

	const hasMaxError = _find(currentErrors, { type: ERROR_TYPE_MAX });
	if (value > 100) {
		!hasMaxError &&
			currentErrors.push({
				type: ERROR_TYPE_MAX,
				message: '100 ์ดํ•˜์˜ ์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.',
			});
	} else {
		_remove(currentErrors, { type: ERROR_TYPE_MAX });
	}
	if (value > maxValue) {
		const hasRangeError = _find(currentErrors, { type: ERROR_TYPE_RANGE });
		!hasRangeError &&
			currentErrors.push({
				type: ERROR_TYPE_RANGE,
				message: '์ง„๋„์œจ ์‹œ์ž‘ ๊ตฌ๊ฐ„์€ ๋ ๊ตฌ๊ฐ„๋ณด๋‹ค ํฌ์ง€ ์•Š๊ฒŒ ์„ค์ •ํ•ด ์ฃผ์„ธ์š”.',
			});
	} else {
		_remove(currentErrors, { type: ERROR_TYPE_RANGE });
	}

	return currentErrors;
};

/**
 * @param {*} errors Object
 * 	{
 * 		min: [ {type: '', message: ''}, ... ],
 * 		max: [ {type: '', message: ''}, ... ],
 *  	...
 *  }
 * @returns Boolean
 */
export const checkIsValid = (errors = {}) => {
	const allErrors = Object.values(errors);
	const hasError =
		allErrors.filter((eachErrors) => eachErrors.length > 0).length > 0;

	return !hasError;
};

์–ป์€ ์ด์ 

  • setValueAs: (v) => (v !== '' ? Number.parseInt(v, 10) : '')ย ๋ฅผ ํ†ตํ•ด ์ž…๋ ฅ๊ฐ’์ด ์ˆซ์ž์ž„์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋ฉด์„œ input value์ด string์ผ ๊ฒฝ์šฐ๋„ ๊ณ ๋ คํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€๋œ ์ฝ”๋“œ๋“ค์„ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๊ฒฐ๋ก 

์ƒˆ๋กœ์šด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ฒ˜์Œ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์‹ค์ œ๋กœ ์ ์šฉ๋œ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉฐ ์‚ฌ์šฉ๋ฒ•์„ ๋น ๋ฅด๊ฒŒ ์ตํžˆ๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•˜์ง€๋งŒ, ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ •๋…ํ•˜๋Š” ์‹œ๊ฐ„์ด ๊ผญ ํ•„์š”ํ•˜๋‹ค. ๋˜, ๊ผญ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ†ตํ•ด์„œ๋งŒ ๋ชจ๋“  ๊ฒƒ์„ ํ•ด๊ฒฐํ•˜๋ ค๊ณ  ํ•˜์ง€ ๋ง์ž. ๊ทธ์ € ํ•˜๋‚˜์˜ ํŽธ๋ฆฌํ•œ ๋„๊ตฌ์ผ ๋ฟ์ด๋‹ค.

  • ์ง€๊ธˆ ๋‚˜ํ•œํ…Œ ์™„์ „ํžˆ ํ•„์š”ํ•œ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด, ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ์—†๋Š” ๋ถ€๋ถ„์€ ๋‚ด๊ฐ€ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ƒ๊ฐ

๊ทธ๋ฆฌ๊ณ , ํ•ญ์ƒ ์ƒ๊ฐํ•ด์•ผ ํ•  ๊ฒƒ

  • ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์–ด๋–ค ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‚˜์˜จ ๊ฒƒ์ธ๊ฐ€?
  • ์ง€๊ธˆ ๋‚ด๊ฐ€ ํ•ด๊ฒฐํ•˜๊ณ ์ž ํ•˜๋Š” ๋ฌธ์ œ์— ๋Œ€ํ•œ ์ ํ•ฉํ•œ ํ•ด๊ฒฐ์ฑ…์ธ๊ฐ€?

์œ„ ์งˆ๋ฌธ์„ ๊ผญ ๋จผ์ € ํ•ด๋ณด๋„๋ก ํ•˜์ž. ์ด์ œ๋Š” ๋‹จ์ˆœํžˆ ๋น ๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์–ด ๋‚ด๋Š” ๊ฒƒ ์— ๊ทธ์น˜๋ฉด ์•ˆ๋˜๊ณ , ์œ ์ง€ ๋ณด์ˆ˜๊ฐ€ ์‰ฌ์šด ์ฝ”๋“œ, ์ ๊ฒŒ ์งœ์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ์ข‹์€ ๊ตฌ์กฐ์˜ ์ฝ”๋“œ๋ฅผ ์ƒ๊ฐํ•ด๋‚ด์•ผ ํ•œ๋‹ค.




# ์นดํ…Œ๊ณ ๋ฆฌ