---
title: useReducer Hook
description: "Details about the useReducer React hook in ReScript"
canonical: "/docs/react/latest/hooks-reducer"
---

# useReducer

<Intro>

`React.useReducer` helps you express your state in an action / reducer pattern.

</Intro>

## Usage

<CodeTab labels={["ReScript", "JS Output"]}>

```res
let (state, dispatch) = React.useReducer(reducer, initialState)
```

```js
var match = React.useReducer(reducer, initialState);
```

</CodeTab>

An alternative to [useState](./hooks-state). Accepts a reducer of type `(state, action) => newState`, and returns the current `state` paired with a `dispatch` function `(action) => unit`. 

`React.useReducer` is usually preferable to `useState` when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. `useReducer` also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

**Note:** You will notice that the action / reducer pattern works especially well in ReScript due to its [immutable records](/docs/manual/latest/record), [variants](/docs/manual/latest/variant) and [pattern matching](/docs/manual/latest/pattern-matching-destructuring) features for easy expression of your action and state transitions.

## Examples

### Counter Example with `React.useReducer`

<CodeTab labels={["ReScript", "JS Output"]}>

```res
// Counter.res

type action = Inc | Dec
type state = {count: int}

let reducer = (state, action) => {
  switch action {
  | Inc => {count: state.count + 1}
  | Dec => {count: state.count - 1}
  }
}

@react.component
let make = () => {
  let (state, dispatch) = React.useReducer(reducer, {count: 0})

  <>
    {React.string("Count:" ++ Belt.Int.toString(state.count))}
    <button onClick={(_) => dispatch(Dec)}> {React.string("-")} </button>
    <button onClick={(_) => dispatch(Inc)}> {React.string("+")} </button>
  </>
}
```

```js
function reducer(state, action) {
  if (action) {
    return {
            count: state.count - 1 | 0
          };
  } else {
    return {
            count: state.count + 1 | 0
          };
  }
}

function Counter(Props) {
  var match = React.useReducer(reducer, {
        count: 0
      });
  var dispatch = match[1];
  return React.createElement(React.Fragment, undefined, "Count:" + String(match[0].count), React.createElement("button", {
                  onClick: (function (param) {
                      return Curry._1(dispatch, /* Dec */1);
                    })
                }, "-"), React.createElement("button", {
                  onClick: (function (param) {
                      return Curry._1(dispatch, /* Inc */0);
                    })
                }, "+"));
}
```

</CodeTab>

> React guarantees that dispatch function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.

### Basic Todo List App with More Complex Actions

You can leverage the full power of variants to express actions with data payloads to parametrize your state transitions:

<CodeTab labels={["ReScript", "JS Output"]}>

```res
// TodoApp.res

type todo = {
  id: int,
  content: string,
  completed: bool,
}

type action =
  | AddTodo(string)
  | RemoveTodo(int)
  | ToggleTodo(int)

type state = {
  todos: array<todo>,
  nextId: int,
}

let reducer = (state, action) =>
  switch action {
  | AddTodo(content) =>
    let todos = Js.Array2.concat(
      state.todos,
      [{id: state.nextId, content: content, completed: false}],
    )
    {todos: todos, nextId: state.nextId + 1}
  | RemoveTodo(id) =>
    let todos = Js.Array2.filter(state.todos, todo => todo.id !== id)
    {...state, todos: todos}
  | ToggleTodo(id) =>
    let todos = Belt.Array.map(state.todos, todo =>
      if todo.id === id {
        {
          ...todo,
          completed: !todo.completed,
        }
      } else {
        todo
      }
    )
    {...state, todos: todos}
  }

let initialTodos = [{id: 1, content: "Try ReScript & React", completed: false}]

@react.component
let make = () => {
  let (state, dispatch) = React.useReducer(
    reducer,
    {todos: initialTodos, nextId: 2},
  )

  let todos = Belt.Array.map(state.todos, todo =>
    <li>
      {React.string(todo.content)}
      <button onClick={_ => dispatch(RemoveTodo(todo.id))}>
        {React.string("Remove")}
      </button>
      <input
        type_="checkbox"
        checked=todo.completed
        onChange={_ => dispatch(ToggleTodo(todo.id))}
      />
    </li>
  )

  <> <h1> {React.string("Todo List:")} </h1> <ul> {React.array(todos)} </ul> </>
}
```

