[Reactjs] Hooks 문서 한글 번역(진행 중)

Dongmin Jang
33 min readMar 6, 2020

--

아래 링크에 대한 번역 내용입니다. (오번역이 많을 수 있습니다. 도와주세요.)

Hooks 는 React 16.8 에서 추가 되었습니다. Hooks 는 class 를 이용하지 않고 state 와 다른 React 기능들을 사용할수 있게 합니다.

import React, { useState } from 'react';function Example() {
// 새로 state 변수를 선언하고 이를 'count' 라고 부르겠습니다.
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

useState 는 우리가 배울 첫 번째 Hook 입니다. 그러나 이 예제는 맛보기에 불과 합니다. 만약 뭔가 이해가 되지 않는다 하더라도 걱정하지 마세요.

당신은 다음 페이지에서 Hooks 에 대해서 배울 수 있습니다. 이 페이지에서는 계속해서 우리가 왜 Hooks 를 추가 했는지와 그것들이 애플리케이션 작성을 어떻게 도와줄 것인지를 설명하겠습니다.

Note

React 16.8.0 은 Hooks 를 제공하는 첫번째 배포 입니다. 업그레이드가 할 때, React DOM 을 포함해서 모든 패키지들을 업데이트 하는 것을 잊지 마세요. React Native 는 다음 stable 버전 배포에서 부터 Hooks를 제공할 것입니다.

Video Introduction

2018 React 컨퍼런스에서, Sophie Alpert and Dan Abramov은 Hooks 에 대해서 설명하였습니다. 그리고 Ryan Florence가 Hooks 를 사용해서 어떻게 애플리케이션을 리팩터 해야 하는지 시연하였습니다. 비디오를 통해 확인하실 수 있습니다.

No Breaking Changes

계속 하기전에 Hooks 에 대해서 잠시 봅시다.

  • 완전한 옵트인이다. 기존의 코드를 재작성하지 않고, 컴포넌트들에 Hooks 를 적용해볼 수 있습니다. 만약 원치 않는다면 Hooks 를 당장 배우거나 적용할 필요는 없습니다.
  • 100% 백워드 호환성. Hooks 는 어떠한 큰 변화를 포함하고 있지 않습니다.
  • 당장 적용 가능. Hooks 는 16.8.0 버전 부터 가능합니다.

리액트에서 클래스들을 지울 계획은 없습니다. 이 페이지의 하단에서 Hooks를 위한 점진적 적용 전략에 대해서 알게 될 것입니다.

Hooks 는 당신이 알고 있는 리액트 컨셉들을 바꾸지는 않을 것입니다. 대신에 Hooks는 당신이 알고 있는 리액트 컨셉들에게 더 많은 api를 제공할 것입니다. (props, state, context, refs 그리고 lifecycle) 뒤에서 보겠지만, 앞에 나열한 것들을 조합하는 아주 강력한 방법을 제공할 것입니다.

만약에 Hooks 에 대한 학습을 바로 시작하고 싶다면, 그냥 다음 페이지로 넘어가세요. 당신은 이 페이지를 읽음으로서, 왜 Hooks가 추가 되었는지에 대해서 더 많이 알게 될 수도 있고, 어플리캐이션을 다시 작성하지 않고도 hooks를 이용할 수 있습니다.

Motivation

Hooks는 5년 넘게 수만개의 컴포넌트들을 작성하고 유지하면서 맞닥드린 리액트의 문제들을 해결해줍니다. 당신이 리액트를 배우든, 매일 사용하거나 혹은 비슷한 컴포넌트 모델의 다른 라이브러리를 선호하든 이러한 문제들에 대해서 알고 있을 것입니다.

컴포넌트간에 stateful 로직을 재사용하기가 어렵다

리액트는 컴포넌트에 재사용 가능한 요소를 붙이는 방법을 제공하지 않습니다. (예를 들면 store 에 연결하는 것). 만약에 당신이 한동안 리액트로 작업 했었다면, 이를 해결하기 위해 HOC, render props와 같은 것들을 이용한 패턴에 익숙해져있을 것입니다. 그러나 이러한 패턴들은 당신의 컴포넌트들을 재구성 하도록 요구됩니다. 이는 복잡하고 번거로울 것이고, 코드를 따라가기에 어렵게 합니다. 만약에 리액트 devTools 에서 전형적인 리액트 앱을 본다면, providers, comsumers higher-order components, render props, 그외 다른 추상적 개념의 layers에 의해 컴포넌트가 감싸여진 컴포넌트 wrapper hell 을 찾을 수 있을 것입니다. 우리가 devTools에서 이것들을 가려내는 동안, 근본적인 문제가 드러났습니다. 리액트는 stateful한 로직을 공유하기 위한 더 나은 기본 구성이 필요하다는 것입니다.

Hooks를 이용해서 컴포넌트로부터 stateful 로직을 추출해낼 수 있습니다. 그리고 그 로직만 테스트 할 수도 있고 재사용할 수 있습니다. 컴포넌트 계층 구조를 수정하지 않고도 Hooks로 stateful한 로직을 재사용할 수 있습니다. 이는 더 많은 컴포넌트와 커뮤니티 사이에서 Hooks 를 공유하기 쉽게 만들어줍니다.

우리는 이점에 대해 Building Your Own Hooks 에서 다룰 것입니다.

복잡한 컴포넌트는 이해를 더 어렵게 만든다

우리는 종종 간단하게 만들어지지만 점점 관리하기 어려운 stateful 로직의 덩어리와 사이드이펙트들로 구성되는 컴포넌트들을 유지하게 됩니다. 각각의 lifecycle method는 연관성 없는 로직들의 뒤섞여 포함되게 됩니다. 예를 들면 componentDidMount, componentDidUpdate 에서 data를 fetching 하는 코드가 수행 됩니다. 그러나 같은componentDidMount에서 이벤트 리스터를 셋업하는 연관성이 떨어지는 로직이 포함 됩니다. componentWillUnmount 에서는 그것을 떼어내는 로직이 포함됩니다. 같이 수정 되어야 하는 상호작용하는 코드는 나뉘어져 있으나 전혀 연관성 없는 코드는 한 method 에 들어가 있게 됩니다. 이것은 버그나 일관성 없는 코드를 유도하기 매우 쉽습니다.

여러 경우들에서 이러한 컴포넌트를 작은 것들로 만드는게 불가능합니다. 왜냐하면 stateful 로직이 컴포넌트 전체에 널려있기 때문입니다. 또한 test 하기에도 어려움이 있습니다. 이는 많은 사람들이 React 를 분리된 state management library 와 조합하는 것을 선호하는 이유 중 하나입니다. 그러나 지나친 추상화는 다른 파일간의 이동을 요구하게 될 것입니다. 그리고 컴포넌트 재사용은 더 어려워질 것입니다.

이것을 해결하기 위해 Hooks는 lifecyle mothod를 기반으로 강제로 나누는 대신에 한 컴포넌트를 연관성 있는 (subscription 이나 fetching data 가 설정 된) 여러 작은 function으로 나누게 할 것입니다. 또한 reducer를 이용하여 컴포넌트의 local state를 좀 더 예측 가능하게 관리할 수 있게 될 것입니다.

좀 더 자세한 것은 Using the Effect Hook 에서 다루게 됩니다.

Class는 사람과 기계, 둘 다 혼란스럽게 합니다.

코드의 재사용, 코드의 구조를 더 복잡하게하는 것외에도 class는 react를 배우는데 큰 장벽이 될 수 있습니다. 자바스크립트에서 this가 어떻게 동작해야 되는지 알아야 하는데, 이것은 대부분의 언어에서 아주 다르게 작동합니다. 이벤트 핸들러를 bind 해야 하는 것을 반듯이 기억해야 합니다. unstable syntax proposals 없이는 그 코드가 장황해 집니다. 사람들은 props, state 그리고 top-down 의 data 흐름을 완벽하게 이해 할 수 있지만, 그래도 여전히 class와 씨름을 해야 합니다. React 에서 function과 class 컴포넌트의 구분, 그리고 각각을 언제 사용 해야하는지는 숙련된 react 개발자간에도 의견이 분분합니다.

추가로 react는 지난 5년동안 사용되었고, 앞으로 5년동안 관련성을 유지하고자 합니다. Svelte, Angular, Glimmer, 그외 다른 것들에서 보여주듯이 컴포넌트의 ahead-of-time compilation (컴파일)은 많은 미래 가능성을 갖고 있습니다. 특히 템플릿에 제한되지 않는다면 더욱 그렇습니다. 최근에 prepack을 이용해서 component folding을 시험 했고, 이른 성과에 도달 했습니다. class 컴포넌트는 의도하지 않은 패턴을 유발한다는 것을 발견 했는데, 이는 최적화를 느리게 합니다. class들은 오늘 날의 툴들에도 문제를 제기합니다. 예를 들면, class들은 최소화가 잘 되지 않으며 hot reloading 이 불안하고 신뢰할 수 없습니다. 우리는 코드가 최적화 된 경로 위에 유지 될 수 있도록 하는 api를 제시합니다.

이러한 문제를 해결하기 위해 Hooks는 class 없이 더 많은 리액트의 기능들을 사용하게 할 것입니다. 개념적으로 React 컴포넌트는 항상 function에 가까웠습니다. Hooks는 functions를 수용 하면서도 실용적인 react 정신을 희생 시키지는 않습니다. Hooks는 도피구에 대한 액세스를 제공하며 복잡한 함수형, 반응형 프로그래밍 기술을 요구하지 않습니다.

Examples

Hooks at a Glance 는 Hooks 학습을 시작하기에 좋습니다.

점증적 적용 전략

TLDR: React 에서 class들을 삭제할 계획은 없습니다.

우리는 React 개발자들이 제품을 배송하는데 집중하고 있으며 매번 릴리즈 되는 새로운 API를 볼 시간이 없다는 것을 알고 있습니다. Hooks 는 아주 새로운 것이라, 학습하고 적용하기보다는 더 많은 예제와 튜토리얼이 나오기를 기다리는 것이 나을 수도 있습니다.

또한 리액트에 새로운 원초적인 요소를 추가하는 기준이 매우 높다는 것을 알고 있습니다. 호기심 많은 독자들을 위해, 더 구체적인 동기를 부여하는 detailed RFC 를 준비하였고 구체적인 디자인 결정과 관련 선행기술에 대한 추가적인 관점을 제공합니다.

결정적으로, Hooks는 기존 코드와 같이 사용 되므로 점증적을 적용할 수 있습니다. Hooks로 전환하는 것을 서두를 필요가 없습니다. ‘거대한 수정'은 피하는 것을 권합니다. 특히 예를 들면 복잡한 class 컴포넌트입니다. Hooks 에서의 사고를 시작하기 위해 심적 변화가 조금 있게 됩니다. 경험에 의하면 먼저 새로운 중요하지 않은 컴포넌트에서 Hooks 사용을 연습하는 것이 제일 좋습니다. 그리고 팀원 모두가 Hooks 사용에 편안함을 느끼도록 해야 합니다. Hook를 권한 이후에는 우리에게 피드백(긍정, 부정 모두)을 보내주세요.

Hooks가 class의 모든 사용 사례들을 다루게 될 것이지만, 당분간(?)은 class 컴포넌트를 지원 할 것입니다. 페이스북에서는 수만개의 class 가 쓰여져 있는데 그것들을 다시 만들 계획은 없습니다. 대신에 class와 함께 새로운 컴포넌트에 Hooks를 사용하기 시작할 것 입니다.

— — — — — — — — — — — — — — — —

Hooks는 기존의 코드와 호환이 됩니다. (backwards-compatible) 이 페이지는 경험이 있는 React 유저들을 위한 요약 내용입니다. 진행이 빠릅니다. 만약 어렵게 느껴진다면 다음과 같이 노란 박스를 찾으세요. (리액트 api 문서 참고)

자세한 설명

왜 Hooks 를 소개하는지 알고 싶다면 Motivation 을 읽으세요.

↑↑↑ 각각의 섹션 끝에는 노란 박스가 있습니다. 자세한 설명으로 연결해 줄 것입니다.

📌 State Hook

이 예제는 카운터를 구현합니다. 버튼을 클릭하면 value 값이 증가합니다.

import React, { useState } from 'react';function Example() {
// 'count' 라는 state 변수 값을 선언합니다.
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

여기서 useState 가 Hook(이게 무엇을 뜻하는지 곧 설명합니다) 입니다. function 컴포넌트 안에서 local state 를 추가할 때 호출합니다. React는 re-renders 가 되어도 이 state를 보존합니다. useState는 두가지를 return 합니다. 현재 state value 와 이 값을 update하는데 사용되는 함수 입니다. 이 함수를 이벤트 핸들러나 다른 곳에서 호출해서 사용할 수 있습니다. class 컴포넌트의 this.setState와 비슷하지만, 이전과 새로운 state를 함께 병합하지는 않습니다. ( useStatethis.state 를 비교하는 예제는 다음 링크에서 볼 수 있습니다. Using the State Hook)

useState의 유일한 인자는 초기 state 입니다. 위의 예제에서는 0 입니다. 카운터가 0 부터 시작하기 때문입니다. this.state와 다르다는 것을 기억하세요. state는 object가 될 수 없습니다. 초기 state 인자는 첫번째 render 에서만 사용 됩니다.

다수의 state 변수 선언

한 컴포넌트 안에 State Hook 을 여러번 사용할 수 있습니다.

function ExampleWithManyStates() {
// 다수의 state 변수 선언
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}

array destructuringuseState를 이용해서 서로 다른 이름의 변수로 선언할 수 있게 해줍니다. 이 이름들은 useState API의 일부가 아닙니다. 대신 React는 useState 를 여러번 호출하면 매 render 시에 같은 순서로 진행하는 것으로 가정합니다. 왜 이렇게 작동하는지 이게 언제 유용한지 나중에 다시 다루겠습니다.

그런데 Hook은 무엇인가?

Hooks는 function 컴포넌트에서 React state 와 lifecycle 기능을 hook 하는 함수입니다. Hooks는 classes안에서는 동작하지 않습니다. Hooks는 class 없이 React를 사용하게 합니다. ( 기존의 코드를 밤을 새서 다시 작성하는 것을 권하지 않습니다. 그러나 Hooks를 이용해서 새로운 컴포넌트를 만들 수는 있습니다.)

React는 useState 와 같이 빌트인으로 몇가지 Hooks를 제공합니다. 또한 다른 컴포넌트간에 stateful 기능을 재사용하기 위해 스스로 Hooks를 만들어 사용할 수 있습니다. 먼저 빌트인 Hooks를 살펴 보겠습니다.

Detailed Explanation

State Hook에 대한 자세한 내용은 다음 페이지에서 볼 수 있습니다. Using the State Hook

⚡️ Effect Hook

당신은 이전에 React 컴포넌트에서 data fetching, subscriptions 또는 DOM 에서 수동으로 수정한적이 있을 것입니다. 이러한 기능을 side effect라고 부릅니다. (또는 effects 라고 짧게..) 왜냐하면 그런 기능들은 다른 컴포넌트에 영향을 미칠 수도 있고 redering 동안에 끝나지 않을 수 있기 때문입니다.

Effect Hook은 (useEffect ) function 컴포넌트에서 side effects를 수행하기 위해 추가된 기능입니다. 이는 React class의 componentDidMount, componentDidUpdate, componentWillUnmount 와 같은 목적을 지닙니다. 그러나 한가지 API 로 통합 되었습니다. (Using the Effect Hook 에서 useEffect와 이들 함수간에 차이를 보여주는 예제를 보실 수 있습니다.)

예를 들면, 다음 컴포넌트는 React가 DOM을 업데이트한 후에 document title을 지정합니다.

import React, { useState, useEffect } from 'react';function Example() {
const [count, setCount] = useState(0);
// componentDidMount, componentDidUpdate와 비슷
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

useEffect 를 호출하면 DOM 변화를 flushing 한 후에 React에게 effect 함수를 실행하게 됩니다. Effects는 컴포넌트 안에서 선언 되기 때문에 props와 state에 접근할 수 있습니다. 기본적으로 React는 매 render 후에 effects를 실행합니다 — 첫 render를 포함합니다. ( Using the Effect Hook 에서 class lifecycles와 어떻게 다른지 더 자세히 다룹니다. )

또한 Effects는 fucntion return에 의해 어떻게 ‘clean up’ 할 것인지 선택적으로 구분 됩니다. 예를 들면, 다음 컴포넌트는 친구의 온라인 상태를 구독하기 위해 effect를 사용합니다. 그리고 구독 해지를 함으로써 온라인 상태를 지웁니다.

import React, { useState, useEffect } from 'react';function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}

이 예제에서 React 컴포넌트가 unmount 될 때와 후속 render를 위해 effect가 재실행 되기 전에 ChatAPI를 구독중지를 합니다. (ChatAPI에 전달해야 할 props.friend.id값이 변하지 않아서, 원한다면 재구독을 건너 뛰는 방법이 있습니다. )

useState 와 같이, 컴포넌트 안에서 많이 사용할 수 있습니다.

function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// ...

Hooks는 lifecycle methods를 이용하여 강제로 나누지 않아도 관련된 요소들에 의해(구독 추가, 삭제) 컴포넌트 안에서 side effect들을 조합하게 합니다.

Detailed Explanation

Using the Effect Hook 에서 useEffect에 대해서 더 자세히 배울 수 있습니다.

✌️ Rules of Hooks

Hooks는 자바스크립트 함수 입니다만, 추가적으로 2가지 기준을 적용합니다.

  • 가장 상위 레벨에서만 Hooks를 호출합니다. loops(반복), conditions(조건), nested functions(하위?) 에서는 호출하지 마세요.
  • React function 컴포넌트에서만 호출하세요. 일반 자바스크립트 함수에서는 호출하지 마세요.(hooks를 호출할 수 있는 유효한 곳이 하나 있습니다 — 당신이 만든 custom hooks 입니다. 곧 배우게 될 것입니다.)

이러한 기준을 자동적으로 강제하기 위해 linter plugin 을 제공합니다. 이러한 기준들은 처음에는 제한적이고 혼란스럽게 보일 수 있습니다. 그러나 Hooks가 잘 작동하기 위해서는 필수적입니다.

Detailed Explanation

다음 페이지에서 기준에 대해서 더 자세히 알수 있습니다. Rules of Hooks

💡 자신만의 Hooks 만들기

컴포넌트들끼리 stateful한 로직 코드를 재사용하고 싶을 때가 있습니다.
전통적으로 이 문제에 대해서 두가지 해결 방법이 있었습니다. (higher-order componentsrender props)
Custom Hooks는 당신의 tree (컴포넌트 구조) 에 더 많은 컴포넌트를 추가하지 않으면서 이를 가능하게 할 것입니다.

이 문서 초기에 친구의 온라인 상태를 구독하게 하기 위해 useStateuseEffect Hooks를 호출하는 FriendStatus 컴포넌트를 소개 했습니다. 이 구독 로직을 다른 컴포넌트에서 재사용하기를 원한다고 가정 하겠습니다.

먼저 로직을 useFriendStatus 라고 부르는 custom Hook으로 빼내겠습니다.

import React, { useState, useEffect } from 'react';function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}

friendID를 인자로 가지고 있고 친구 온라인 상태인지를 리턴합니다.

이제 다음 두 컴포넌트에서 이를 사용할 수 있습니다.

function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
/*****************************/function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}

