React-Hook 양식을 사용하여 양식 관리

글은 my personal blog에 최초로 발표되었다.
React에서 폼을 처리하는 것은 매우 어려운 일이다. 특히 동적 필드와 관련이 있을 때.많은 라이브러리들이 전체 과정을 간소화할 수 있다.그 중 하나는 React Hook Form 이다.말 그대로 React-Hook-form은 하나의 폼 구성 요소가 아니라 폼 행위를 제어하는 데 도움이 되는 각종 갈고리를 공개하고 하나의 구성 요소의 실현 세부 사항을 사용자에게 남겨 준다.이런 방법은 몇 가지 장점이 있는데 주로 사용자가 특정한 UI 프레임워크나 미리 정의된 폼 구성 요소에 의존하지 않는다는 것이다. 
이 글에서, 우리는 기본적인 세부 사항과 동적 재료 목록을 입력할 수 있는 간단한 식단표를 구축할 것이다.최종 결과는 다음과 같습니다.

사용자 인터페이스의 측면에서 볼 때, 그것은 그다지 화려하게 보이지 않는다. 왜냐하면 주로 React 갈고리 형식을 사용하기 때문이다.이외에도 UI 구성 요소 라이브러리와 Semantic UI React 를 사용하여 구성 요소의 스타일을 조정할 수 있습니다.
첫 번째 단계로 필요한 모든 종속성을 설치합니다.
npm i @emotion/core @emotion/styled semantic-ui-react semantic-ui-css react-hook-form
현재, 우리는 Form.js 라는 새 파일에서 폼 구성 요소를 설정할 수 있다.
 
import React from "react";
import styled from "@emotion/styled";
import { useForm } from "react-hook-form";

export const Recipe = () => {
  return (
    <Container>
      <h1>New recipe</Title>
    </Container>
  );
};

const Container = styled.div`
  display: flex;
  flex-direction: column;
`;
또한 import "semantic-ui-css/semantic.min.css";에 추가index.js, 사용자 정의index.css 스타일 위에 기억하십시오.        

감정/스타일 템플릿 베이스


이 모든 설정이 있어서 우리는 마침내 표 자체를 처리하기 시작할 수 있다.우리는 기초 부분부터 시작할 것이다. 이것은 레시피에 관한 일반적인 정보가 있을 것이다.폼 필드를 각 부분으로 나누는 데 도움을 주기 위해서, 우리는 사용자 정의 구성 요소 FieldSet 를 추가합니다. 이것은 본 컴퓨터의 HTML fieldset 위의 작은 추상입니다.
 
// FieldSet.js

export const FieldSet = ({ label, children }) => {
  return (
    <Container>
      {label && <Legend>{label}</Legend>}
      <Wrapper>{children}</Wrapper>
    </Container>
  );
};

const Container = styled.fieldset`
  margin: 16px 0;
  padding: 0;
  border: none;
`;

const Wrapper = styled.div`
  display: flex;
  justify-content: space-between;
  flex-direction: column;
  align-items: self-start;
`;

const Legend = styled.legend`
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 20px;
`;
폼 자체에 대해 우리는 의미 UI React에서 온 Form 구성 요소를 사용할 것이다. 이 구성 요소는 예를 들어 Form.Field 편리한 하위 구성 요소를 포함한다.이 간단한 레시피 표에 대해 우리는 레시피 이름, 설명, 부수 등 몇 개의 기본 필드만 있다.우리는 그것들을 표에 추가합시다.
 
import React from "react";
import styled from "@emotion/styled";
import { Button, Form } from "semantic-ui-react";
import { FieldSet } from "./FieldSet";

const fieldWidth = 8;

