useRef와 폼 그리고 에러 경계

</>·2021년 11월 8일
2

Get to Know React

목록 보기
5/8
post-thumbnail

목표

  • Ref로 Dom을 다루어 본다.
  • Form을 다루어 본다.
  • 에러 경계(Error Boundaries)에 대해 알아본다.

1. Dom 다루기(Ref)

🤔 의문

  • 로그인 페이지가 있다고 가정했을 때, 로그인 페이지가 렌더링되고 그 후에 input값에 focus를 주고 싶을 때는 어떻게 해야할까?

1-1. Vanilla javascript

  <body>
    <input id="input" />
    <script>
      document.getElementById("input").focus();
    </script>
  </body>
  • Javascript만 사용할 경우 getElementById와 focus를 사용해서 적용할 수 있다.

1-2. React

  • 리액트에서는 useRef() 를 제공하는데, 이를 사용해서 focus를 줄 수 있다.
 <script type="text/babel">
    const rootElement = document.getElementById("root");

    const App = () => {
      const inputRef = React.useRef();

      React.useEffect(() => {
        inputRef.current.focus();
      }, []);

      return (
        <>
          <input ref={inputRef} />
        </>
      );
    };

    ReactDOM.render(<App />, rootElement);
  </script>

💡 주의

  • useRef를 사용할 때 주의해야 할 점이 있는데, 바로 current 속성을 사용해야 한다. 이는 우리가 선택하고자 하는 DOM을 가리킨다고 보면 된다.

🤔 의문

  • document 객체로 접근하면 간단하게 접근할 수 있는데 리액트에서는 굳이 useRef를 제공하는 이유는 무엇일까?
  • 그 이유는 효율성 때문이다.

  • document 객체를 사용해서 DOM에 직접적으로 도달하게 되면 비효율적인 상황이 발생할 수 있다.

  • 리액트에서는 컴포넌트의 상태 안에서 특정 변수로 라이프 사이클과는 독립적이고 최적화된 상태로 사용하고 싶은 저장 공간을 제공한다.


2. Form 다루기

  • form을 이용해서 로그인 페이지를 만들어보면 다음과 같다.

2-1. HTML form

<form>
  <label for="fname">First name:</label><br>
  <input type="text" id="fname" name="fname"><br>
  <label for="lname">Last name:</label><br>
  <input type="text" id="lname" name="lname"><br><br>
  <input type="submit" value="Submit">
</form> 
  • 기본적으로 Form에는 보통 LabelInputSubmit 3가지 요소가 있다.

2-2. React

  const App = () => {
    return (
      <>
        <form>
          <label for="fname">First name:</label>
          <br />
          <input type="text" id="fname" name="fname" value="John" />
          <br />
          <label for="lname">Last name:</label>
          <br />
          <input type="text" id="lname" name="lname" value="Doe" />
          <br />
          <br />
          <input type="submit" value="Submit" />
        </form>
      </>
    );
  };

💡 주의

  • 리액트에서 br태그나 input 태그 처럼 닫는 태그를 넣어주는 것이 좋은데 그 이유는 엘리먼트 요소에 닫는 태그가 없으면 인식하지 못하기 때문이다.

💡 주의

  • 리액트에서는 label 태그에서 for대신 htmlFor를 쓰는 것을 권장하고 있다.
  • for는 리액트가 이해하지 못하거나 혼동될 수 있는 키워드이기 때문이다.

htmlFor

💡 주의

  • 또한, 리액트에서는 input 태그에서 value 프로퍼티를 사용할 때 onChange 함수와 같이 쓰거나 또는 value 대신 defalutValue 프로퍼티를 이용하라고 권장하고 있다.

defaultValue

주의사항들을 처리하면 다음과 같다.

  const App = () => {
    return (
      <>
        <form>
          <label htmlFor="fname">First name:</label>
          <br />
          <input type="text" id="fname" name="fname" defaultValue="John" />
          <br />
          <label htmlFor="lname">Last name:</label>
          <br />
          <input type="text" id="lname" name="lname" defaultValue="Doe" />
          <br />
          <br />
          <input type="submit" value="Submit" />
        </form>
      </>
    );
  };

이제, submit 버튼을 눌렀을 때 onSubmit을 이용해 alert창으로 First name과 Last name을 띄우려고 한다.

form

그 전에 한 가지 처리해야할 부분이 있는데 기본적으로 form 태그는 submit 버튼을 누르면 action을 취하게 되고 새로고침이 된다.

🤔 의문

  • 그렇다면 어떻게 새로고침되는 것을 막을 수 있을까?

