> For the complete documentation index, see [llms.txt](https://hoilzz-til.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hoilzz-til.gitbook.io/docs/devlog/0523/usestate-usereducer.md).

# useState vs useReducer

useState에 primitive 값만 관리했다.

그러다가 여러개의 아코디언의 열고닫힘 상태를 관리해야했다. 그래서 부모 컴포넌트에서 다음과 같이 상태 관리하려했다.

```javascript
const defaultOpenState = {
  acco1: true,
  acco2: true,
  acco3: true,
  acco4: true
}; // 처음엔 다 열려 있는 상태.

function Component() {
  const [openState, setOpenState] = useState(defaultOpenState);

  const handleClickAcco = useCallback(
    name => {
      setOpenState({
        ...openState,
        [name]: !openState[name]
      });
    },
    [openState]
  ); // 나는 1개의 아코디언 상태만 바꿨는데.. 4개의 아코디언의 함수가 다시 실행되는 상황..
  // 왜냐면 openState는 새로운 상태 객체를 갖기 떄문에.. useCallback deps가 콜백함수 다시 만듬

  return (
    <>
      <Acco1 onClick={handleClickAcco} />
      <Acco2 onClick={handleClickAcco} />
      <Acco3 onClick={handleClickAcco} />
      <Acco4 onClick={handleClickAcco} />
    </>
  );
}
```

useReducer로 위문제를 해결할 수 있지 않을까? 해서 관련 포스팅을 찾았다. 거기서 글들좀 발췌해보겠다. [should i useState or useReducer](https://kentcdodds.com/blog/should-i-usestate-or-usereducer)

`useReducer`보다 `useState`를 선호하는 1가지 상황이 있다.

**component를 프로토타이핑/빌딩 하고 구현히 확실하지 않을 때**

...

## useReducer가 항상 더 나은 상황일 때

상태의 1가지 요소가 다른 상태 값에 의존한다면 useReducer가 항상 나을 것이다.

[the state reducer pattern with react hooks](https://kentcdodds.com/blog/the-state-reducer-pattern-with-react-hooks)

state reducer 패턴은 제어의 역전을 허용한다. 즉, API 작성자가 API 사용자에게 내부적으로 어떻게 작동할지 권한을 주는 것이다.

> 제어의 역전 제어권을 다른 대상에게 위임하고 실행에 필요한 객체의 생성 사용 등 제어권한을 위임하는 것. 다른 시스템이 무엇읋 할지 추측하지 않아도 됨. 의존성 주입은 제어의 역전의 대표적인 방식.

## hooks로 state reducer 이용하기

컨셉은 다음과 같다.

1. End User는 action을 한다.
2. Dev는 dispatch를 호출한다.
3. hook은 필요한 변경을 결정한다.
4. hook은 추가 변경에 대해 dev 코드를 호출한다. **이것이 제어의 역전이다**
5. hook은 state 변경을 만든다.

Toggle component는 유저가 Switch 컴포넌트를 4번 이상 클릭하지 못하게 한다. (유저가 리셋 버튼을 누르면 초기화)

```javascript
function Toggle() {
  const [clicksSinceReset, setClicksSinceReset] = React.useState(0);
  const tooManyClicks = clicksSinceReset >= 4;
  const { on, toggle, setOn, setOff } = useToggle();
  function handleClick() {
    toggle();
    setClicksSinceReset(count => count + 1);
  }
  return (
    <div>
      <button onClick={setOff}>Switch Off</button>
      <button onClick={setOn}>Switch On</button>
      <Switch on={on} onClick={handleClick} />
      {tooManyClicks ? (
        <button onClick={() => setClicksSinceReset(0)}>Reset</button>
      ) : null}
    </div>
  );
}
```

이 문제에 대한 쉬운 해결책은 `handleClick` 함수에 if 문을 추가하고 `tooManyClicks`가 true면 `toggle`을 호출하지 않는다. 하지만 이 예제의 목적을 유지하자.

제어의 역전을 위해 `useToggle` hook을 변경해보는건 어떨까?

API를 먼저 생각해보자. 그리고나서 구현하자.

사용자라면 state update 될 때마다 hooks을 통해 그것이 실제로 일어나기 전에 수정해보자. like so:

```javascript
function Toggle() {
  const [clicksSinceReset, setClicksSinceReset] = React.useState(0);
  const tooManyClicks = clicksSinceReset >= 4;
  const { on, toggle, setOn, setOff } = useToggle({
    modifyStateChange(currentState, changes) {
      if (tooManyClicks) {
        // other changes are fine, but on needs to be unchanged
        return { ...changes, on: currentState.on };
      } else {
        // the changes are fine
        return changes;
      }
    }
  });
  function handleClick() {
    toggle();
    setClicksSinceReset(count => count + 1);
  }
  return (
    <div>
      <button onClick={setOff}>Switch Off</button>
      <button onClick={setOn}>Switch On</button>
      <Switch on={on} onClick={handleClick} />
      {tooManyClicks ? (
        <button onClick={() => setClicksSinceReset(0)}>Reset</button>
      ) : null}
    </div>
  );
}
```

좋아졌다.(사람들이 "switch off" or "Switch on" 버튼을 클릭할 때 일어나는 변화를 막는 거 빼고는..)

`<Switch/>`가 상태 토글하는 것을 막는 걸 구현해보자.

`modifyStateChange`가 `reducer`가 호출하도록 바꾸는 건 어떨까? 그리고 2번째 인자로 `action`을 받자.

action은 무슨 변경 타입이 일어나는지 결정하는 `type`을 가진다.

```javascript
const { on, toggle, setOn, setOff } = useToggle({
  reducer(currentState, action) {
    if (tooManyClicks && action.type === "TOGGLE") {
      // other changes are fine, but on needs to be unchanged
      return { ...action.changes, on: currentState.on };
    } else {
      // the changes are fine
      return action.changes;
    }
  }
});
```

control의 종류를 부여했다. `TOGGLE` type을 스트링으로 사용하지 말자. 대신에 참조값으로 변경하자. 이거는 typo를 피하고 editor 자동완성을 개선시켜준다.

```javascript
reducer(currentState, action) {
      if (tooManyClicks && action.type === useToggle.types.toggle) {
        // other changes are fine, but on needs to be unchanged
        return {...action.changes, on: currentState.on}
      } else {
        // the changes are fine
        return action.changes
      }
    },
```

## hooks 으로 state reducer 구현하기

완성된 API가 만족스럽다. `useToggle` hook으로 구현하는 방법을 보자.

```javascript
function useToggle() {
  const [on, setOnState] = React.useState(false);
  const toggle = () => setOnState(o => !o);
  const setOn = () => setOnState(true);
  const setOff = () => setOnState(false);
  return { on, toggle, setOn, setOff };
}
```

이 helper function 에 로직을 추가할 수 있다. 하지만 로직 추가하게 되면 단순한 훅일지라도 조금 성가신다.

대신에, `useState`에서 `useReducer`로 재작성할 것이다. 그리고 많이 쉬워진 구현체로 만들 것이다.

```javascript
function toggleReducer(state, action) {
  switch (action.type) {
    case "TOGGLE": {
      return { on: !state.on };
    }
    case "ON": {
      return { on: true };
    }
    case "OFF": {
      return { on: false };
    }
    default: {
      throw new Error(`Unhandled type: ${action.type}`);
    }
  }
}
function useToggle() {
  // useState를 useReducer로 변경.
  const [{ on }, dispatch] = React.useReducer(toggleReducer, { on: false });
  const toggle = () => dispatch({ type: "TOGGLE" });
  const setOn = () => dispatch({ type: "ON" });
  const setOff = () => dispatch({ type: "OFF" });
  return { on, toggle, setOn, setOff };
}
```

이제 문자열 타입을 제거하고 `type` 프로퍼티를 추가하자.

```javascript
function toggleReducer(state, action) {
  switch (action.type) {
    case useToggle.types.toggle: {
      return { on: !state.on };
    }
    case useToggle.types.on: {
      return { on: true };
    }
    case useToggle.types.off: {
      return { on: false };
    }
    default: {
      throw new Error(`Unhandled type: ${action.type}`);
    }
  }
}

function useToggle() {
  const [{ on }, dispatch] = React.useReducer(toggleReducer, { on: false });
  const toggle = () => dispatch({ type: useToggle.types.toggle });
  const setOn = () => dispatch({ type: useToggle.types.on });
  const setOff = () => dispatch({ type: useToggle.types.off });
  return { on, toggle, setOn, setOff };
}
useToggle.types = {
  toggle: "TOGGLE",
  on: "ON",
  off: "OFF"
};
```

이제 유저는 `useToggle` 함수에 환경설정 객체로 `reducer`를 전달할 수 있다.

```javascript
function useToggle({ reducer }) {
  const [{ on }, dispatch] = React.useReducer(toggleReducer, { on: false });
  const toggle = () => dispatch({ type: useToggle.types.toggle });
  const setOn = () => dispatch({ type: useToggle.types.on });
  const setOff = () => dispatch({ type: useToggle.types.off });
  return { on, toggle, setOn, setOff };
}
```

자 이제 개발자가 정의한 `reducer`를 가진다. 우리 리듀서와 어떻게 합칠 수 있을까?

Well remember that the developer needs to know what our changes will be, so we'll definitely need to determine those changes first. Let's make an inline reducer:

개발자는 무엇이 변경되는지 알아야 하기 떄문에, 우리가 먼저 그 변경들을 결정할 필요가 있다는 것을 기억하자.

일단 인라인 리듀서를 만들자.

```javascript
function useToggle({ reducer }) {
  const [{ on }, dispatch] = React.useReducer(
    (state, action) => {
      const changes = toggleReducer(state, action);
      return changes;
    },
    { on: false }
  );
  const toggle = () => dispatch({ type: useToggle.types.toggle });
  const setOn = () => dispatch({ type: useToggle.types.on });
  const setOff = () => dispatch({ type: useToggle.types.off });
  return { on, toggle, setOn, setOff };
}
```

직관적인 리팩토링이다. toggle hook의 기능은 변경되지 않았다. (실제로 블랙박스와 세부 구현체의 매직..)

```javascript
function useToggle({reducer}) {
  const [{on}, dispatch] = React.useReducer(
    (state, action) => {
      const changes = toggleReducer(state, action)
      return reducer(state, {...action, changes})
    },
    {on: false},
  )
  ...
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://hoilzz-til.gitbook.io/docs/devlog/0523/usestate-usereducer.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
