State Management in React with Hookstate
Tags: react, hookstate
Published: 2023-02-17 21:36:41

by [Clarence Bakosi](https://blog.openreplay.com/authors/clarence-bakosi)

Managing the state of a medium-sized application as a React Developer can be difficult. It is relatively simple to pass state from one component to another when developing small-scale applications. When the application's scale changes, it becomes inconvenient because you have to prop drill to access the state in another component.

[Hookstate](https://hookstate.js.org/docs/getting-started) is a state management library solely based on React state hook. It is simple to implement, quick, straightforward, and extensible. No boilerplate is required, and it can also be used in Next.js applications.

In this article, we'll look at how to use this library, one of the most user-friendly React state management libraries. You must have a basic understanding of [React](https://reactjs.org/docs/getting-started.html), [React Hooks](https://reactjs.org/docs/hooks-intro.html), [Chakra-UI](https://chakra-ui.com/getting-started), and [Axios](https://www.npmjs.com/package/axios) to follow along.




## Getting started with Hookstate

In this section, we'll use the code block below to create a React application:

```
npx create-react-app react-hookstate
cd react-hookstate
```

To install the required libraries, use either of the following code blocks:

```
 npm install --save @hookstate/core @chakra-ui/react @emotion/react @emotion/styled framer-motion axios
 
```
or 

``` 
 yarn add  @hookstate/core @chakra-ui/react @emotion/react @emotion/styled framer-motion axios
```

## Local state

In general, using the local state in a React application is recommended when a state is used by a parent component, its children, or just the parent. It is recommended to use a global state when multiple components share a state, and every component in the application has access to the state in this way.


To show how Hookstate handles state locally, we will leverage `useHookstate`.



```javascript
import React from "react";
import { useHookstate } from "@hookstate/core";
import { Box, Button, Flex, Text } from "@chakra-ui/react";

const App = () => {
  const state = useHookstate(0);
  return (
    <Box
      display="flex"
      flexDirection="column"
      justifyContent="center"
      alignItems="center"
      maxW="1440px"
      minH="100vh"
      m="auto"
    >
      <Text textAlign="center" fontWeight="700" fontSize={{base: "32px", md: "64px"}}>
        Counter value: {state.get()}{" "}
      </Text>
      <Flex gap={4}>
        <Button onClick={() => state.set((p) => p + 1)} bg="green" color="#fff">Increment</Button>
        <Button onClick={() => state.set((p) => p - 1)} bg="red" color="#fff">Decrement</Button>
      </Flex>
    </Box>
  );
};

export default App;
```

The code above shows a counter application and how we used `useHookstate`. After assigning `useHookstate` to the variable `state`, we set the default value to `0` and used set and get methods provided by `useHookstate`. The `set` method is used to mutate the state, while the `get` method retrieves the state's value.

![Local state](https://blog.openreplay.com/images/state-management-in-react-with-hookstate/images/KcXsO3x.gif)


## Global state
In this section, we will look at how to manage the state in an application at the global level. As in the previous section, the state will be global this time and accessible from anywhere in the application.

Create a new directory in `src/store/index.js` and paste the code block below into it:

```javascript
import { hookstate, useHookstate } from "@hookstate/core";

const initialState = hookstate({
  count: 0,
});

export const useGlobalState = () => {
  const state = useHookstate(initialState);

  return {
    getCount: () => state.count.value,
    increment: () => {
      state.count.set((count) => count + 1);
    },
    decrement: () => {
      state.count.set((count) => count - 1);
    },
  };
};
```

In the preceding code block, we declared the application's `initialState` using `hookState`, which has an object containing `count`, with the value set to `0`.

Following that, we created a custom hook called `useGlobalstate` and passed the argument `initialState` to `useHookstate`. We have three functions in the return block that read and modify the state.

To access the state globally, we must first modify the `App.js` component.

```javascript
import React from "react";
import { Box, Button, Flex, Text } from "@chakra-ui/react";
import { useGlobalState } from "./store";

const App = () => {
  const state = useGlobalState();

  const increment =()=> {
    state.increment()
  }

  const decrement =()=> {
    state.decrement()
  }

  return (
    <Box
      display="flex"
      flexDirection="column"
      justifyContent="center"
      alignItems="center"
      maxW="1440px"
      minH="100vh"
      m="auto"
    >
      <Text textAlign="center" fontWeight="700" fontSize={{base: "32px", md: "64px"}}>
        Counter value: {state.getCount()}{" "}
      </Text>
      <Flex gap={4}>
        <Button onClick={() => increment()} bg="green" color="#fff">Increment</Button>
        <Button onClick={() => decrement()} bg="red" color="#fff">Decrement</Button>
      </Flex>
    </Box>
  );
};

export default App;
```

We now have access to the global state via `useGlobalState`, the custom hook imported from `src/store/index.js`, which we set to a variable called `state` in the updated `App.js` component. We now have global access to read and mutate state.

![Global state](https://blog.openreplay.com/images/state-management-in-react-with-hookstate/images/OUuJieP.gif)


In general, using the local state in a React application is recommended when a state is used by a parent component, its children, or just the parent. It is recommended to use a global state when multiple components share a state. Every component in the application has access to the state in this way.


## Global State with CRUD functionality

In this section, we will create a [CRUD](https://developer.mozilla.org/en-US/docs/Glossary/CRUD) application. This will show a real-world example of how to manage the state of an application.

We will be creating a simple blog application. The first approach will be to create the application's state and the functions to mutate it.

Add the following code block to the `src/store/index` file.

```javascript
import { hookstate, useHookstate } from "@hookstate/core";

const initialState = hookstate({
  blog: [],
});

export const useGlobalState = () => {
  const state = useHookstate(initialState);

  return {
    getCountBlog: () => state.blog.length,
    addBlog: (blog) => {
      state.blog.merge([blog]);
    },
    updateBlog: (id, blog) => {
      state.blog.set((b) =>
        b.map((blogs) => {
          if (blogs.id === id) {
            blogs.content = blog.content;
          }
          return blogs;
        })
      );
    },
    deleteBlog: (id) => {
      state.blog.set((blogs) => blogs.filter((blog) => blog.id !== id));
    },
    fetchBlogs: () => state.blog,
  };
};
```

We created five distinct functions for the application in the above code block. These functions can read and modify the state. The `addBlog` function, for example, uses the `merge` API from Hookstate, which, like `set,`  partially updates the application's existing state.


We will create the same functions in App.js as in `src/store/index`.


```javascript
import React, { useEffect, useState } from "react";
import {
  Box,
  Button,
  Card,
  CardBody,
  CardFooter,
  Flex,
  Image,
  Input,
  Stack,
  Text,
} from "@chakra-ui/react";
import { useGlobalState } from "./store";

const App = () => {
  const state = useGlobalState();
  const [data, setData] = useState([]);
  const [content, setContent] = useState("");
  const [edit, setEdit] = useState(false);
  const [updateId, setUpdateId] = useState(0);

  const fetchBlog = () => {
    setData(state.fetchBlogs());
  };

  useEffect(() => {
    fetchBlog();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const addBlog = () => {
    const blog = {
      id: state.getCountBlog() + 1,
      content: content,
    };
    state.addBlog(blog);
    setContent("");
  };

  const updateBlog = (id) => {
    const blog = {
      id,
      content,
    };
    state.updateBlog(id, blog);
    setContent("");
    setUpdateId(0);
    setEdit(false);
  };

  const deleteBlog = (id) => {
    state.deleteBlog(id);
  };

  return (
    <Box
      display="flex"
      flexDirection="column"
      justifyContent="flex-start"
      alignItems="center"
      maxW="1440px"
      minH="100vh"
      m="auto"
    >
       <Box
        width={{ base: "auto", md: "700px" }}
        minH="100vh"
        mt="0rem"
        bg={{ base: "transparent", md: "blackAlpha.400" }}
        p={8}
      >
        <Text fontSize="28px" fontWeight="600" mb={4}>
          Blog posts: {state.getCountBlog()}
        </Text>
        <Flex>
          <Input
            name="content"
            value={content}
            onChange={(e) => setContent(e.target.value)}
            errorBorderColor="crimson"
            placeholder="Enter Quote"
            borderInlineEndRadius={0}
          />
          {edit ? (
            <Button
              onClick={() => updateBlog(updateId)}
              borderInlineStartRadius={0}
              bg="green"
              color="#fff"
            >
              Update
            </Button>
          ) : (
            <Button onClick={addBlog} borderInlineStartRadius={0} bg="green" color="#fff">
              Add
            </Button>
          )}
        </Flex>
        <Box my={8}>
          {data.length < 1 && (
            <Text py={4} textAlign="center">
              No blog post found
            </Text>
          )}
          {data &&
            data.map((item, index) => (
              <Card
                key={index}
                direction={{ base: "column", sm: "row" }}
                overflow="hidden"
                variant="outline"
                my={4}
              >
                <Image
                  objectFit="cover"
                  maxW={{ base: "100%", sm: "200px" }}
                  src="https://images.unsplash.com/photo-1667489022797-ab608913feeb?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHw5fHx8ZW58MHx8fHw%3D&auto=format&fit=crop&w=800&q=60"
                  alt="Caffe Latte"
                />

                <Stack w="full">
                  <CardBody>
                    <Text fontSize="24px" fontWeight="600" py="2">
                      {item.get(item).content}
                    </Text>
                  </CardBody>

                  <CardFooter display="flex" justifyContent="flex-end" gap={4}>
                    <Button
                      onClick={() => {
                        setContent(item.get(item).content);
                        setEdit(true);
                        setUpdateId(item.get(item).id);
                      }}
                      bg="blue"
                      color="#fff"
                    >
                      Edit
                    </Button>
                    <Button
                      onClick={() => deleteBlog(item.get(item).id)}
                      bg="red"
                      color="#fff"
                    >
                      Delete
                    </Button>
                  </CardFooter>
                </Stack>
              </Card>
            ))}
        </Box>
      </Box>
    </Box>
  );
};

export default App;
```

We created named functions in the preceding code block and passed the functions from the global state into each. Simply use `item.get(item).content` or `item.value.content` to display a value in Hookstate.

![CRUD Global state](https://blog.openreplay.com/images/state-management-in-react-with-hookstate/images/7eY2nLu.gif)

<h2>Session Replay for Developers</h2> 
<p><em>
Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data
</em></p>
<img alt="OpenReplay" width="768" height="400" src="https://blog.openreplay.com/assets/overview_Z17Qdyg.png" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em>



## Asynchronous state
Hookstate handles asynchronous data easily, performing API calls until it is resolved. The asynchronous data can be stored globally and accessed from the store from anywhere in your application.

We'll use the code block below to create an asynchronous state that will fetch a list of users from an API and display the results in the App.js component.

```javascript
import { hookstate, useHookstate } from "@hookstate/core";
import axios from "axios";

const initialState = hookstate(
  {
    loading: false,
    users: [],
  }
);

export const useGlobalState = () => {
  const state = useHookstate(initialState);
  const resourcePath = "https://jsonplaceholder.typicode.com/users";

  return {
    loading: () => state.loading,
    getUsers: async () => {
    await axios.get(resourcePath).then((r) => state.users.set(r.data));
    state.loading.set(true)
    },
    fetchUsers: () => state.users,
  };
};
```

We added users to the application's initial state and set the value to an empty array using the code block above. The array will contain a list of all users retrieved from the API. We also added the state `loading`, which has a default value of false.

Following that, three functions that can read and modify state were created. The loading function reads the loading state; `getUsers` must be handled as a promise to access the state. `fetchUsers` simply returns the current state of users.

```javascript
import React, { useEffect, useState } from "react";
import {
  Box,
  Card,
  CardBody,
  CardFooter,
  Image,
  Stack,
  Text,
} from "@chakra-ui/react";
import { useGlobalState } from "./store";

const App = () => {
  const state = useGlobalState();
  const [user, setUser] = useState([]);

  useEffect(() => {
    state.getUsers();

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    if (state.loading().value === true) {
      setUser(state.fetchUsers());
    }
  }, [state]);

  return (
    <Box
      display="flex"
      flexDirection="column"
      justifyContent="flex-start"
      alignItems="center"
      maxW="1440px"
      minH="100vh"
      m="auto"
    >
       <Box
        width={{ base: "auto", md: "700px" }}
        minH="100vh"
        mt="0rem"
        bg={{ base: "transparent", md: "blackAlpha.400" }}
        p={8}
      >
         <Text fontSize="28px" fontWeight="600" mb={4}>
          User Count: {user.length}
        </Text>
        <Box my={8}>
          {user.length < 1 && (
            <Text py={4} textAlign="center">
              No user post found
            </Text>
          )}
          {user &&
            user.map((item, index) => (
              <Card
                key={index}
                direction={{ base: "column", sm: "row" }}
                overflow="hidden"
                variant="outline"
                my={4}
              >
                <Image
                  objectFit="cover"
                  maxW={{ base: "100%", sm: "200px" }}
                  src="https://images.unsplash.com/photo-1667489022797-ab608913feeb?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHw5fHx8ZW58MHx8fHw%3D&auto=format&fit=crop&w=800&q=60"
                  alt="Caffe Latte"
                />

                <Stack w="full">
                  <CardBody>
                    <Text fontSize="24px" fontWeight="600" py="2">
                      {item.value.name}
                    </Text>
                  </CardBody>

                  <CardFooter
                    display="flex"
                    justifyContent="flex-start"
                    gap={4}
                  >
                    <Text>{item.value.email}</Text>
                  </CardFooter>
                </Stack>
              </Card>
            ))}
        </Box>
      </Box>
    </Box>
  );
};

export default App;
```

As before, we must initialize the `getUsers` function from the global state and load the data into users before successfully retrieving the users list with `fetchUsers` only when `loading` is set to true. Next, we map the data from the `user` state and use `item.value.name` and `item.value.email` to get the value of each item.

![Async state](https://blog.openreplay.com/images/state-management-in-react-with-hookstate/images/42CPF8v.gif)

## Dev tools
Using dev tools, you can inspect the state of your application in Hookstate. It has the simplest configuration known; all that is required is to pass a second argument to `hookstate` or `useHookstate` as shown in the code below. This has no adverse effects on your application in production.

```javascript
...
import { devtools } from "@hookstate/devtools";

const initialState = hookstate(
  {
    loading: false,
    users: [],
  },
  devtools({ key: "my-state-label" })
);
...
```
![Dev tools](https://blog.openreplay.com/images/state-management-in-react-with-hookstate/images/t1Ph4oY.png)


## Conclusion

The Hookstate library is one of the simplest state management libraries to integrate into your application. The library has more to offer, and it has excellent developer experience. To learn more about Hookstate, go to the [official docs](https://hookstate.js.org/docs/getting-started). Access the [source code](https://github.com/clarencepenz/react-hookstate) and [live version](https://react-hookstate.netlify.app/) of the project used in this article.






[![newsletter](https://blog.openreplay.com/newsletter_Z1P0UIP.png)](https://newsletter.openreplay.com/)