이번 편에서는 Ref와 React Hooks의 기본 API 중 하나인 useRef를 알아보겠습니다.

document.getElementById

import React from 'react'

const User = () => {
  const requestToLogin = event => {
    event.preventDefault()

    const idReference = document.getElementById('id')
    const passwordReferenece = document.getElementById('password')

    const id = idReference.target.value
    const password = passwordReferenece.target.value

    // a AJAX logic
  }

  return (
    <form onSubmit={requestToLogin}>
      <label>
        id:
        <input id='id' type='text' />
      </label>
      <label>
        password:
        <input id='password' type='password' />
      </label>
      <button type='submit'>로그인!</button>
    </form>
  )
}

컴포넌트를 작성하다 보면 태그를 직접 다루어야 하는 경우도 있습니다.

해당 프로젝트 안에서 유일한 id를 지정한다면 document.getElementById 메서드로 직접 Element를 불러와서 사용할 수 있습니다.

Ref

간단한 컴포넌트라면 document.getElementById 메서드를 이용할 수도 있곘지만, 테이블처럼 반복 컴포넌트인 경우에는 아무래도 한계와 React스럽지 않다는 찜찜함도 남습니다.

다행히도 React는 해결할 수 있는 방법을 제공합니다.

import React, { createRef, useState } from 'react'

const User = () => {
  const [idReference, setIdReference] = useState(() => createRef())
  const [passwordReferenece, setPasswordReference] = useState(() => createRef())

  const requestToLogin = event => {
    event.preventDefault()

    const id = idReference.current.target.value
    const password = passwordReferenece.current.target.value

    // a AJAX logic
  }

  return (
    <form onSubmit={requestToLogin}>
      <label>
        id:
        <input ref={idReference} type='text' />
      </label>
      <label>
        password:
        <input ref={passwordReference} type='password' />
      </label>
      <button type='submit'>로그인!</button>
    </form>
  )
}

useState를 이용하여 createRef 함수를 State로 생성하고 해당 State를 ref 속성에 지정하면 해당 State의 .current 를 통해서 사용할 수 있습니다.

Custom Hooks로 감싸기

createRef 함수로 생성한 State는 직접 Element를 지정해줄 일이 없기 때문에 useState의 2번째 return 값이 필요가 없으므로, Custom Hooks로 분리하면 깔끔하게 작성할 수 있습니다.

import React, { createRef, useState } from 'react'

const useReference = () => {
  const [reference, setReference] = useState(() => createRef())

  return reference
}

const User = () => {
  const idReferenece = useReference()
  const passwordReference = useReference()

  // ...

  return (
    <form>
      <label>
        id:
        <input ref={idReference} type='text' />
      </label>
      <label>
        password:
        <input ref={passwordReference} type='password' />
      </label>
      <button type='submit'>로그인!</button>
    </form>
  )
}

useRef

Custom Hooks로 작성하면 보다 깔끔하게 사용할 수 있지만, useReference는 자주 사용할 Custom Hooks인데 모든 프로젝트 마다 추가하는 것은 아무래도 번거로운 작업입니다.

React는 고맙게도 useRef 라는 이러한 기능을 지원합니다.

import React, { useRef } from 'react'

const User = () => {
  const idReferenece = useRef()
  const passwordReference = useRef()

  // ...

  return (
    <form>
      <label>
        id:
        <input ref={idReference} type='text' />
      </label>
      <label>
        password:
        <input ref={passwordReference} type='password' />
      </label>
      <button type='submit'>로그인!</button>
    </form>
  )
}

forwardRef

import React, { useRef } from 'react'

const User = () => {
  const idReferenece = useRef()
  const passwordReference = useRef()

  // ...

  return (
    <form>
      <LabelInput text='id:' type='text' ref={idReference} />
      <LabelInput text='password:' type='password' ref={passwordReference} />
    </form>
  )
}

const LabeledInput = ({ text, type, ref }) => (
  <label>
    {text}
    <input type={type} ref={ref} />
  </label>
)

컴포넌트를 작성하다 보면 하위 컴포넌트의 Element가 필요한 경우도 있지만, ref 속성은 React에서 특별한 속성이기 때문에 위처럼 작성하면 원하는 동작을 하지 않습니다.

import React, { forwardRef, useRef } from 'react'

const User = () => {
  const idReferenece = useRef()
  const passwordReference = useRef()

  // ...

  return (
    <form>
      <LabelInput text='id:' type='text' ref={idReference} />
      <LabelInput text='password:' type='password' ref={passwordReference} />
    </form>
  )
}

const LabeledInput = forwardRef(({ text, type }, ref) => (
    <label>
    {text}
    <input type={type} ref={ref} />
  </label>
))

여러 방법이 있지만 React가 지원하는 forwardRef 함수를 이용해서 해결하는 방법이 가장 깔끔합니다.