Allows you to declare an action on your model. An action is used to perform updates on your store.

addTodo: action((state, payload) => {


  • handler (Function, required)

    The handler for your action. It will receive the following arguments:

    • state (Object)

      The part of the state tree that the action is against. You can mutate this state value directly as required by the action. Under the hood we convert these mutations into an update against the Redux store.

    • payload (any)

      The payload, if any, that was provided to the action when it was dispatched.

Actions are synchronous

Actions are executed synchronously, therefore, you can immediately query your store to see the updated state.

store.getActions().todos.addTodo('Learn Easy Peasy');

// ["Learn Easy Peasy"]

Debugging Actions

Ensure you have the Redux Dev Tools extension installed. This will allow you to see your dispatched actions, with their payload and the effect that they had on your state.


This example demonstrates how to both define an action and consume it from a React component.

import { action, createStore, useStoreActions } from 'easy-peasy';

const store = createStore({
  total: 0,
  add: action((state, payload) => { += payload;

function Add10Button() {
  const add = useStoreActions(actions => actions.add);
  return <button onClick={() => add(10)}>Add 10</button>;

Using console.log within actions

Despite the Redux Dev Tools extension being available there may be cases in which you would like to perform a console.log within the body of your actions to aid debugging.

If you try to do so you may note that a Proxy object is printed out instead of your expected state. This is due to us using immer under the hood, which allows us to track mutation updates to the state and then convert them to immutable updates.

To get around this you can use the debug utility.

import { debug } from 'easy-peasy';

const model = {
  myAction: action((state, payload) => {

Don't destructure the state argument

In order to support the mutation API we utilise immer. Under the hood immer utilises Proxies in order to track our mutations, converting them into immutable updates. Therefore if you destructure the state that is provided to your action you break out of the Proxy, after which any update you perform to the state will not be applied.

Below are a couple examples of this antipattern.

addTodo: action(({ items }, payload) => {


addTodo: action((state, payload) => {
  const { items } = state;

Switching to an immutable API

By default we use immer to provide a mutation based API.

This is completely optional, you can instead return new state from your actions like below.

import { action } from 'easy-peasy';

const model = {
  todos: [],
  addTodo: action((state, payload) => {
    // 👇 new immutable state returned
    return [...state, payload];

Should you prefer this approach you can explicitly disable immer via the disableImmer configuration value of createStore.

import { createStore } from 'easy-peasy';

const store = createStore(model, {
  disableImmer: true // 👈 set the flag