이들 컴포넌트들의 state는 완전히 독립적입니다. Hooks는 스스로 state가 되지 않고 stateful 로직 코드를 재사용할 수 있는 방법입니다. 사실 각각의 hook 호출은 완전히 별도의 state를 갖습니다. 그래서 한 컴포넌트에서 같은 custom hook을 두번 사용할 수 있습니다.

Custom hooks는 기능이라기 보다는 컨벤션(관습)에 가깝습니다. 만약 함수의 이름이 ‘use로 시작되고 다른 Hooks를 호출한다면, 우리는 그것을 custom Hook이라고 부릅니다. useSomething 네이밍 컨벤션은 linter plugin 이 Hooks를 사용하는 코드에서 버그를 찾을 수 있는 방법입니다.

당신은 폼 핸들링, 애니매이션, 선언적 구독, 타이머, 그리고 우리가 고려하지 못하는 더 많은 것들까지 다양한 것들을 다루는 custom Hook를 만들 수 있습니다. 우리는 앞으로 React 커뮤니티에서 어떠한 custom Hooks를 보게 될 지 기대됩니다.

Detailed Explanation

custom hooks에 대한 자세한 내용은 다음 페이지에서 볼 수 있습니다: Building Your Own Hooks.

🔌 Other Hooks

비교적 덜 사용되지만 당신이 유용하다고 느낄 수 있는 빌트인 hooks가 있습니다. 예를 들면, useContext 는 nesting 를 하지 않아도 React context 를 구독 할 수 있습니다.