export const Recipe = () => {
  return (
    <Container>
      <h1>New recipe</h1>
      <Form size="large">
        <FieldSet label="Basics">
          <Form.Field width={fieldWidth}>
            <label htmlFor="name">Name</label>
            <input type="text" name="name" id="name" />
          </Form.Field>
          <Form.Field width={fieldWidth}>
            <label htmlFor="description">Description</label>
            <textarea name="description" id="description" />
          </Form.Field>
          <Form.Field width={fieldWidth}>
            <label htmlFor="amount">Servings</label>
            <input type="number" name="amount" id="amount" />
          </Form.Field>
        </FieldSet>

        <Form.Field>
          <Button>Save</Button>
        </Form.Field>
      </Form>
    </Container>
  );
};

const Container = styled.div`
  display: flex;
  flex-direction: column;
  padding: 25px 50px;
`;
여기에서 우리는 라벨이 있는 레시피 필드를 추가했는데, 그 결과는 다음과 같다.폼 요소에 name 속성이 사용되었음을 주의하십시오. 왜냐하면 그것들은 더욱 편리해지기 때문입니다.이 밖에 우리는 htmlForid 속성의 조합을 사용하여 필드의 접근성을 높인다. 

이제 React-Hook 양식을 사용하여 양식의 상태를 관리할 때가 되었습니다.이 라이브러리의 장점 중 하나는 상태 관리를 더욱 쉽게 하고 연결고리 setState 를 추가할 필요가 없다는 것이다.우리가 해야 할 일은 nameref 속성의 조합을 사용하여 표 상태에 필드를 등록하는 것이다.
import React from "react";
import styled from "@emotion/styled";
import { Button, Form } from "semantic-ui-react";
import { FieldSet } from "./FieldSet";
import { useForm } from "react-hook-form";

const fieldWidth = 8;

export const Recipe = () => {
  const { register, handleSubmit } = useForm();

  const submitForm = formData => {
    console.log(formData);
  };

  return (
    <Container>
      <h1>New recipe</h1>
      <Form size="large" onSubmit={handleSubmit(submitForm)}>
        <FieldSet label="Basics">
          <Form.Field width={fieldWidth}>
            <label htmlFor="name">Name</label>
            <input type="text" name="name" id="name" ref={register} />
          </Form.Field>
          <Form.Field width={fieldWidth}>
            <label htmlFor="description">Description</label>
            <textarea name="description" id="description" ref={register} />
          </Form.Field>
          <Form.Field width={fieldWidth}>
            <label htmlFor="amount">Servings</label>
            <input type="number" name="amount" id="amount" ref={register} />
          </Form.Field>
        </FieldSet>

        <Form.Field>
          <Button>Save</Button>
        </Form.Field>
      </Form>
    </Container>
  );
};
우리는 갈고리를 가져오고 호출하기 시작했고, 갈고리는 몇 명의 유용한 조수를 되돌려 주었다.이런 상황에서 우리는 useForm 그 이름을 통해 폼 필드를 상태의 상응하는 속성에 분배한다.이것이 바로 여기에 필드에 이름을 추가하는 것이 중요한 이유입니다.제출 함수를 register 리셋에 봉인해야 합니다.이제 양식 필드에 레시피 세부 정보를 입력하고 handleSubmit 를 누르면 콘솔에서 다음 객체를 볼 수 있습니다.
    {
      name: "Pancakes",
      description: "Super delicious pancake recipe",
      amount: "10" 
    }
이것이 바로 React 갈고리 폼을 사용하기 시작하는 데 필요한 모든 설정입니다.그러나 그 기능은 여기서 끝나지 않았습니다. 다음은 폼에 추가할 수 있는 향상된 것들을 볼 수 있습니다.

양식 검증 및 오류 처리


