A somewhat tricky hook usage
Tags: react, hooks, javascript, webdev
Published: 2019-02-04 19:25:27

---
title: A somewhat tricky hook usage
published: true
description: I recently updated a React state management library, hoping to reduce its API from two wrapper functions into a single hook. I did not succeed but I still ended up with something nice. Here is why and how.
tags: react, hooks, javascript, webdev
cover_image: https://thepracticaldev.s3.amazonaws.com/i/souko3krk2pupgfogt7i.png
---

I recently updated a React state management library, hoping to reduce its API from two wrapper functions into a single hook. I did not succeed but I still ended up with something nice. Here is why and how.

## The starting point

The library had the following API for dealing with global state

```jsx
import React from 'react'
import { store, view } from 'react-easy-state'

const count = store({ num: 0 })
const increment = () => count.num++

export default view(() => <button onClick={increment}>{count.num}</button>)
```

... and local state.

```jsx
import React, { Component } from 'react'
import { view, store } from 'react-easy-state'

class Counter extends Component {
  counter = store({ num: 0 })
  increment = () => counter.num++

  render() {
    return <button onClick={this.increment}>{this.counter.num}</button>
  }
}

export default view(Counter)
```

Initially, I hoped to reduce the API to something like below.


```jsx
import React from 'react'
import useStore from 'react-easy-state'

export default function Counter () {
  const counter = useStore({ num: 0 })
  const increment = () => counter.num++

  return <button onClick={increment}>{counter.num}</button>
}
```

> Notice how the `view` HOC is gone in this failed experiment.

## The issues

- State management libs are expected to optimize re-renders with `shouldComponentUpdate` or `memo` by default. This kind of render bailout can not be done and [should not be done](https://overreacted.io/why-isnt-x-a-hook/#not-a-hook-usebailout) with hooks.

- Some state management libraries need to know details about the components - like what props do they expect or what data do they use from elsewhere. Hooks are not wrapping the components, so they can not provide these kinds of higher level information.

- A `useStore` hook is nice for local state in function components but it does not work for global state and local state in class components.

All of the above can be solved with Higher Order Components. Hooks are still needed to enable local state with function components though.

## The second iteration

After some tinkering and prototyping, I ditched the idea of reducing the API into a single hook but I tried to not extend it with new functions at least. I succeeded this time through a somewhat unorthodox hook usage.

I overloaded the `store` function to behave like a hook when it is called inside a function component but keep its original behavior otherwise. As a result, I got this syntax for local state

```jsx
import React from 'react'
import { view, store } from 'react-easy-state'

export default view(() => {
  const count = store({ num: 0 })
  const increment = () => count.num++

  return <button onClick={increment}>{count.num}</button>
})
```

... and for global state.

```jsx
import React from 'react'
import { view, store } from 'react-easy-state'

const count = store({ num: 0 })
const increment = () => count.num++

export default view(() => {
  return <button onClick={increment}>{count.num}</button>
})
```

 > Notice that the only difference is the placement of the state stores.

## The implementation

This is a simplified version of the `view` HOC which toggles the `isInsideFunctionComponent` flag to true while the wrapped function component is rendering. This could not be done with a hook.

```js
import { memo } from 'react'

export let isInsideFunctionComponent = false

export default function view (Comp) {
  return memo(props => {
    isInsideFuntionComponent = true
    try {
      return Comp(props)
    } finally {
      isInsideFunctionComponent = false
    }
  })
}
```

The below snippet is a simplified version of the `store` wrapper. It decides if it should behave like a local or global store based on the `isInsideFunctionComponent` flag.

```js
import { useMemo } from 'react'
import { isInsideFunctionComponent } from './view'

export default function store (obj) {
  if (isInsideFunctionComponent) {
    return useMemo(() => storeImplementation(obj), [])
  }
  return storeImplementation(obj)
}
```

If `store` is called inside a function component it returns a memoized local state store, otherwise it returns a global store.

## But ...

- **It is not called `useStore`.**

The `useX` naming is just a convention, which can be broken with a good reason. Ergonomic API design is good enough for me.

- **It is a hook behind an `if` statement.**

If you are familiar with the [rules of hooks](https://reactjs.org/docs/hooks-rules.html) the `if` statement likely made you frown, but it is actually there to adhere to the rules not to break them.

Whenever `store` is called from a function component the `isInsideFunctionComponent` flag is true and the `if` block is entered. From the function component's point of view `useMemo` is not behind an `if` block, it is called at the correct place every time the component is rendered.

- **It is magic (aka dirty hack).**

This is why I love JavaScript. One can save immense efforts with decent language knowledge and an easy mind. Simpler is better if you know what you do.

- react-easy-state is changing language behavior with ES6 Proxies.
- I have a lib built around the 'deprecated' `with` keyword and I think it is great.
- I like to use sparse arrays and the `delete` keyword for storing data (in deduped priority queues).
- The React team started throwing Promises.

These kind of 'hacks' are awesome as long as they do not pose edge cases and hidden pitfalls to their users.

---

*If this article captured your interest please help by sharing it. Also, check out the [React Easy State repo](https://github.com/solkimicreb/react-easy-state) if you have some state to manage.*

*Thanks!*