Simplify React state management with Hookstate
Tags: react, webdev
Published: 2021-07-01 20:55:34

**Written by [Ebenezer Don](https://blog.logrocket.com/author/ebenezerdon/)** ✏️

If you’ve used React to build a web application, chances are you’ve had major challenges with state management. For too long, we relied on Redux for state management, but because of its complexity and excessive amount of code, we eventually turned to other solutions like RxJS/React Hooks and the React Context API. We’ve also seen Redux Toolkit solve the Redux boilerplate problem — and I can argue that it’s the simplest of all the tools I just mentioned.

Then there’s React’s `useState` Hook for local state management. If you’ve worked with it before, then you might have wondered why global state management can’t be just as easy. Why do we still need so much boilerplate to manage state with the Context API? What if we don’t want to be constrained by opinionated tools like Redux Toolkit or forced to use actions and reducers in our React applications?

This is where Hookstate comes in. Hookstate is not just another state management solution. Apart from being feature-rich, fast, and flexible, the library takes the idea of simplifying state management in React apps to a whole new level.

Hookstate works almost exactly like the React `useState` Hook. In fact, with Hookstate, creating a global state is just as easy as creating a local state with the `useState` Hook. In addition to its simplicity, Hookstate also extends our created state instance with other useful features.

In this article, we’ll introduce Hookstate as a simple and efficient state management solution for React applications by building a demo chat app. The only prerequisite you’ll need to follow along with this article is knowledge of React.

## Hookstate and its features

As its name suggests, [Hookstate] (https://hookstate.js.org/) is a fast and flexible state management tool based on the React state Hook. It’s a small library packed with features that include both global and local states, as well as partial state updates and asynchronously loaded states.

Our focus for this article is on the `@hookstate/core` package, but Hookstate has [several optional plugins] (https://hookstate.js.org/docs/extensions-overview/) that enable us to extend or customize our state Hooks — and the library’s documentation is well-written and packed with good demos. Below are some noteworthy plugins:

*   [`@hookstate/persistence`](https://hookstate.js.org/docs/extensions-persistence) enables us to persist our state to the browser’s local storage, which is useful for offline apps or if you’d like a user to retain their state data after reloading the page
*   [`@hookstate/validation`] (https://hookstate.js.org/docs/extensions-validation) can be very useful for form fields because it enables validation and error/warning messages for a state
*   [`@hookstate/broadcasted`] (https://hookstate.js.org/docs/extensions-broadcasted) is a very useful tool if you want to enable synchronization of your state across different browser tabs

Let’s explore some of the core features in Hookstate that make it a good choice for state management in React applications. We’ll do this by building a chat app. Here’s what our app should look like by the end of the article:

![Final chat app demo](https://blog.logrocket.com/wp-content/uploads/2021/05/final-chat-app-demo-v2.gif)

Our chat app will have two components that will be able to interact with each other by sending and receiving data from our global Hookstate store.

## Building our app with React and Hookstate

Let’s start by using the `create-react-app` package to generate a new React application. We’ll run the following command in our terminal:

```shell
npx create-react-app hookstate-chat
```

Next, we’ll `cd` into our new `hookstate-chat` directory and install Hookstate:

```shell
cd hookstate-chat
npm install --save @hookstate/core
```

### Setting up our chat box components

Now that we have Hookstate installed, let’s install [`react-custom-chat`] (https://www.npmjs.com/package/react-custom-chat). I created this npm package for this article so that we can focus on Hookstate and not have to bother ourselves with the design of our chat app, but you can use or build another, if you like:

```shell
npm install --save react-custom-chat
```

With `react-custom-chat` installed, let’s go over to the `./src` directory and create a JSX file for our first chat component. We’ll name it `FirstPerson`.

Next, we’ll add the following code to our `./src/FirstPerson.jsx` file:

```javascript
import ChatBox from 'react-custom-chat'

const FirstPerson = () => {
  return (
    <ChatBox
      messageList={[]}
      onSendMessage={newMessage => {}} // do something with newMessage
      settings={{
        position: 'left',
        navColor: 'green',
        navText: 'Mycroft'
      }}
    />
  )
}

export default FirstPerson
```

In our `FirstPerson` component, we started by importing `ChatBox` from the `react-custom-chat` package. The `ChatBox` component has a `messageList` prop, which will contain an array of our message objects. Each [message object](https://github.com/ebenezerdon/react-custom-chat/blob/main/README.md#message-object) will contain:

*   A `text` property of type `string`, which represents the message text
*   A `person` property of type `string`, which can either be “primary” or “secondary”

The `person` property helps the `ChatBox` component to determine how to style each message.

The `onSendMessage` prop expects a function that’ll tell it what to do whenever a message is sent.

Lastly, we added a `settings` prop to define the look of our chat boxes. In this case, we want the FirstPerson chat box to be positioned on the left of our page.

Let’s do the same thing for our SecondPerson chat box. We’ll create a JSX file named `SecondPerson` in our `./src` directory and paste the following code in it:

```javascript
import ChatBox from 'react-custom-chat'

const SecondPerson = () => {
  return (
    <ChatBox
      messageList={[]}
      onSendMessage={() => {}}
      settings={{
        position: 'right',
        navColor: 'blue',
        navText: 'Cortana'
      }}
    />
  )
}

export default SecondPerson
```

Notice how we changed the position, navigation color, and text for our SecondPerson chat box. You can find other custom settings for the `ChatBox` component in the [`react-custom-chat` documentation](https://github.com/ebenezerdon/react-custom-chat#readme).

### Putting our chat components to use

With our chat components successfully created, we can now import and use them in our `./src/App.js` file. Let’s replace what’s currently there with the following code:

```javascript
import FirstPerson from './FirstPerson'
import SecondPerson from './SecondPerson'

const App = () => {
  return (
    <>
      <FirstPerson />
      <SecondPerson />
    </>
  );
}

export default App
```

We can go ahead and start our application by running `npm start` on our terminal. We should see a page that looks like this when we open our application in the browser:

![Chat app start up page](https://blog.logrocket.com/wp-content/uploads/2021/05/start-page-chat-app.png)

Clicking on the chat icons should open up our chat windows:

![Chat app with open chat windows](https://blog.logrocket.com/wp-content/uploads/2021/05/chat-app-open-windows.png)

### Creating our global store with Hookstate

Now that we’ve successfully created our chat components, let’s use Hookstate to set up and manage our application’s message functionality. We’ll start by creating a new JavaScript file named `store.js` in the `./src` directory, which will house our application’s global state.

Hookstate doesn't have any restrictions on where you can create global states, as long as any component that needs it is able to successfully import it.

Inside our `./src/store.js` file, we’ll use the `createState` method from Hookstate to create our state:

```javascript
import { createState } from '@hookstate/core'

const store = createState({
  firstPersonMessageList: [],
  secondPersonMessageList: []
})

export default store
```

Alternatively, we can create our `FirstPerson` and `SecondPerson` states individually:

```javascript
...
const firstPersonMessageList = createState([])
const secondPersonMessageList = createState([])
...
```

Either option will work fine for our app because Hookstate enables us to create as many states as we want, and also provides us with the ability to easily update nested state properties. For the purposes of our tutorial, we’ll go with the first approach.

We’ve also provided the `createState` method with an initial object state that contains the properties `firstPersonMessageList` and `secondPersonMessageList`.

With our state successfully created, we can go ahead and access it with the `useState` Hook from Hookstate. Since it’s a React Hook, we’ll need to call it inside our React components. Our returned state from the `useState` Hook will have:

*   A `get()` method we can use to get the state data
*   A `set()` method for setting a new value for our state
*   A `merge()` method for adding data to our state

There are also [other methods] (https://hookstate.js.org/docs/typedoc-hookstate-core/#interface-statemethods-s) like `attach()` and `batch()`, but we won’t need them for our demo app.

### Accessing and updating our global store

Let’s see how we can access and update our store from the individual components. We’ll go over to the `./src/FirstPerson.jsx` file and import the `useState` Hook from Hookstate. We’ll also import our store from the `store.js` file:

```javascript
import { useState } from '@hookstate/core'
import store from './store'
...
```

We can now use the `useState` Hook to access our store. Since `useState` is a React Hook, we’ll need to use it inside the body of our `FirstPerson` component. Let’s create a variable named `globalState` and call the `useState` Hook with our imported store as its value.

```javascript
...
const FirstPerson = () => {
  const globalState = useState(store)
  ...
}

export default FirstPerson
```

The `globalState` variable should contain the initial state we provided to our store. We can also directly destructure the `firstPersonMessageList` and `secondPersonMessageList` properties when using the `useState` Hook to access our store. Let’s change our `globalState` variable declaration to the following line of code:

```javascript
const { firstPersonMessageList, secondPersonMessageList } = useState(store)
```

Now, we can use `firstPersonMessageList` and `secondPersonMessageList` as individual states.

This is a powerful Hookstate advantage because our nested properties also have the state methods that our `globalState` variable would have had. We can now equally use the `get()` , `set()`, and `merge()` methods in our destructured properties.

### Handling the user `sendMessage` event

Next, let’s create our function for handling the user `sendMessage` event. We’ll name it `handleSendMessage`:

```javascript
...
const handleSendMessage = newMessage => {
  firstPersonMessageList.merge([{text: newMessage, person: 'primary'}])
  secondPersonMessageList.merge([{text: newMessage, person: 'secondary'}])
}
```

In the above block, we’ve created a function named `handleSendMessage` and have provided it with a parameter named `newMessage`. Our `newMessage` parameter represents whatever our user types into the chat input field. For every new primary message we add to the `firstPersonMessageList`, we’re also making a corresponding secondary addition to the `secondPersonMessageList`. We’ll do the reverse when we get to the `SecondPerson` component.

Notice how easy it is to update our state with the `merge()` method. If we were using the `set()` method or React’s built-in `useState` Hook, our function would look similar to this:

```javascript
const handleSendMessage = newMessage => {
  firstPersonMessageList.set([...firstPersonMessageList, {text: newMessage, person: 'primary'}])
  secondPersonMessageList.merge([...secondPersonMessageList, {text: newMessage, person: 'secondary'}])
}
```

Our second function definitely looks more complex than the first. With the `merge()` method, if the current state value and the argument are both arrays, Hookstate will do the job of concatenating the current value with the value of the argument and setting it to the state. You can see other ways to use the `merge()` method in the [Hookstate documentation](https://hookstate.js.org/docs/typedoc-hookstate-core/#merge).

Just for aesthetics, let’s delay the `secondPersonMessageList` state update by 500 milliseconds:

```javascript
...
const handleSendMessage = newMessage => {
  firstPersonMessageList.merge([{text: newMessage, person: 'primary'}])
  setTimeout(() => {
    secondPersonMessageList.merge([{text: newMessage, person: 'secondary'}])
  }, 500)
}
...
```

We can now supply the `handleSendMessage` function as our `ChatBox` `onSendMessage` prop value. We’ll also use the `get()` method from our `firstPersonMessageList` to access our state, and then use it as the value for our `ChatBox` `messageList` prop:

```javascript
...
<ChatBox
  messageList={firstPersonMessageList.get()}
  onSendMessage={handleSendMessage}
  settings={{
    position: 'left',
    navColor: 'green',
    navText: 'Mycroft',
    isOpen: true
  }}
/>
...
```

Our `FirstPerson.jsx` file should now look like this:

```javascript
import { useState } from '@hookstate/core'
import ChatBox from 'react-custom-chat'
import store from './store'

const FirstPerson = () => {
  const { firstPersonMessageList, secondPersonMessageList } = useState(store)

  const handleSendMessage = newMessage => {
    firstPersonMessageList.merge([{text: newMessage, person: 'primary'}])
    setTimeout(() => {
      secondPersonMessageList.merge([{text: newMessage, person: 'secondary'}])
    }, 500)
  }

  return (
    <ChatBox
      messageList={firstPersonMessageList.get()}
      onSendMessage={handleSendMessage}
      settings={{
        position: 'left',
        navColor: 'green',
        navText: 'Mycroft'
      }}
    />
  )
}

export default FirstPerson
```

Let’s do the same thing in our `SecondPerson.jsx` file. Since we’ve already explained the steps in detail, we can go ahead and paste the following code in our file:

```javascript
import { useState } from '@hookstate/core'
import ChatBox from 'react-custom-chat'
import store from './store'

const SecondPerson = () => {
  const { firstPersonMessageList, secondPersonMessageList } = useState(store)

  const handleSendMessage = newMessage => {
    secondPersonMessageList.merge([{text: newMessage, person: 'primary'}])
    setTimeout(() => {
      firstPersonMessageList.merge([{text: newMessage, person: 'secondary'}])
    }, 500)
  }

  return (
    <ChatBox
      messageList={secondPersonMessageList.get()}
      onSendMessage={handleSendMessage}
      settings={{
        position: 'right',
        navColor: 'blue',
        navText: 'Cortana'
      }}
    />
  )
}

export default SecondPerson
```

In the `handleMessage` function for our `SecondPerson` component, we’ve done the reverse of what we did in the `FirstPerson` component: whenever a message is sent, it gets added as primary to the `secondPersonMessageList` and as secondary to the `firstPersonMessageList`.

Now, when we open up our application in the browser, we should be able to send messages across our two components via the Hookstate tool: ![Final chat app demo](https://blog.logrocket.com/wp-content/uploads/2021/05/final-chat-app-demo-v2.gif)  

## Conclusion

We’ve learned how to use Hookstate for state management in React applications. We’ve also seen why Hookstate is not just another state management solution, but a tool that takes the idea of simplifying state management in React apps to a whole new level.

I’ve not used it in a large-scale app yet, but so far, it’s proven to be an efficient library. Here are links to the [codebase for our `react-custom-chat` package](https://github.com/ebenezerdon/react-custom-chat) and our [demo app](https://github.com/ebenezerdon/react-hookstate-chat). If you want to keep in touch, consider subscribing to my [YouTube channel](https://youtube.com/ebenezerdon) and following me on GitHub. Keep building!

## Further reading

*   [RxJS with React Hooks for state management](https://blog.logrocket.com/rxjs-with-react-hooks-for-state-management/)
*   [React Reference Guide: Context API](https://blog.logrocket.com/react-reference-guide-context-api/)
*   [Smarter Redux with Redux Toolkit](https://blog.logrocket.com/smarter-redux-with-redux-toolkit/)

---

## 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).

![LogRocket Dashboard Free Trial Banner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4xq20mw1jyr5ayspqkd3.png) [![LogRocket Dashboard Free Trial Banner] (https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://www2.logrocket.com/react-performance-monitoring)

[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).