FrontEnd/[리액트 2부] 고급 주제와 훅

[4.1장 레프 훅][4.2장 레프 훅]

Tony Lim 2024. 3. 19. 13:20
  function useRef(initialValue) {
    if (!isInitialized[cursor]) {
      memorizedStates[cursor] = { current: initialValue };
      isInitialized[cursor] = true;
    }
    const memorizedState = memorizedStates[cursor];
    cursor = cursor + 1;
    return memorizedState;
  }

closure를 사용해서 useRef를 사용하는 function componenet가 re render되더라도 주어진 값을 보존할 수 있도록 하였다.

https://www.inflearn.com/questions/1211434/4-1%EC%9E%A5-%EB%A0%88%ED%94%84-%ED%9B%85-useref%EA%B4%80%EB%A0%A8-%EC%A7%88%EB%AC%B8%EC%9D%B4%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4

 

[4.1장 레프 훅] useRef관련 질문이있습니다. - 인프런

안녕하세요 선생님 본 강의 예시에서import MyReact from './lib/MyReact';import React from 'react';export default () => { const ref1 = MyReact.useRef(1); const ref2 ...

www.inflearn.com


const LoginForm = () => {
  const [values, setValues] = React.useState({
    email: "",
    password: "",
  });
  const [errors, setErrors] = React.useState({
    email: "",
    password: "",
  });
  const [touched, setTouched] = React.useState({
    email: false,
    password: false,
  });

  const validate = (values) => {
    const errors = {
      email: "",
      password: "",
    };

    if (!values.email) {
      errors.email = "이메일을 입력하세요.";
    }
    if (!values.password) {
      errors.password = "비밀번호를 입력하세요.";
    }

    return errors;
  };

  const handleChange = (e) => {
    console.log(e.target.name);
    console.log([e.target.name]);
    setValues({
      ...values,
      [e.target.name]: e.target.value,
    });
  };

  const handleBlur = (e) => {
    console.log("handleBlur", e.target.name);
    console.log("handleBlur", [e.target.name]);
    console.log("handleBlur ", touched);
    console.log("handleBlur errors ", errors);
    setTouched({
      ...touched,
      [e.target.name]: true,
    });
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    const nextTouched = {
      email: true,
      password: true,
    };

    setTouched(nextTouched);

    const errors = validate(values);
    console.log(errors);
    setErrors(errors);

    if (Object.values(errors).some(Boolean)) return;

    console.log("Submitted", values);
  };

  React.useEffect(() => {
    console.log("UseEffect called");
    setErrors(validate(values));
  }, [values]);

  return (
    <form noValidate onSubmit={handleSubmit}>
      <input
        type="text"
        name="email"
        placeholder="Email"
        autoFocus
        value={values.email}
        onChange={handleChange}
        onBlur={handleBlur}
      ></input>
      {touched.email && errors.email && <span>{errors.email}</span>}
      <input
        type="password"
        name="password"
        placeholder="password"
        value={values.password}
        onChange={handleChange}
        onBlur={handleBlur}
      ></input>
      {touched.password && errors.password && <span>{errors.password}</span>}
      <button>Login</button>
    </form>
  );
};

export default LoginForm;

브라우저에서 컨트롤하게 냅두지 않고 statae와 event handler를 통해서 react가 제어하는 component로 변경하여 다룬 예제이다.

blur, onchange, onsubmit 등 브라우저에서 발행되는 이벤트들을 react에서 handling함으로서 좀 더 섬세하게 다룰 수 있게 된다.

export const useForm = ({ initialValues, validate, onSubmit }) => {
  const [values, setValues] = React.useState({ initialValues });
  const [errors, setErrors] = React.useState({});
  const [touched, setTouched] = React.useState({});

  const handleChange = (e) => {
    const nextValues = {
      ...values,
      [e.target.name]: e.target.value,
    };
    setValues(nextValues);
  };

  const handleBlur = (e) => {
    const nextTouched = {
      ...touched,
      [e.target.name]: true,
    };
    setTouched(nextTouched);
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    const nextTouched = Object.keys(values).reduce((touched, filed) => {
      touched[filed] = true;
      return touched;
    }, {});
    setTouched(nextTouched);

    const errors = validate(values);
    setErrors(errors);

    if (Object.values(errors).some(Boolean)) return;

    onSubmit(values);
  };

  React.useEffect(() => {
    setErrors(validate(values));
  }, [values]);

  return {
    values,
    errors,
    touched,
    handleBlur,
    handleSubmit,
    handleChange,
  };
};

custom hook으로 재사용할 수 있게 분리를 했다. 

사용하는 쪽에서 초기화 state값이랑 validate 로직을 가지고 있는 함수랑 , onSubmit시 후속처리할 로직을 전달해주면 된다.

const LoginForm = () => {
  const validate = (values) => {
    const errors = {
      email: "",
      password: "",
    };

    if (!values.email) {
      errors.email = "이메일을 입력하세요.";
    }
    if (!values.password) {
      errors.password = "비밀번호를 입력하세요.";
    }

    return errors;
  };

  const { values, touched, errors, handleChange, handleBlur, handleSubmit } =
    MyForm.useForm({
      initialValues: { email: "", password: "" },
      validate,
      onSubmit: (values) => console.log("Submitted", values),
    });
  return (
    <form noValidate onSubmit={handleSubmit}>
      <input
        type="text"
        name="email"
        placeholder="Email"
        autoFocus
        value={values.email}
        onChange={handleChange}
        onBlur={handleBlur}
      ></input>
      {touched.email && errors.email && <span>{errors.email}</span>}
      <input
        type="password"
        name="password"
        placeholder="password"
        value={values.password}
        onChange={handleChange}
        onBlur={handleBlur}
      ></input>
      {touched.password && errors.password && <span>{errors.password}</span>}
      <button>Login</button>
    </form>
  );
};

export default LoginForm;

  const getFieldProps = (name) => {
    const value = values[name];
    const onBlur = handleBlur;
    const onChange = handleChange;
    return {
      name,
      value,
      onBlur,
      onChange,
    };
  };

useForm custom hook에 자주 쓰이는 함수들을 props로 편하게 쓸 수 있게 전달 해준다.

  const {
    values,
    touched,
    errors,
    handleSubmit,
    getFieldProps,
  } = MyForm.useForm({
    initialValues: { email: "", password: "" },
    validate,
    onSubmit: (values) => console.log("Submitted", values),
  });

요렇게 가져와서

      <input
        type="text"
        name="email"
        placeholder="Email"
        autoFocus
        value={values.email}
        onChange={handleChange}
        onBlur={handleBlur}
      ></input>
      <input
        type="text"
        placeholder="Email"
        autoFocus
        {...getFieldProps("email")}
      ></input>

좀 더 간편하게 줄여서 사용할 수 있다.

 


const formContext = React.createContext({});
formContext.displayName = "formContext";

export const Form = ({ children, ...rest }) => {
  const formValue = useForm(rest);
  return (
    <formContext.Provider value={formValue}>
      <form noValidate onSubmit={formValue.handleSubmit}>
        {children}
      </form>
    </formContext.Provider>
  );
};

export const Field = ({ as = "input", children, ...rest }) => {
  const { getFieldProps } = React.useContext(formContext);
  return React.createElement(
    as,
    { ...rest, ...getFieldProps(rest.name) },
    children
  );
};

export const ErrorMessage = ({ name }) => {
  const { touched, errors } = React.useContext(formContext);
  if (!touched[name] || !errors[name]) return null;
  return <span>{errors[name]}</span>;
};

React Context를 통해서  useForm custom hook을 provider에서 제공하고 이를 사용하는 2개의 Consumer 인 Field, ErrorMessage를 만들었다.

Field는 react element를 직접만드는데 기본 값으로 input element를 만들게 되고 넘겨받은 name으로 각각 알맞은
(email or password) props을 만들어서 생성한다.

const LoginForm = () => {
  const validate = (values) => {
    const errors = {
      email: "",
      password: "",
    };

    if (!values.email) {
      errors.email = "이메일을 입력하세요.";
    }
    if (!values.password) {
      errors.password = "비밀번호를 입력하세요.";
    }

    return errors;
  };

  const handleSubmit = (values) => console.log("Submitted", values);

  return (
    <MyForm.Form
      initialValues={{ email: "", password: "" }}
      validate={validate}
      onSubmit={handleSubmit}
    >
      <MyForm.Field name="email"></MyForm.Field>
      <MyForm.ErrorMessage name="email"></MyForm.ErrorMessage>
      <MyForm.Field name="password"></MyForm.Field>
      <MyForm.ErrorMessage name="password"></MyForm.ErrorMessage>
      <button>Login</button>
    </MyForm.Form>
  );
};

export default LoginForm;

선언형 방식으로 굉장히 간결해진것을 확인할 수 있다.