우리가 Save에서 얻은 register 값은 사실상 검증 파라미터를 대상으로 하는 함수이다.몇 가지 유효성 검사 규칙이 있습니다.
  • 필수
  • 최대
  • 최소 길이
  • 최대 길이
  • 패턴
  • 검증
  • 레시피 이름을 필수 필드로 만들기 위해 useForm 속성 호출 레지스터를 사용하십시오.
     
    <input type="text" name="name" id="name" ref={register({required: true})} /> 
    
    또한 required 는 모든 오류를 필드 이름에 비추는 useForm 대상을 되돌려줍니다.따라서 레시피 이름이 없으면 errors 유형의 errors 객체가 있습니다.또한 부울 값으로 인증 규칙을 지정하지 않고 오류 메시지로 문자열을 전달할 수 있습니다.
    ref={register({required: 'This field is required'})} 
    
    또는 name 속성을 사용할 수도 있습니다.나중에 required 을 통해 오류 메시지에 액세스할 수 있습니다.또한 오류 상태를 전환하기 위해 필드 오류를 부울 값으로 message 에 전달합니다. 
    현재, 우리는 폼 검증과 오류를 결합하여 사용자에게 유용한 정보를 표시할 수 있다.
    export const Recipe = () => {
      const { register, handleSubmit, errors } = useForm();
    
      const submitForm = formData => {
        console.log(formData);
      };
    
      return (
        <Container>
          <h1>New recipe</h1>
          <Form size="large" onSubmit={handleSubmit(submitForm)}>
            <FieldSet label="Basics">
              <Form.Field width={fieldWidth} error={!!errors.name}>
                <label htmlFor="name">Name</label>
                <input
                  type="text"
                  name="name"
                  id="name"
                  ref={register({ required: "Recipe name is required." })}
                />
                {errors.name && <ErrorMessage>{errors.name.message}</ErrorMessage>}
              </Form.Field>
              <Form.Field width={fieldWidth} error={!!errors.description}>
                <label htmlFor="description">Description</label>
                <textarea
                  name="description"
                  id="description"
                  ref={register({ maxLength: 100 })}
                />
                {errors.description && (
                  <ErrorMessage>
                    Description cannot be longer than 100 characters.
                  </ErrorMessage>
                )}
              </Form.Field>
              <Form.Field width={fieldWidth} error={!!errors.amount}>
                <label htmlFor="amount">Servings</label>
                <input
                  type="number"
                  name="amount"
                  id="amount"
                  ref={register({ max: 10 })}
                />
                {errors.amount && (
                  <ErrorMessage>Maximum number of servings is 10.</ErrorMessage>
                )}
              </Form.Field>
            </FieldSet>
    
            <Form.Field>
              <Button>Save</Button>
            </Form.Field>
          </Form>
        </Container>
      );
    };
    
    const Container = styled.div`
      display: flex;
      flex-direction: column;
      padding: 25px 50px;
    `;
    
    const ErrorMessage = styled.span`
      font-size: 12px;
      color: red;
    `;
    
    ErrorMessage.defaultProps = { role: "alert" };
    
    만약 우리가 잘못된 데이터를 포함하는 폼을 제출하려고 한다면, 필드의 편리한 검증 메시지를 받을 것입니다.

    또한 errors.name.message 규칙을 통해 사용자 정의 검증 규칙을 필드에 적용할 수 있습니다.그것은 서로 다른 검증 규칙을 가진 함수나 함수 대상이 될 수 있다.예를 들어, 다음과 같이 필드 값이 동일한지 확인할 수 있습니다.
    ref={register({validate: value => value % 2 === 0})
    

    숫자 입력 처리


    현재 폼에서 우리는 디지털 입력 필드를 서비스로 사용합니다.그러나 HTML 입력 요소의 작업 방식 때문에 폼을 제출할 때 이 값은 폼 데이터의 문자열입니다.어떤 경우, 이것은 우리가 원하는 것이 아닐 수도 있다. 예를 들어 데이터가 백엔드의 숫자로 예상된다면.여기에 간단한 복구 방법은 제출할 때 금액을 숫자로 바꾸는 것이다. 그러나 이것은 가장 좋은 것이 아니다. 특히 우리가 이런 필드가 많은 상황에서.더 좋은 해결 방안은 유형 변환 논리를 사용하여 디지털 입력을 단독 구성 요소로 추상화하는 것이다.이렇게 하면 폼이 제출될 때 데이터는 우리가 필요로 하는 유형을 가지고 있다.이 구성 요소를 폼에 연결하기 위해, React-Hook 폼은 제어된 외부 구성 요소를 처리하는 데 사용되는 패키지 Form.Field 를 제공합니다. 
    우선 validate라는 구성 요소를 만듭니다.
     
    // NumberInput.js
    
    import React from "react";
    
    export const NumberInput = ({ value, onChange, ...rest }) => {
      const handleChange = e => {
        onChange(Number(e.target.value));
      };
    
      return (
        <input
          type="number"
          min={0}
          onChange={handleChange}
          value={value}
          {...rest}
        />
      );
    };
    
    이후에 우리는 이 새 부품으로 전류Controller장을 교체할 수 있다.
     
    import { useForm, Controller } from "react-hook-form";
    
    //...
    
    const { register, handleSubmit, errors, control } = useForm();
    
    //...
    
    <Form.Field width={fieldWidth} error={!!errors.amount}>
      <label htmlFor="amount">Servings</label>
      <Controller
        control={control}
        name="amount"
        defaultValue={0}
        rules={{max: 10}}
        render={props => <NumberInput id="amount" {...props} />}
      />
      {errors.amount && (
        <ErrorMessage>Maximum number of servings is 10.</ErrorMessage>
      )}
    </Form.Field>
    
    우리는 NumberInput로부터 얻은 amount 대상을 사용합니다. registerprop을 사용하는 것이 아니라 controlprop을 검증하는 데 사용합니다.등록하려면 useForm 속성을 rules 에 추가해야 합니다.그리고 우리는 name 도구를 통해 입력 구성 요소를 전달합니다.현재 레시피 서비스의 데이터는 이전처럼 폼에 저장되고 외부 구성 요소를 사용합니다. 

    동적 필드


    조미료가 없으면 완전한 식단이 없다.그러나, 우리는 고정 성분 필드를 표에 추가할 수 없습니다. 왜냐하면 그것들의 수량은 레시피에 달려 있기 때문입니다.일반적으로 동적 필드를 처리하기 위해 사용자 정의 논리를 굴려야 하지만, React 갈고리 형식은 동적 입력을 처리하는 사용자 정의 갈고리 - Controller 를 추가합니다.이것은 창의 제어 대상과 필드 이름을 사용하여 동적 입력을 처리하는 데 사용되는 몇 개의 실용 프로그램을 되돌려줍니다.재료 배합 필드를 레시피 표에 추가해서 실제 동작을 봅시다.
     
    import React from "react";
    import styled from "@emotion/styled";
    import { useForm, Controller, useFieldArray } from "react-hook-form";
    import { Button, Form } from "semantic-ui-react";
    import { FieldSet } from "./FieldSet";
    import { NumberInput } from "./NumberInput";
    
    const fieldWidth = 8;
    
    export const Recipe = () => {
      const { register, handleSubmit, errors, control } = useForm();
      const { fields, append, remove } = useFieldArray({
        name: "ingredients",
        control
      });
    
      const submitForm = formData => {
        console.log(formData);
      };
    
      return (
        <Container>
          <h1>New recipe</h1>
          <Form size="large" onSubmit={handleSubmit(submitForm)}>
            <FieldSet label="Basics">
              <Form.Field width={fieldWidth} error={!!errors.name}>
                <label htmlFor="name">Name</label>
                <input
                  type="text"
                  name="name"
                  id="name"
                  ref={register({ required: "Recipe name is required." })}
                />
                {errors.name && <ErrorMessage>{errors.name.message}</ErrorMessage>}
              </Form.Field>
              <Form.Field width={fieldWidth} error={!!errors.description}>
                <label htmlFor="description">Description</label>
                <textarea
                  name="description"
                  id="description"
                  ref={register({ maxLength: 100 })}
                />
                {errors.description && (
                  <ErrorMessage>
                    Description cannot be longer than 100 characters.
                  </ErrorMessage>
                )}
              </Form.Field>
              <Form.Field width={fieldWidth} error={!!errors.amount}>
                <label htmlFor="amount">Servings</label>
                <Controller
                  control={control}
                  name="amount"
                  defaultValue={0}
                  rules={{max: 10}}
                  render={props => <NumberInput id="amount" {...props} />}
                />
                {errors.amount && (
                  <ErrorMessage>Maximum number of servings is 10.</ErrorMessage>
                )}
              </Form.Field>
            </FieldSet>
            <FieldSet label="Ingredients">
              {fields.map((field, index) => {
                return (
                  <Row key={field.id}>
                    <Form.Field width={8}>
                      <label htmlFor={`ingredients[${index}].name`}>Name</label>
                      <input
                        type="text"
                        ref={register()}
                        name={`ingredients[${index}].name`}
                        id={`ingredients[${index}].name`}
                      />
                    </Form.Field>
                    <Form.Field width={6}>
                      <label htmlFor={`ingredients[${index}].amount`}>Amount</label>
                      <input
                        type="text"
                        ref={register()}
                        defaultValue={field.amount}
                        name={`ingredients[${index}].amount`}
                        id={`ingredients[${index}].amount`}
                      />
                    </Form.Field>
                    <Button type="button" onClick={() => remove(index)}>
                      &#8722;
                    </Button>
                  </Row>
                );
              })}
              <Button
                type="button"
                onClick={() => append({ name: "", amount: "" })}
              >
                Add ingredient
              </Button>
            </FieldSet>
            <Form.Field>
              <Button>Save</Button>
            </Form.Field>
          </Form>
        </Container>
      );
    };
    const Container = styled.div`
      display: flex;
      flex-direction: column;
      padding: 25px 50px;
    `;
    const ErrorMessage = styled.span`
      font-size: 12px;
      color: red;
    `;
    const Row = styled.div`
      display: flex;
      align-items: center;
      & > * {
        margin-right: 20px !important;
      }
      .ui.button {
        margin: 10px 0 0 8px;
      }
    `;
    ErrorMessage.defaultProps = { role: "alert" };
    
    첫 번째 단계는 가져오기 render 와 폼 갈고리에서 얻은 useFieldArray 로 호출하여 필드의 이름을 전달하는 것입니다.useFieldArray 동적 필드를 관리하는 데 사용되는 몇 가지 유틸리티를 되돌려줍니다. 우리는 그 중에서 control useFieldArray 과 필드 자체의 그룹을 사용할 것입니다.도서관 은 실용 프로그램 기능의 전체 목록을 제공한다.재료의 기본값이 없기 때문에 이 필드는 처음에 비어 있습니다.함수를 채우고 빈 필드에 기본값을 제공하기 위해 append, 함수를 사용할 수 있습니다.필드의 렌더링은 그룹에 있는 색인을 통해 이루어지기 때문에 필드 이름의 형식remove이 중요합니다.또한 필드의 인덱스를 append 함수에 전달하여 필드를 삭제할 수 있습니다.현재, 몇 개의 성분 필드를 추가하고 그 값을 기입한 후, 우리가 표를 제출할 때, 이 모든 값은 표의 fieldArrayName[fieldIndex][fieldName] 필드에 저장됩니다. 
    이것은 기본적으로 React-Hook 폼으로 기능이 완비되고 관리하기 쉬운 폼을 구축하는 데 필요한 전부이다.이 라이브러리는 아직 많은 다른 기능이 있기 때문에 본고는 소개하지 않았으니 반드시 documentation site 를 보고 더 많은 예시를 얻으십시오.

    좋은 웹페이지 즐겨찾기