Simplify React state management with Kea
Tags: react, tutorial, webdev
Published: 2020-03-27 16:15:08

---
title: Simplify React state management with Kea
published: true
date: 2020-03-10 16:00:35 UTC
tags: react,tutorial,webdev
canonical_url: https://blog.logrocket.com/simplify-react-state-management-with-kea/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/388wk5x76w3o3go8kpve.png
---
**Written by [John Au-Yeung](https://blog.logrocket.com/author/johnau-yeung/)**✏️

There are a few ways to share data between React components. First, we can pass data from parent to child via props. React also has the context API to pass data between components with any relationship as long as we wrap the context provider component inside the React components that we want to share data between.

We also have global state management solutions like [Redux](https://redux.js.org/) and[MobX](https://mobx.js.org/README.html) which let us share data easily within the entire app.

Any component that wants to get the latest value of a state can subscribe to a data store with a global state management solution.

Another state management solution is [Kea](https://kea.js.org/), which works similarly to Redux. We can subscribe to a store created with Kea to get data and set the latest state. Kea is powered by Redux, so lots of concepts like reducers and stores will be used with Kea as well.

In this article, we’ll look at how to use Kea in a React app as a global state management solution.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Basic state management

We can get started by creating an app with create -react-app by running:

```jsx
npx create-react-app kea-app
```

Then we can install the libraries needed by Kea, which is Kea itself, Redux, and React-Redux. To install them we run the following code:

```jsx
npm i kea redux react-redux reselect
```

Then we can write a simple app with Kea as our app-wide global state management solution by writing the following code:

```jsx
//index.js
import React from "react";
import ReactDOM from "react-dom";
import { resetContext, getContext } from "kea";
import { Provider } from "react-redux";
resetContext({
  createStore: {},
  plugins: []
});

import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={getContext().store}>
    <App />
  </Provider>,
  rootElement
);
```

```jsx
//App.js
import React from "react";
import { kea, useActions, useValues } from "kea";

const logic = kea({
  actions: () => ({
    setName: name => ({ name })
  }),

  reducers: ({ actions }) => ({
    name: [
      "",
      {
        [actions.setName]: (_, payload) => payload.name
      }
    ]
  })
});

const Name = () => {
  const { name } = useValues(logic);
  return <p>{name}</p>;
};

export default function App() {
  const { setName } = useActions(logic);
  return (
    <div className="App">
      <input onChange={e => setName(e.target.value)} />
      <Name />
    </div>
  );
}
```

In the code above, we imported the React Redux’s `Provider` component and then wrapped it around our whole app to let Kea work as the app-wide state management library.

However, we pass in `getContext().store` as the value of the store instead of a Redux store as we usually do. We leave the `createStore` and `plugins` properties with an empty object and array in the object that we pass into `resetContext` since we aren’t using any plugins and isn’t changing any options when we create the store.

Then in `App.js`, we create an object with the `kea` function which has the logic that we’ll use in our store. It included logic for both retrieving and setting values for our store.

We have the following in `App.js` to create the `logic` object that we’ll use to read and write values from the store:

```jsx
const logic = kea({
  actions: () => ({
    setName: name => ({ name })
  }),

  reducers: ({ actions }) => ({
    name: [
      "",
      {
        [actions.setName]: (_, payload) => payload.name
      }
    ]
  })
});
```

We have the `actions` property with the methods that we’ll use to set the value of the `name` state in the store. The `reducers` property has the action name as the key of the object.

The first entry of the reducer array is the default value of it.

It uses the name of the function as the identifier for the reducer function that we have in the object of the second entry of the array of the reducer. Like a Redux reducer, we return the value that we want to set in the store with the reducer function.

Then we set the `name` value in the store by calling the Kea’s `useActions` function with the `logic` object passed in. It has the `setName` method that we can call with the object that it returns.

In the input element of `App`, we call `setName` to set the value of `name` to the inputted value.

Then in the `Name` component, we called Kea’s `useValues` method with the `logic` object that we created earlier as the argument and then get the `name` value from the store and render it.

Therefore then the text that’s typed into the input will show in the `Name` component below it.

## Listeners

Listeners are functions that run after an action is dispatched. They’re useful if we want to be able to cancel these actions that are within listeners.

To use it, we can add the `kea-listeners` package by running:

```jsx
npm i kea-listeners
```

We can use it to listen to an action that’s being performed by Kea and then use that to trigger another action as follows:

```jsx
//index.js
import React from "react";
import ReactDOM from "react-dom";
import { resetContext, getContext } from "kea";
import { Provider } from "react-redux";
import listeners from "kea-listeners";
import App from "./App";

resetContext({
  createStore: {},
  plugins: [listeners]
});

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={getContext().store}>
    <App />
  </Provider>,
  rootElement
);
```

```jsx
//App.js
import React from "react";
import { kea, useActions, useValues } from "kea";

const logic = kea({
  actions: () => ({
    setCount: count => ({ count }),
    setDoubleCount: doubleCount => ({ doubleCount })
  }),

  listeners: ({ actions, values, store, sharedListeners }) => ({
    [actions.setCount]: ({ count }) => {
      actions.setDoubleCount(count * 2);
    }
  }),

  reducers: ({ actions }) => ({
    count: [
      0,
      {
        [actions.setCount]: (_, payload) => payload.count
      }
    ],
    doubleCount: [
      0,
      {
        [actions.setDoubleCount]: (_, payload) => payload.doubleCount
      }
    ]
  })
});

const Count = () => {
  const { count, doubleCount } = useValues(logic);
  return (
    <p>
      {count} {doubleCount}
    </p>
  );
};

export default function App() {
  const { count } = useValues(logic);
  const { setCount } = useActions(logic);
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <Count />
    </div>
  );
```

In the code above, we added the `listeners` plugin by adding the `listeners` plugin to the array that we set as the value of the `plugins` property in `index.js`.

Then we can listen to the `actions.setCount` action as it’s being run in the `listeners` property. The `listeners` property is set to an object that takes an object with the `actions`, `values`, `store`, and `sharedListeners` properties.

In the example above, we called the `setDoubleCount` action by accessing the action method with the `actions` property.

We also defined the `doubleCount` reducer so that we can call the `setDoubleCount` action, as we did above,  to update the value of the `doubleCount` state. Then in the `Count` component, we call `useValues` with `logic` to get both `count` and `doubleCount` and display the values.

Therefore, when we click the Increment button, we get one count that increments by 1, which is `count`, and another one that increments by 2, which is `doubleCount`.

## Canceling actions

We can add a `breakpoint` method call, which returns a promise to wait for a specified number of milliseconds where we can cancel the action if the same action is called again.

For instance, we can write the following code to create a cancellable action:

```jsx
//App.js
import React from "react";
import { kea, useActions, useValues } from "kea";

const logic = kea({
  actions: () => ({
    setName: name => ({ name }),
    setResult: result => ({ result })
  }),
  listeners: ({ actions, values, store, sharedListeners }) => ({
    [actions.setName]: async ({ name }, breakpoint) => {
      await breakpoint(3000);
      const res = await fetch(`https://api.agify.io?name=${name}
      `);
      breakpoint();
      actions.setResult(await res.json());
    }
  }),

  reducers: ({ actions }) => ({
    name: [
      "",
      {
        [actions.setName]: (_, payload) => payload.name
      }
    ],
    result: [
      "",
      {
        [actions.setResult]: (_, payload) => payload.result
      }
    ]
  })
});

export default function App() {
  const { result } = useValues(logic);
  const { setName } = useActions(logic);
  return (
    <div className="App">
      <input onChange={e => setName(e.target.value)} />
      <button onClick={() => setName("")}>Cancel</button>
      <p>{result.name}</p>
    </div>
  );
}
```

In the code above, we have the method with the `actions.setName` key that’s set to an `async` function and takes a `breakpoint` function. We call the `breakpoint` function with 3000 milliseconds of waiting to let us cancel the request.

We also have a cancel button which also calls the `setName` action, which lets us cancel the action. The second breakpoint call breaks cancel the action when the `setName` action is called a second time.

## Sagas

To incorporate sagas into Kea, we have to install the Redux-Saga and Kea Saga packages by running:

```jsx
npm install --save kea-saga redux-saga
```

Then we can add sagas and use them with Kea as follows:

```jsx
//index.js
import React from "react";
import ReactDOM from "react-dom";
import { resetContext, getContext } from "kea";
import { Provider } from "react-redux";
import sagaPlugin from "kea-saga";
import App from "./App";
resetContext({
  createStore: true,
  plugins: [sagaPlugin({ useLegacyUnboundActions: false })]
});

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={getContext().store}>
    <App />
  </Provider>,
  rootElement
);
```

In the code above, we added the `sagaPlugin` from `kea-saga` as our Kea plugin. We also have to set `createStore` to `true` to let us use sagas in our store:

```jsx
//App.js
import React from "react";
import { kea, useActions, useValues } from "kea";
import { put } from "redux-saga/effects";

const logic = kea({
  actions: () => ({
    setCount: count => ({ count }),
    setDoubleCount: doubleCount => ({ doubleCount })
  }),
  start: function*() {
    console.log(this);
  },

  stop: function*() {},

  takeEvery: ({ actions }) => ({
    [actions.setCount]: function*({ payload: { count } }) {
      yield put(this.actions.setDoubleCount(count * 2));
    }
  }),

  reducers: ({ actions }) => ({
    count: [
      0,
      {
        [actions.setCount]: (_, payload) => payload.count
      }
    ],
    doubleCount: [
      0,
      {
        [actions.setDoubleCount]: (_, payload) => payload.doubleCount
      }
    ]
  })
});

const Count = () => {
  const { count, doubleCount } = useValues(logic);
  return (
    <p>
      {count} {doubleCount}
    </p>
  );
};

export default function App() {
  const { setCount } = useActions(logic);
  const { count } = useValues(logic);
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <Count />
    </div>
  );
}
```

In the code above, we have our saga methods in the object that we pass into the `kea` function. The `takeEvery` is called every time a new value is emitted, so we can use it to run code like another action like we did above.

We use the `yield` keyword to return the value that’s used to set the action. `put` is used to schedule the dispatching of action from the store.

`this.actions.setDoubleCount(count * 2)` returns the value that we want to emit for `setDoubleCount`, so `yield` and `put` together will send the action to the `setDoubleCount` and emit the value to our components via the `useValue` hook.

The `start` method is a generator function that’s called when our store initializes, so we can put any store initialization code inside.

Therefore, when we click the increment button, the `setCount` function is called, which updates the `count` state in the store. Then the `takeEvery` method is called, which dispatches the `setDoubleCount` action. Then that value is emitted and ends up in the `Count` component.

So the left number will increment by 1 and the right one will increment by 2.

## Thunks

Thunks are another way to commit side effects with Redux. It lets us dispatch multiple actions at once and also lets us run async code with Redux. It does the same things in Kea.

To use thunks with Kea, we install the Kea Thunk and Redux Thunk packages as follows:

```jsx
//index.js
import React from "react";
import ReactDOM from "react-dom";
import { resetContext, getContext } from "kea";
import { Provider } from "react-redux";
import thunkPlugin from "kea-thunk";
import App from "./App";
resetContext({
  createStore: true,
  plugins: [thunkPlugin]
});

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={getContext().store}>
    <App />
  </Provider>,
  rootElement
);
```

```jsx
//App.js
import React from "react";
import { kea, useActions, useValues } from "kea";
const delay = ms => new Promise(resolve => window.setTimeout(resolve, ms));

const logic = kea({
  actions: () => ({
    setCount: count => ({ count }),
    setDoubleCount: doubleCount => ({ doubleCount })
  }),

  thunks: ({ actions, dispatch, getState }) => ({
    setCountAsync: async count => {
      await delay(1000);
      actions.setCount(count);
      await delay(1000);
      actions.setDoubleCount(count * 2);
    }
  }),

  reducers: ({ actions }) => ({
    count: [
      0,
      {
        [actions.setCount]: (state, payload) => payload.count
      }
    ],
    doubleCount: [
      0,
      {
        [actions.setDoubleCount]: (state, payload) => payload.doubleCount
      }
    ]
  })
});
const Count = () => {
  const { count, doubleCount } = useValues(logic);
  return (
    <p>
      {count} {doubleCount}
    </p>
  );
};

export default function App() {
  const { setCountAsync } = useActions(logic);
  const { count } = useValues(logic);
  return (
    <div className="App">
      <button onClick={() => setCountAsync(count + 1)}>Increment</button>
      <Count />
    </div>
  );
}
```

In the code above, we added the `kea-thunk` plugin with:

```jsx
plugins: [thunkPlugin]
```

Then in the `thunks` property of the object that we pass into the `kea` function, we defined our thunk, which has the async `delay` function to pause the thunk for 1 second. Then we dispatch the `setCount` action and dispatch the `setDoubleAction` after call `delay` to wait another second.

We can’t run async code with actions functions since they’re supposed to be pure synchronous functions.

Using thunks is a good way to run async code when dispatching actions.

In the end, we should get the increment button, which we can click to increment the `count` one second after the button is clicked and increment `doubleCount` after two seconds.

## Conclusion

Kea is an alternative to Redux for state management. It has various plugins to do state management like sagas and thunks.

It works similarly to how Redux works and uses Redux as a base for its state management solution.

It works by creating a store with actions and reducers. They are the same as what they are in Redux. Also, we can add listeners to listen to action dispatch events. We can also add sagas and thunks via Kea’s plugins.

* * * 
## Full visibility into production React apps 

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, [try LogRocket.](https://www2.logrocket.com/react-performance-monitoring)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/eq752g8qhbffxt3hp9t4.png)

[LogRocket](https://www2.logrocket.com/react-performance-monitoring) is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free.](https://www2.logrocket.com/react-performance-monitoring)
* * *

The post [Simplify React state management with Kea](https://blog.logrocket.com/simplify-react-state-management-with-kea/) appeared first on [LogRocket Blog](https://blog.logrocket.com).