function Example() {
const locale = useContext(LocaleContext);
const theme = useContext(ThemeContext);
// ...
}

그리고 useReducer 는 reducer와 함께 복잡한 컴포넌트의 local state를 관리할 수 있습니다

function Todos() {
const [todos, dispatch] = useReducer(todosReducer);
// ...

Detailed Explanation

빌트인 hooks에 대해서 더 자세한 내용은 다음 페이지에서 볼 수 있습니다: Hooks API Reference.

introduction page에서는 다음 예제를 사용하여 Hooks를 익혔습니다.

import React, { useState } from 'react';function Example() {
// count 라는 새로운 state 변수를 선업합니다.
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

동일한 기능의 class 컴포넌트와 이 코드를 비교하며 Hooks에 대해서 알아보겠습니다.

동등한 기능의 Class 예제

만약 이전에 리액트에서 class를 사용했다면, 코드는 다음과 같을 것입니다.

class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}

{ count: 0 } 으로 state는 시작 합니다. 그리고 user가 버튼들 클릭하면 this.setState() 호출 할 때, state.count 를 증가시킵니다. 이 class의 snippets을 문서 전반에 걸쳐 사용할 것입니다.

Note

더 현실적인 예제들 대신에 왜 이 카운터를 사용하는지 궁금해 하실 수 있습니다. 이는 Hooks를 가지고 첫번째 단계를 만들어가는 동안 API 에 집중할 수 있도록 해줍니다.

Hooks and Function Components

function 컴포넌트는 다음과 같습니다.

const Example = (props) => {
// You can use Hooks here!
return <div />;
}

또는 다음과 같을 수도 있습니다.

function Example(props) {
// You can use Hooks here!
return <div />;
}

예전에는 ‘stateless components’로 알고 있었을 것입니다. 이제 React state를 사용하는 기능에 대해서 소개할 것이니, function 컴포넌트라는 이름을 사용하는게 더 나을 것입니다.

Hooks는 class 안에서 작동하지 않습니다. 그러나 class 작성하는 것 대신에 사용할 수 있습니다.

Hook이 무엇인가요?

Our new example starts by importing the useState Hook from React:

import React, { useState } from 'react';function Example() {
// ...
}

Hook이 무엇인가요? Hook은 React 기능을 추가할 수 있는 특별한 함수 있습니다. 예를 들면 useState function 컴포넌트에 React state 를 추가할 수 있는 Hook 입니다. 우리는 뒤에서 다른 Hook에 대해서 더 배울 것입니다.

언제 Hook을 사용하나요? 만약 function 컴포넌트를 사용하다가 state를 추가해야 한다면, 예전에는 class 컴포넌트로 바꿨습니다. 이제는 function 컴포넌트 안에서 Hook을 사용하면 됩니다. 지금 바로 해보겠습니다.

Note:

컴포넌트 안에서 Hook을 사용할 수 있는지에 대한 규칙이 있습니다. Rules of Hooks 에서 학습 할 수 있습니다.

State Variable 선언

class 에서는 constructor에서 this.state{ count: 0 } 라고 초기화 할 수 있었습니다.

class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}