```js
function reducer(state, action) {
  switch (action.TAG | 0) {
    case /* AddTodo */0 :
        var todos = state.todos.concat([{
                id: state.nextId,
                content: action._0,
                completed: false
              }]);
        return {
                todos: todos,
                nextId: state.nextId + 1 | 0
              };
    case /* RemoveTodo */1 :
        var id = action._0;
        var todos$1 = state.todos.filter(function (todo) {
              return todo.id !== id;
            });
        return {
                todos: todos$1,
                nextId: state.nextId
              };
    case /* ToggleTodo */2 :
        var id$1 = action._0;
        var todos$2 = Belt_Array.map(state.todos, (function (todo) {
                if (todo.id === id$1) {
                  return {
                          id: todo.id,
                          content: todo.content,
                          completed: !todo.completed
                        };
                } else {
                  return todo;
                }
              }));
        return {
                todos: todos$2,
                nextId: state.nextId
              };
    
  }
}

var initialTodos = [{
    id: 1,
    content: "Try ReScript & React",
    completed: false
  }];

function TodoApp(Props) {
  var match = React.useReducer(reducer, {
        todos: initialTodos,
        nextId: 2
      });
  var dispatch = match[1];
  var todos = Belt_Array.map(match[0].todos, (function (todo) {
          return React.createElement("li", undefined, todo.content, React.createElement("button", {
                          onClick: (function (param) {
                              return Curry._1(dispatch, {
                                          TAG: /* RemoveTodo */1,
                                          _0: todo.id
                                        });
                            })
                        }, "Remove"), React.createElement("input", {
                          checked: todo.completed,
                          type: "checkbox",
                          onChange: (function (param) {
                              return Curry._1(dispatch, {
                                          TAG: /* ToggleTodo */2,
                                          _0: todo.id
                                        });
                            })
                        }));
        }));
  return React.createElement(React.Fragment, undefined, React.createElement("h1", undefined, "Todo List:"), React.createElement("ul", undefined, todos));
}
```

</CodeTab>


## Lazy Initialization



<CodeTab labels={["ReScript", "JS Output"]}>

```res
let (state, dispatch) =
  React.useReducerWithMapState(reducer, initialState, initial)
```

```js
var match = React.useReducer(reducer, initialState, init);
```

</CodeTab>

You can also create the `initialState` lazily. To do this, you can use `React.useReducerWithMapState` and pass an `init` function as the third argument. The initial state will be set to `init(initialState)`.

It lets you extract the logic for calculating the initial state outside the reducer. This is also handy for resetting the state later in response to an action:

<CodeTab labels={["ReScript", "JS Output"]}>

```res
// Counter.res

type action = Inc | Dec | Reset(int)
type state = {count: int}

let init = initialCount => {
  {count: initialCount}
}

let reducer = (state, action) => {
  switch action {
  | Inc => {count: state.count + 1}
  | Dec => {count: state.count - 1}
  | Reset(count) => init(count)
  }
}

@react.component
let make = (~initialCount: int) => {
  let (state, dispatch) = React.useReducerWithMapState(
    reducer,
    initialCount,
    init,
  )

  <>
    {React.string("Count:" ++ Belt.Int.toString(state.count))}
    <button onClick={_ => dispatch(Dec)}> {React.string("-")} </button>
    <button onClick={_ => dispatch(Inc)}> {React.string("+")} </button>
  </>
}
```

```js
function init(initialCount) {
  return {
          count: initialCount
        };
}

function reducer(state, action) {
  if (typeof action === "number") {
    if (action !== 0) {
      return {
              count: state.count - 1 | 0
            };
    } else {
      return {
              count: state.count + 1 | 0
            };
    }
  } else {
    return {
            count: action._0
          };
  }
}

function Counter(Props) {
  var initialCount = Props.initialCount;
  var match = React.useReducer(reducer, initialCount, init);
  var dispatch = match[1];
  return React.createElement(React.Fragment, undefined, "Count:" + String(match[0].count), React.createElement("button", {
                  onClick: (function (param) {
                      return Curry._1(dispatch, /* Dec */1);
                    })
                }, "-"), React.createElement("button", {
                  onClick: (function (param) {
                      return Curry._1(dispatch, /* Inc */0);
                    })
                }, "+"));
}
```

</CodeTab>
