[React.js] useInput

폼의 다른 입력을 처리할 때 `use-form-hook` 모듈을 많이 사용했기 때문에 컴포넌트를 직접 제어하는 ​​방법을 잊었습니다.

그래서 오랜만에 다시 입력을 처리해주는 커스텀 훅을 만들어보려고 합니다!

짜잔

const useInput = (
  initialValue: string
): {
  ref: MutableRefObject<HTMLInputElement>;
  value: string;
  isDirty: boolean;
  onChange: (e: ChangeEvent<HTMLInputElement>) => void;
  onReset: () => void;
  onFocus: () => void;
} => {
  const ref = useRef() as MutableRefObject<HTMLInputElement>;
  const (value, setValue) = useState(initialValue);
  const (isDirty, setDirty) = useState(false);

  const handler = (e: ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value);
  };

  const onReset = () => setValue(initialValue);

  const onFocus = () => {
    if (ref.current) {
      ref.current.focus();
    }
  };

  useEffect(() => {
    setDirty(!
!
value.length); }, (value)); return { ref, value, isDirty, onChange: handler, onReset, onFocus }; }; export default useInput;

제네릭은 `useInput`의 인자인 `initialValue`로 서로 다른 타입을 입력할 수 있다는 전제하에 사용할 수 있지만 대부분의 텍스트(문자열)를 처리하기 때문에 타입은 `문자열`로 제한됩니다.

다양한 방식으로 대응하고 싶다면 제네릭 의약품 사용,

import { Dispatch, SetStateAction, useCallback, useState, ChangeEvent } from 'react';

type ReturnTypes<T> = (T, (e: ChangeEvent<HTMLInputElement>) => void, Dispatch<SetStateAction<T>>);

const useInput = <T>(initialData: T): ReturnTypes<T> => {
  const (value, setValue) = useState(initialData);
  const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
    setValue((e.target.value as unknown) as T);
  }, ());
  return (value, onChange, setValue);
};

export default useInput;

내가 그것을 바꿀 수 있는지 궁금합니다.

입력 값을 처리할 때 DOM에 직접 접근하여 값을 추출하느냐, React 컴포넌트에서 값을 조정해서 추출하느냐에 따라 비제어 컴포넌트(전자)와 제어 컴포넌트(후자)로 나눌 수 있다.

여기서 ref에 대한 직접 접근은 입력된 DOM(onFocus 메소드)에 집중하기 위해서만 사용되었고, DOM이 자율적으로 접근할 수 있도록 ref가 반환되었습니다.

값(`value`)을 추적하고 값이 변경될 때 특정 setter(`setDirty`)를 실행하기 위해 `useEffect`를 함께 사용했습니다.

이렇게 생성된 `useInput` hook을 원하는 뷰 페이지에 호출하여 사용하면,

import { FC, useState, useEffect, useCallback, SyntheticEvent, useRef, MutableRefObject } from 'react';
import { useUI } from '../ui/context';
import Input from '../ui/Input/Input';
import useInput from '../../lib/hooks/useInput';
import { Button } from '../ui';

const LoginView: FC = () => {
  const {
    ref: emailRef,
    value: email,
    onChange: onEmailChange,
    onReset: onEmailReset,
    onFocus: onEmailFocus,
  } = useInput('');
  const { value: password, onChange: onPasswordChange, onReset: onPasswordReset } = useInput('');
  const (disabled, setDisabled) = useState(true);
  
  const handleLogin = async (e: SyntheticEvent<HTMLFormElement>) => {
    e.preventDefault();

    try {
      console.log({ email, password });
      onEmailReset();
      onPasswordReset();
    } catch ({ errors }: any) {
      ...
    } finally {
      setDisabled(true);
      onEmailFocus();
    }
  };

  const handleValidation = useCallback(async () => {
    const validEmail = /(a-z0-9)+@/.test(email);
    const validPassword = /(A-Za-z0-9._%+-){8,}/.test(password);
    
    setDisabled(!
validEmail || !
validPassword); }, (email, password)); useEffect(() => { handleValidation(); }, (handleValidation)); return ( <div> <h2>로그인</h2> <form onSubmit={handleLogin} ref={root}> <Input ref={emailRef} value={email} onChange={onEmailChange} /> <Input value={password} onChange={onPasswordChange} /> <Button type="submit" disabled={disabled} loading={loading}> 로그인 </Button> </form> </div> ); }; export default LoginView;

읽기 쉽고 여러 입력을 관리하기 위해 여러 번 재사용할 수 있습니다!

입력 타입이 파일일 때 아쉬운 점이 있다면 다른 로직이 필요하므로 useFiles`라는 커스텀 훅으로 여러 파일을 비동기적으로 병렬로 관리할 수 있는 방식으로 개발해야 할 것 같습니다.

나중에!

끝.