function 컴포넌트에서는 this가 없습니다. 그래서 this.state를 할당하거나 사용할 수 없습니다. 대신에, 컴포넌트안에서 useState을 직접 호출합니다.

import React, { useState } from 'react';function Example() {
// count라고 새로운 state 변수 선언
const [count, setCount] = useState(0);

useState 호출하면 무엇을 하나요?? “state 변수”를 선업합니다. 위 예제에서는 count이며, banana 와 같이 다른 것으로 부를 수는 없습니다. 이는 함수 호출간에 몇몇 값을 보존하는 방법입니다 — useState 는 class에서 this.state가 제공했던 기능을 동일하게 사용할 수 있게하는 새로운 방법입니다. 보통 function가 종료할 때 변수도 사라집니다. 그러나 state 변수는 React에 의해 보존 됩니다.

useState 에 인자로 무엇을 넘겨야 할까요? useState()Hook 의 유일한 인자는 초기값입니다. class와 다르게, state는 object를 갖지 않습니다. 숫자나 문자를 넣을 수 있습니다. 예제에서는 user가 얼마나 클릭했는지를 알아야 하기 때문에 초기값으로 0을 넣었습니다. (만약 두개의 다른 변수를 state에 저장하기를 원한다면useState() 를 두번 호출하면 됩니다.)

useState 무엇을 반환하나요? 두가지 값을 반환합니다: 현재 값과 update를 위한 함수 입니다. 이것이 다음과 같이 코드를 작성하는 이유입니다.
const [count, setCount] = useState().
이것은 class 컴포넌트에this.state.countthis.setState 랑 비슷합니만 쌍으로 존재한다는 것만 다릅니다. 우리가 사용하는 이 방식이 익숙하지 않다면, 이 링크 페이지의 하단으로 돌아갈 것입니다.

useState Hook이 무엇인지 알았으니, 예제가 좀 더 와닿을 겁니다.

Now that we know what the useState Hook does, our example should make more sense:

import React, { useState } from 'react';function Example() {
// count 라는 새로운 state 변수 선업
const [count, setCount] = useState(0);

우리는 count라는 state 변수를 선언했고 0을 할당 했습니다. React는 re-render 사이에 현재 값을 기억할 것입니다. 그리고 function에 가장 최근 값을 제공할 것입니다. 만약 count를 업데이트 하고 싶다면setCount 를 호출하세요.

Note

useStatecreateState 로 이름 짓지 않았는지 의문을 갖을 수 있습니다.

state는 첫번째 컴포넌트 render에서 만들어 지기 때문에 “Create” 는 정확한 표현이 아닐 수 있습니다. 다음 render가 이뤄지는 동안에useState 는 현재 state를 넘겨 줍니다. 그렇지 않으면 “state” 가 되지 않을 것입니다. Hook의 이름이 use로 시작하는 이유가또 있습니다. Rules of Hooks에서 나중에 배우게 될 것입니다.

Reading State

class에서 현재 count를 노출하고 싶을 때는 this.state.count 값을 읽습니다.

<p>You clicked {this.state.count} times</p>

function에서는 직접 count를 사용합니다.

<p>You clicked {count} times</p>

Updating State

class에서 count를 업데이트 해야 할 때는this.setState() 호출 합니다.

<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>

function에서는 이미 setCountcount 를 갖고 있습니다. 그래서 this가 필요 없습니다.

<button onClick={() => setCount(count + 1)}>
Click me
</button>

Recap

지금 부터 우리가 배운 것을 다시 줄 라인 별로 확인하고 이해하도록 하겠습니다.

 1:  import React, { useState } from 'react';
2:
3: function Example() {
4: const [count, setCount] = useState(0);
5:
6: return (
7: <div>
8: <p>You clicked {count} times</p>
9: <button onClick={() => setCount(count + 1)}>
10: Click me
11: </button>
12: </div>
13: );
14: }
  • Line 1: useState 를 React에 import 합니다. 이는 function 컴포넌트에서 local state를 갖도록 합니다.
  • Line 4: Example 컴포넌트에서, useState Hook을 이용해서 새로운 state 변수를 선언 했습니다. 두개의 값을 반환해주는데, 여기서 이름이 정해집니다. 버튼 클릭 횟수를 가지고 있기 때문에 count라고 변수를 부를 것입니다. useState인자로 0을 넘겨줌으로써, 초기화를 시켜줍니다. 두번째로 반환되는 것은 스스로를 위한 함수입니다. count 를 업데이트 할 수 있는데, 그래서 우리는 이것을 setCount라고 부를 것입니다.
  • Line 9: 유저가 클릭할 때, setCount 새로운 값과 함께 호출 합니다. React 는 새로운 값을 count에 전달하여 예제 컴포넌트를 re-render 시킬 것입니다.

처음에는 이러한 내용들이 많아 보일 것입니다. 서두르지 마세요. 설명에서 헤매게 되면 위 코드를 다시 보고 위에서 아래로 읽어내려가려고 시도하세요. class 컴포넌트에서 state가 어떻게 동작하는지를 잊고 이 코드를 새로운 시각으로 바라보려고 노력한다면 점점 이해가 될 것이라고 약속합니다.

Tip: Square Brackets은 무엇인가요?

state 변수를 선언할 때, square bracket을 보셨을 것입니다.

const [count, setCount] = useState(0);

왼쪽에 있는 이것은 React API는 아닙니다. 당신은 직접 state 변수 이름을 지을 수 있습니다.

const [fruit, setFruit] = useState('banana');

이 javascript syntax는 “array destructuring” 이라고 합니다. 이는 fruit ,setFruit이라는 새로운 두 변수를 만든 것을 의미합니다. fruituseState 에 의해 반환되는 첫번째 값으로 설정 됩니다. setFruit 는 두번째 값입니다. 이것은 다음 코드와 같습니다.

var fruitStateVariable = useState('banana'); // 한쌍의 값 반환
var fruit = fruitStateVariable[0]; // 첫번째 item
var setFruit = fruitStateVariable[1]; // 두번째 item

useState 를 이용해서 state 변수를 만들 때, 한 쌍의 값이 반환 됩니다.(array 안에 두 값이 있음) 첫번째 item 은 현재 값입니다. 두번째 값은 update 할 수 있는 함수 입니다. [0], [1] 을 이용해서 이 값들에 접근하는 것은 특정한 의미가 있기 때문에 혼란이 있습니다. 이러한 이유로 array destructuing 을 사용하는 것입니다.

Note

this와 같은 것을 React에 넘기지 않았는데 React가 어떻게 어떤 컴포넌트 useState와 상응하는지 궁금해 하실 수 있습니다. 이 문제와 다른 많은 문제들을 FAQ 섹션에서 답할 것 입니다.

Tip: 다중 state 변수 사용

한개 이상의 변수를 사용해야 할 때, 각각의 state 변수에 다른 이름을 명할 수 있기 때문에 [something, setSomething] 처럼 쌍으로 state 변수를 선언하는 방법은 아주 편리합니다.

function ExampleWithManyStates() {
// 다중 state 변수 선언!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);

위의 컴포넌트에서 age, fruit, todos를 지역 변수로 가지고 있습니다. 그리고 각각을 업데이트 할 수 있습니다.

function handleOrangeClick() {
// this.setState({ fruit: 'orange' }) 와 비슷합니다
setFruit('orange');
}

당신은 다수의 state 변수를 사용할 필요가 없습니다. state 변수들은 objects와 arrays 를 잘 유지 할 수 있습니다. 그래서 관련된 데이터를 함께 그룹화 할 수 있습니다. 그러나 클래스의 this.setState 와는 다르게 state 변수를 병합하는 대신, 대체(replace)하여 update하게 됩니다.

FAQ 에서 독립적인 state 변수 분할에 대한 추가 권장 사항을 제공합니다.

— — — — — —

Effect Hook은 함수형 컴포넌트에서 side effects를 수행할 수 있게 합니다.

import React, { useState, useEffect } from 'react';function Example() {
const [count, setCount] = useState(0);
// componentDidMount and componentDidUpdate 와 비슷합니다
useEffect(() => {
// browser API를 이용해서 document title를 업데이트 합니다
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

This snippet is based on the counter example from the previous page, but we added a new feature to it: we set the document title to a custom message including the number of clicks.
이 스니펫은 이전 페이지의 counter 예제를 이용합니다. 여기에 새로운 기능을 추가하였습니다. 클릭한 숫자를 포함한 커스텀 메세지를 document title로 업데이트 합니다.

리액트 컴포넌트에서 Data 받기, 구독 설정, DOM 수동 조작은 side effects의 예제 입니다. 이 기능을 side effects 라고 부르는데 익숙하지 않을 수 있는데, 당신은 이미 컴포넌트에서 수행 했었을 것입니다.

Tip

만약에 리액트 라이프사이클 함수에 익숙하다면 useEffectcomponentDidMount, componentDidUpdate, componentWillUnmount 의 조합이라고 생각하면 됩니다.

리액트 컴포넌트에서 두개의 일반적인 side effects가 있습니다. 이들은 cleanup을 요구하기도 하고 그렇지 않기도 합니다. 이 차이에 대해서 더 자세히 알아 보겠습니다.

Cleanup 없는 Effects

때때로 우리는 리액트가 DOM 업데이트를 마친 뒤에 추가적으로 코드를 실행해야 합니다. 네트워크 요청, DOM 조작 그리고 로깅은 cleanup을 요구하지 않는 effect의 일반적인 예입니다. 우리는 이러한 코드들을 유지하지 않고 실행 할 수 있기 때문이라고 설명합니다. 클래스와 훅이 이러한 side effects를 어떻게 실행시키는지 비교해보겠습니다.

클래스 사용 예제

클래스 컴포넌트에서는render 함수가 side effect를 스스로 실행할 수 없습니다. 우리는 일반적으로 리액트가 DOM을 업데이트한 이후에 effects를 실행시키기를 원하는데, render 함수는 이에 비하면 너무 빨리 실행됩니다.

이것이 리액트 클래스의 componentDidMouncomponentDidUpdate안에 side effects를 넣는 이유입니다. 예제로 다시 돌아와서, 리액트가 DOM 업데이트를 한 이후에 document title 을 업데이트하는 리액트 counter 클래스 컴포넌트가 있습니다.

class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}

이 두 lifecycle 함수에서 어떻게 중복 코드를 갖게 되었는지 주목하세요.

이는 많은 경우에서 컴포넌트가 mount 되었거나 업데이트 되었을 때 모두 같은 side effect가 수행되기를 원했기 때문입니다. 개념적으로는 모든 render 함수 이후에 발생 되기를 희망하지만, 리액트 클래스 컴포넌트에는 이런 기능을 하는 함수가 없습니다. 별도의 함수로 추출할 수 있지만 여전히 두 곳에서 호출해야 합니다.

이제부터는 useEffect Hook을 이용해서 같은 기능을 구현하겠습니다.

Hooks를 이용한 예제

우리는 이미 이 페이지의 윗부분에서 이 예제를 봤습니다. 좀 더 자세히 볼까요?

import React, { useState, useEffect } from 'react';function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

useEffect 는 무엇을 합니까? 이 훅을 이용하므로써, 당신은 리액트에 render 이후에 무엇인가를 해야 한다고 명령하게 됩니다. 리액트는 당신이 전달한 함수(이를 effect라고 간주합니다)를 기억하게 됩니다. 그리고 DOM 업데이트를 수행한 이후에 그것을 호출할 것입니다. 이 effect안에서 document title 를 설정합니다. data fetching 이나 중요한 API를 호출하는 것도 가능합니다.

useEffect은 컴포넌트 안에서 호출되나요? useEffect 를 컴포넌트 안에 위치시키는 것은 count state 변수에 접근하게 합니다(또는 다른 props). 별도의 다른 API를 사용하지 않아도 됩니다. 이미 함수 scope 안에 있습니다. Hooks 는 javascript의 클로저를 수용하고 javascript가 이미 솔루션을 제공하는 React 관련 api를 피하도록 합니다.

useEffect 모든 render 이후에 실행 되나요? 기본적으로는 그렇습니다. 첫번째 render나 이후에 모든 update에서 실행 됩니다. (나중에 어떻게 커스텀하는지 다룰 것입니다.) “mounting” and “updating” 의 개념에서 생각하는 대신에 render 이후에 effects 가 발생된다고 생각하는 것이 더 쉬울 것입니다. 리액트는 DOM이 update 될 때마다 effects를 실행하는 것을 보장합니다.

자세한 설명

이제 effects에 대해서 더 많이 알게 되었으니, 이 코드들이 이해가 될 것입니다.

function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});

count state 변수를 선언하였고, effect를 사용합니다. useEffect Hook에 함수를 전달합니다. 전달한 함수가 우리의 effect 입니다. effect 안에서, document.title browser API를 이용해서 브라우저 타이틀을 설정합니다. We can read the latest count inside the effect because it’s in the scope of our function. When React renders our component, it will remember the effect we used, and then run our effect after updating the DOM. This happens for every render, including the first one.

Experienced JavaScript developers might notice that the function passed to useEffect is going to be different on every render. This is intentional. In fact, this is what lets us read the count value from inside the effect without worrying about it getting stale. Every time we re-render, we schedule a different effect, replacing the previous one. In a way, this makes the effects behave more like a part of the render result — each effect “belongs” to a particular render. We will see more clearly why this is useful later on this page.

Tip

Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffectdon’t block the browser from updating the screen. This makes your app feel more responsive. The majority of effects don’t need to happen synchronously. In the uncommon cases where they do (such as measuring the layout), there is a separate useLayoutEffect Hook with an API identical to useEffect.

Effects with Cleanup

Earlier, we looked at how to express side effects that don’t require any cleanup. However, some effects do. For example, we might want to set up a subscription to some external data source. In that case, it is important to clean up so that we don’t introduce a memory leak! Let’s compare how we can do it with classes and with Hooks.

Example Using Classes

In a React class, you would typically set up a subscription in componentDidMount, and clean it up in componentWillUnmount. For example, let’s say we have a ChatAPI module that lets us subscribe to a friend’s online status. Here’s how we might subscribe and display that status using a class:

class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}

Notice how componentDidMount and componentWillUnmount need to mirror each other. Lifecycle methods force us to split this logic even though conceptually code in both of them is related to the same effect.

Note

Eagle-eyed readers may notice that this example also needs a componentDidUpdatemethod to be fully correct. We’ll ignore this for now but will come back to it in a later sectionof this page.

Example Using Hooks

Let’s see how we could write this component with Hooks.

You might be thinking that we’d need a separate effect to perform the cleanup. But code for adding and removing a subscription is so tightly related that useEffect is designed to keep it together. If your effect returns a function, React will run it when it is time to clean up:

import React, { useState, useEffect } from 'react';function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}

Why did we return a function from our effect? This is the optional cleanup mechanism for effects. Every effect may return a function that cleans up after it. This lets us keep the logic for adding and removing subscriptions close to each other. They’re part of the same effect!

When exactly does React clean up an effect? React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time. We’ll discuss why this helps avoid bugs and how to opt out of this behavior in case it creates performance issues later below.

Note

We don’t have to return a named function from the effect. We called it cleanup here to clarify its purpose, but you could return an arrow function or call it something different.

Recap

We’ve learned that useEffect lets us express different kinds of side effects after a component renders. Some effects might require cleanup so they return a function:

useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});