방법은 onSubmit에서 event의 기본적인 동작들을 막는 것이다. 아래 코드와 같이 파라미터로 들어오는 event 인자를 이용하면 된다. event 객체의 preventDefault() 함수를 이용하면 form에 실행되는 이벤트들을 모두 막을 수 있다.

  const onSubmit = (event) => {
    event.preventDefault();
  };

  const App = () => {
    return (
      <>
        <form onSubmit={onSubmit}>
          <label htmlFor="fname">First name:</label>
          <br />
          <input type="text" id="fname" name="fname" defaultValue="John" />
          <br />
          <label htmlFor="lname">Last name:</label>
          <br />
          <input type="text" id="lname" name="lname" defaultValue="Doe" />
          <br />
          <br />
          <input type="submit" value="Submit" />
        </form>
      </>
    );
  };

🤔 의문

  • 이제 alert창만 띄우는 일만 남았는데 input에 있는 value값을 어떻게 접근해야 할까?
  const onSubmit = (event) => {
    event.preventDefault();

    console.log(event.target);
  };

콘솔로 event.target 을 찍어보면 다음과 같이 태그가 출력된다.

console.log

참고로 태그 대신 객체의 속성을 출력해보고 싶다면 console.log 대신 console.dir()을 이용하면 된다. 다음과 같이 객체의 속성이 출력된다.

console.dir

그 중에서도 elements라는 속성이 있는데 이를 통해 input값에 있는 value들을 접근할 수 있다.

elements

   const onSubmit = (event) => {
    event.preventDefault();

    alert(`
      firstName: ${event.target.elements.fname.value}, 
      lastName: ${event.target.elements.lname.value}
    `);
  };

3. 에러 경계(Error Boundaries)

3-1. ErrorBoundary

  • 요즘에는 React Hook 때문에 함수형 컴포넌트가 대부분이다. 하지만, 아직 Hook으로 지원되지 않는 몇 가지 개념들이 있는데 그 중에 하나가 에러 경계(Error Boundaries)이다. 따라서, 에러 경계는 클래스형 컴포넌트로 코드를 구성해야 한다.

  • 아래의 코드는 부모(App)와 자식(Child) 컴포넌트 그리고 클래스로 구성된 ErrorBoundary 컴포넌트에 대한 코드이다.

class ErrorBoundary extends React.Component {
    state = { error: null };
    static getDerivedStateFromError(error) {
      return { error };
    }

    render() {
      const { error } = this.state;
      if (error) {
        return <p>There is Something Wrong.</p>;
      }

      return this.props.children;
    }
  }

  const Child = () => {
    throw new Error();
    return (
      <>
        <p>Child</p>
      </>
    );
  };

  const App = () => {
    return (
      <>
        <p>App</p>
        <ErrorBoundary>
          <Child />
        </ErrorBoundary>
      </>
    );
  };
  • Child 컴포넌트에서 throw new Error()를 통해 렌더링 되기 전에 일부로 에러를 발생시킨다. 이렇게 되면 화면에 렌더링이 되지 않고 개발자 도구 콘솔에는 에러에 대한 것들이 표시된다.
  • 이를 방지하기 위해 ErrorBoundary 컴포넌트를 사용한다.
  • 생명주기 메서드인 static getDerivedStateFromError()componentDidCatch() 중 하나 (혹은 둘 다)를 정의하면 클래스 컴포넌트 자체가 에러 경계된다고 공식 문서에 나와있다.
  • 따로 정의한 ErrorBoundary 컴포넌트를 에러를 발생시키는 Child 컴포넌트에 감싸면 에러가 발생해도 위에서 정의한 대로 p 태그를 호출한다.
  • 에러가 발생하지 않았다면 Child 컴포넌트를 호출한다.

🤔 의문

  • ErrorBoundary 컴포넌트 마다 다르게 리턴해주고 싶다면 어떻게 해야할까?

props로 컴포넌트를 넘겨주어 custom화 할 수 있다.

3-2. ErrorBoundary custom화


  class ErrorBoundary extends React.Component {
    state = { error: null };
    static getDerivedStateFromError(error) {
      return { error };
    }

    render() {
      const { error } = this.state;
      if (error) {
        return <this.props.fallback />;
      }

      return this.props.children;
    }
  }

  const Child1Fallback = (error) => {
    console.log(error);

    return <p>Child1 is Something Wrong</p>;
  };

  const Child2Fallback = (error) => {
    console.log(error);

    return <p>Child2 is Something Wrong</p>;
  };

  const App = () => {
    return (
      <>
        <p>App</p>
        <ErrorBoundary fallback={Child1Fallback}>
          <Child1 />
        </ErrorBoundary>
        <ErrorBoundary fallback={Child2Fallback}>
          <Child2 />
        </ErrorBoundary>
      </>
    );
  };
  • ErrorBoundary 컴포넌트에 fallback props를 넘겨주어 커스텀화 할 수 있다.

참고

profile
개발자가 되고 싶은 개발자

0개의 댓글