Other effects might not have a cleanup phase, and don’t return anything.

useEffect(() => {
document.title = `You clicked ${count} times`;
});

The Effect Hook unifies both use cases with a single API.

If you feel like you have a decent grasp on how the Effect Hook works, or if you feel overwhelmed, you can jump to the next page about Rules of Hooks now.

Tips for Using Effects

We’ll continue this page with an in-depth look at some aspects of useEffect that experienced React users will likely be curious about. Don’t feel obligated to dig into them now. You can always come back to this page to learn more details about the Effect Hook.

Tip: Use Multiple Effects to Separate Concerns

One of the problems we outlined in the Motivation for Hooks is that class lifecycle methods often contain unrelated logic, but related logic gets broken up into several methods. Here is a component that combines the counter and the friend status indicator logic from the previous examples:

class FriendStatusWithCounter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0, isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
// ...

Note how the logic that sets document.title is split between componentDidMount and componentDidUpdate. The subscription logic is also spread between componentDidMountand componentWillUnmount. And componentDidMount contains code for both tasks.

So, how can Hooks solve this problem? Just like you can use the State Hook more than once, you can also use several effects. This lets us separate unrelated logic into different effects:

function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}

Hooks let us split the code based on what it is doing rather than a lifecycle method name. React will apply every effect used by the component, in the order they were specified.

Explanation: Why Effects Run on Each Update

If you’re used to classes, you might be wondering why the effect cleanup phase happens after every re-render, and not just once during unmounting. Let’s look at a practical example to see why this design helps us create components with fewer bugs.

Earlier on this page, we introduced an example FriendStatus component that displays whether a friend is online or not. Our class reads friend.id from this.props, subscribes to the friend status after the component mounts, and unsubscribes during unmounting:

componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}

But what happens if the friend prop changes while the component is on the screen? Our component would continue displaying the online status of a different friend. This is a bug. We would also cause a memory leak or crash when unmounting since the unsubscribe call would use the wrong friend ID.

In a class component, we would need to add componentDidUpdate to handle this case:

componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentDidUpdate(prevProps) {
// Unsubscribe from the previous friend.id
ChatAPI.unsubscribeFromFriendStatus(
prevProps.friend.id,
this.handleStatusChange
);
// Subscribe to the next friend.id
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}

Forgetting to handle componentDidUpdate properly is a common source of bugs in React applications.

Now consider the version of this component that uses Hooks:

function FriendStatus(props) {
// ...
useEffect(() => {
// ...
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});

It doesn’t suffer from this bug. (But we also didn’t make any changes to it.)

There is no special code for handling updates because useEffect handles them by default. It cleans up the previous effects before applying the next effects. To illustrate this, here is a sequence of subscribe and unsubscribe calls that this component could produce over time:

// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // Run first effect
// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // Run next effect
// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // Run next effect
// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effect

This behavior ensures consistency by default and prevents bugs that are common in class components due to missing update logic.

Tip: Optimizing Performance by Skipping Effects

In some cases, cleaning up or applying the effect after every render might create a performance problem. In class components, we can solve this by writing an extra comparison with prevProps or prevState inside componentDidUpdate:

componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}

This requirement is common enough that it is built into the useEffect Hook API. You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect:

useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

In the example above, we pass [count] as the second argument. What does this mean? If the count is 5, and then our component re-renders with count still equal to 5, React will compare [5] from the previous render and [5] from the next render. Because all items in the array are the same (5 === 5), React would skip the effect. That’s our optimization.

When we render with count updated to 6, React will compare the items in the [5] array from the previous render to items in the [6] array from the next render. This time, React will re-apply the effect because 5 !== 6. If there are multiple items in the array, React will re-run the effect even if just one of them is different.

This also works for effects that have a cleanup phase:

useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]); // Only re-subscribe if props.friend.id changes

In the future, the second argument might get added automatically by a build-time transformation.

Note

If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders. Learn more about how to deal with functions and what to do when the array changes too often.

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.

If you pass an empty array ([]), the props and state inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model, there are usually bettersolutions to avoid re-running effects too often. Also, don’t forget that React defers running useEffect until after the browser has painted, so doing extra work is less of a problem.

We recommend using the exhaustive-deps rule as part of our eslint-plugin-react-hooks package. It warns when dependencies are specified incorrectly and suggests a fix.

— — — — — — — — -

Hooks are JavaScript functions, but you need to follow two rules when using them. We provide a linter plugin to enforce these rules automatically:

Only Call Hooks at the Top Level

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, we’ll explain this in depth below.)

Only Call Hooks from React Functions

Don’t call Hooks from regular JavaScript functions. Instead, you can:

  • ✅ Call Hooks from React function components.
  • ✅ Call Hooks from custom Hooks (we’ll learn about them on the next page).

By following this rule, you ensure that all stateful logic in a component is clearly visible from its source code.

ESLint Plugin

We released an ESLint plugin called eslint-plugin-react-hooks that enforces these two rules. You can add this plugin to your project if you’d like to try it:

npm install eslint-plugin-react-hooks --save-dev// Your ESLint configuration
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
"react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
}
}

In the future, we intend to include this plugin by default into Create React App and similar toolkits.

You can skip to the next page explaining how to write your own Hooks now. On this page, we’ll continue by explaining the reasoning behind these rules.

Explanation

As we learned earlier, we can use multiple State or Effect Hooks in a single component:

function Form() {
// 1. Use the name state variable
const [name, setName] = useState('Mary');
// 2. Use an effect for persisting the form
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
// 3. Use the surname state variable
const [surname, setSurname] = useState('Poppins');
// 4. Use an effect for updating the title
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
// ...
}

So how does React know which state corresponds to which useState call? The answer is that React relies on the order in which Hooks are called. Our example works because the order of the Hook calls is the same on every render:

// ------------
// First render
// ------------
useState('Mary') // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm) // 2. Add an effect for persisting the form
useState('Poppins') // 3. Initialize the surname state variable with 'Poppins'
useEffect(updateTitle) // 4. Add an effect for updating the title
// -------------
// Second render
// -------------
useState('Mary') // 1. Read the name state variable (argument is ignored)
useEffect(persistForm) // 2. Replace the effect for persisting the form
useState('Poppins') // 3. Read the surname state variable (argument is ignored)
useEffect(updateTitle) // 4. Replace the effect for updating the title
// ...

As long as the order of the Hook calls is the same between renders, React can associate some local state with each of them. But what happens if we put a Hook call (for example, the persistForm effect) inside a condition?

// 🔴 We're breaking the first rule by using a Hook in a condition
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}

The name !== '' condition is true on the first render, so we run this Hook. However, on the next render the user might clear the form, making the condition false. Now that we skip this Hook during rendering, the order of the Hook calls becomes different:

useState('Mary')           // 1. Read the name state variable (argument is ignored)
// useEffect(persistForm) // 🔴 This Hook was skipped!
useState('Poppins') // 🔴 2 (but was 3). Fail to read the surname state variable
useEffect(updateTitle) // 🔴 3 (but was 4). Fail to replace the effect

React wouldn’t know what to return for the second useState Hook call. React expected that the second Hook call in this component corresponds to the persistForm effect, just like during the previous render, but it doesn’t anymore. From that point, every next Hook call after the one we skipped would also shift by one, leading to bugs.

This is why Hooks must be called on the top level of our components. If we want to run an effect conditionally, we can put that condition inside our Hook:

useEffect(function persistForm() {
// 👍 We're not breaking the first rule anymore
if (name !== '') {
localStorage.setItem('formData', name);
}
});

Note that you don’t need to worry about this problem if you use the provided lint rule.But now you also know why Hooks work this way, and which issues the rule is preventing.

— — — — — — — — — — — —

Building your own Hooks lets you extract component logic into reusable functions.

When we were learning about using the Effect Hook, we saw this component from a chat application that displays a message indicating whether a friend is online or offline:

import React, { useState, useEffect } from 'react';function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}

Now let’s say that our chat application also has a contact list, and we want to render names of online users with a green color. We could copy and paste similar logic above into our FriendListItem component but it wouldn’t be ideal:

import React, { useState, useEffect } from 'react';function FriendListItem(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}

Instead, we’d like to share this logic between FriendStatus and FriendListItem.

Traditionally in React, we’ve had two popular ways to share stateful logic between components: render props and higher-order components. We will now look at how Hooks solve many of the same problems without forcing you to add more components to the tree.

Extracting a Custom Hook

When we want to share logic between two JavaScript functions, we extract it to a third function. Both components and Hooks are functions, so this works for them too!

A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks. For example, useFriendStatus below is our first custom Hook:

import React, { useState, useEffect } from 'react';function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}

There’s nothing new inside of it — the logic is copied from the components above. Just like in a component, make sure to only call other Hooks unconditionally at the top level of your custom Hook.

Unlike a React component, a custom Hook doesn’t need to have a specific signature. We can decide what it takes as arguments, and what, if anything, it should return. In other words, it’s just like a normal function. Its name should always start with use so that you can tell at a glance that the rules of Hooks apply to it.

The purpose of our useFriendStatus Hook is to subscribe us to a friend’s status. This is why it takes friendID as an argument, and returns whether this friend is online:

function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ... return isOnline;
}

Now let’s see how we can use our custom Hook.

Using a Custom Hook

In the beginning, our stated goal was to remove the duplicated logic from the FriendStatusand FriendListItem components. Both of them want to know whether a friend is online.

Now that we’ve extracted this logic to a useFriendStatus hook, we can just use it:

function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}

Is this code equivalent to the original examples? Yes, it works in exactly the same way. If you look closely, you’ll notice we didn’t make any changes to the behavior. All we did was to extract some common code between two functions into a separate function. Custom Hooks are a convention that naturally follows from the design of Hooks, rather than a React feature.

Do I have to name my custom Hooks starting with “use”? Please do. This convention is very important. Without it, we wouldn’t be able to automatically check for violations of rules of Hooks because we couldn’t tell if a certain function contains calls to Hooks inside of it.

Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.

How does a custom Hook get isolated state? Each call to a Hook gets isolated state. Because we call useFriendStatus directly, from React’s point of view our component just calls useState and useEffect. And as we learned earlier, we can call useState and useEffect many times in one component, and they will be completely independent.

Tip: Pass Information Between Hooks

Since Hooks are functions, we can pass information between them.

To illustrate this, we’ll use another component from our hypothetical chat example. This is a chat message recipient picker that displays whether the currently selected friend is online:

const friendList = [
{ id: 1, name: 'Phoebe' },
{ id: 2, name: 'Rachel' },
{ id: 3, name: 'Ross' },
];
function ChatRecipientPicker() {
const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);
return (
<>
<Circle color={isRecipientOnline ? 'green' : 'red'} />
<select
value={recipientID}
onChange={e => setRecipientID(Number(e.target.value))}
>
{friendList.map(friend => (
<option key={friend.id} value={friend.id}>
{friend.name}
</option>
))}
</select>
</>
);
}

We keep the currently chosen friend ID in the recipientID state variable, and update it if the user chooses a different friend in the <select> picker.

Because the useState Hook call gives us the latest value of the recipientID state variable, we can pass it to our custom useFriendStatus Hook as an argument:

const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);

This lets us know whether the currently selected friend is online. If we pick a different friend and update the recipientID state variable, our useFriendStatus Hook will unsubscribe from the previously selected friend, and subscribe to the status of the newly selected one.

useYourImagination()

Custom Hooks offer the flexibility of sharing logic that wasn’t possible in React components before. You can write custom Hooks that cover a wide range of use cases like form handling, animation, declarative subscriptions, timers, and probably many more we haven’t considered. What’s more, you can build Hooks that are just as easy to use as React’s built-in features.

Try to resist adding abstraction too early. Now that function components can do more, it’s likely that the average function component in your codebase will become longer. This is normal — don’t feel like you have to immediately split it into Hooks. But we also encourage you to start spotting cases where a custom Hook could hide complex logic behind a simple interface, or help untangle a messy component.

For example, maybe you have a complex component that contains a lot of local state that is managed in an ad-hoc way. useState doesn’t make centralizing the update logic any easier so might you prefer to write it as a Redux reducer:

function todosReducer(state, action) {
switch (action.type) {
case 'add':
return [...state, {
text: action.text,
completed: false
}];
// ... other actions ...
default:
return state;
}
}

Reducers are very convenient to test in isolation, and scale to express complex update logic. You can further break them apart into smaller reducers if necessary. However, you might also enjoy the benefits of using React local state, or might not want to install another library.

So what if we could write a useReducer Hook that lets us manage the local state of our component with a reducer? A simplified version of it might look like this:

function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}

Now we could use it in our component, and let the reducer drive its state management:

function Todos() {
const [todos, dispatch] = useReducer(todosReducer, []);
function handleAddClick(text) {
dispatch({ type: 'add', text });
}
// ...
}

The need to manage local state with a reducer in a complex component is common enough that we’ve built the useReducer Hook right into React. You’ll find it together with other built-in Hooks in the Hooks API reference.

— — — — — — — — -

--

--

No responses yet