HooX is a lightweight hook – based React state management tool. React allows you to manage the global state of React applications. The concept is simple and supports TS perfectly.

Embrace functional components more

From [email protected]’s hook to vue@3’s composition-API, functional components are the future. HooX provides a state management solution for functional components, as well as a series of apis based entirely on functional writing, allowing users to embrace functional components and move further into the future.

2. Simplify the complex code brought by pure hook writing

Those of you who have written Hook will know that the logical abstraction that hook brings makes our code more organized. But:

  1. UseCallback /useMemo is really written very, very much
  2. Because of scope issues, state in some methods is often not known to be correct

For example, if you click on a list to load the next page, what happens if you hook it?

import { useState, useEffect } from 'react'

const fetchList = (. args) = > fetch('./list-data'. args)export default function SomeList() {
  const [list, setList] = useState([])
  const [pageNav, setPageNav] = useState({ page: 1.size: 10 })
  const { page, size } = pageNav

  // Initialize the request
  useEffect((a)= > {
    fetchList(pageNav).then(data= > {
      setList(data)
    })
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  // Get the next page
  const nextPage = (a)= > {
    const newPageNav = {
      page: page + 1,
      size
    }
    fetchList(newPageNav).then(data= > {
      setList(data)
      setPageNav(newPageNav)
    })
  }

  return (
    <div>
      <div className="list">
        {list.map((item, key) => (
          <div className="item" key={key}>.</div>
        ))}
      </div>
      <div className="nav">
        {page}/{size}
        <div className="next" onClick={nextPage}>The next page</div>
      </div>
    </div>)}Copy the code

It’s pretty routine. Now, WHAT I want to do is give the next page method a little bit of a shake off, so what do we do? Is that so?

// Get the next page
const nextPage = debounce((a)= > {
  const newPageNav = {
    page: page + 1,
    size
  }
  fetchList(newPageNav).then(data= > {
    setList(data)
    setPageNav(newPageNav)
  })
}, 1000)
Copy the code

At first glance, there seems to be no problem, but in fact, there is a great hidden trouble! Because every render brings up a new nextPage. If the component is rendered again within one second due to changes in its props or state, then clicking on it is already a completely new method and does not provide any anti-shake effect. So what do you do? You must work with useMemo, and the code looks like this:

// Get the next page
const nextPage = useMemo(
  (a)= >
    debounce((a)= > {
      const newPageNav = {
        page: page + 1,
        size
      }
      fetchList(newPageNav).then(data= > {
        setList(data)
        setPageNav(newPageNav)
      })
    }, 1000),
  [page, size]
)
Copy the code

NextPage internally depends on page/size, so useMemo’s second argument must add them. However, the nextPage is still regenerated whenever my page changes to size.

Uncomfortable.

Another problem is that each update brings a rendering because list and pageNav use two usestates. If we synthesize a state, since the setState returned by useState is a reset state, we pass the full amount of data each time, like this:

const [{ list, pageNav }, setState] = useState({
  list: [].pageNav: { page: 1.size: 10}})const { page, size } = pageNav

// Initialize the request
useEffect((a)= > {
  fetchList(pageNav).then(data= > {
    setState({
      list: data,
      pageNav
    })
  })
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
Copy the code

Pointless setting a pageNav, ** uncomfortable. ** For more complex scenarios, transfer functions can be used to retrieve old data, which is a bit easier, but still quite cumbersome.

Of course, there are several libraries that can handle merge updates, such as the useSetState for React-Use.

However, there is another, once and for all, solution: HooX.

Let’s implement this logic by HooX.

import { useEffect } from 'react'
import createHoox from 'hooxjs'
import { debounce } from 'lodash'

const fetchList = (. args) = > fetch('./list-data'. args)const { useHoox, getHoox } = createHoox({
  list: [].pageNav: { page: 1.size: 10}})const nextPage = debounce((a)= > {
  const [{ pageNav }, setHoox] = getHoox()
  const newPageNav = {
    page: pageNav.page + 1.size: pageNav.size
  }
  fetchList(newPageNav).then(data= > {
    setHoox({
      list: data,
      pageNav: newPageNav
    })
  })
})

const initData = (a)= > {
  const [{ pageNav }, setHoox] = getHoox()
  fetchList(pageNav).then(data= > {
    setHoox({ list: data })
  })
}

export default function SomeList() {
  const [{ list, pageNav }] = useHoox()

  const { page, size } = pageNav

  // Initialize the request
  useEffect(initData, [])

  return (
    <div>
      <div className="list">
        {list.map((item, key) => (
          <div className="item" key={key}>.</div>
        ))}
      </div>
      <div className="nav">
        {page}/{size}
        <div className="next" onClick={nextPage}>The next page</div>
      </div>
    </div>)}Copy the code

Since we take all the state updating out of the components and hooks, and getHoox gets the latest data state, there is no useMemo and no dependencies to pass. As our scenario becomes more complex and functions/data are passed between components, this benefit becomes greater and greater. This is an advantage that some state managers that globalize hooks do not have.

3. Perfect TS support and editor prompt

Because HooX is fully ts implemented, it supports TS and type derivation perfectly. Take the code above for example:

In addition, since each action/effect is declared separately, it is directly referenced. So no matter where they are defined, the editor can directly locate the corresponding implementation, without resorting to vscode plug-ins like dva.

4. Compatible with class components

What about some historical class components that you want to use global state but don’t want to modify? Hoox provides Connect, which makes it easy to inject state into class components. Here’s another example (TS) :

class SomeList extends React.PureComponent<{
  list: any[]
  pageNav: { page: number; size: number }
  nextPage: (a)= > void
  initData: (a)= > void
}> {

  componentDidMount() {
    this.props.initData();
  }

  render() {
    const { list, pageNav, nextPage } = this.props;
    const { page, size } = pageNav;
    return (
      <div>
        <div className="list">
          {list.map((item, key) => (
            <div className="item" key={key}>.</div>
          ))}
        </div>
        <div className="nav">
          {page}/{size}
          <div className="next" onClick={nextPage}>The next page</div>
        </div>
      </div>); }}const ConnectedSomeList = connect(state= > ({
  list: state.list,
  pageNav: state.pageNav,
  nextPage,
  initData,
}))(SomeList);
Copy the code

Because all props are injected, the new component that is eventually returned does not need any props (hence never). You can also inject partial props.

Because only list and pageNav are injected, nextPage and initData are still props for the component.

5. Simple implementation, no Hack, stable

The implementation of HooX is very simple, removing some type derivation, about 100 lines of code, completely based on the Context + Provider, without any hacking technology, pure react native mechanism and capabilities, so there is no need to worry about some weird problems.

If you have any special demands, you can fork your own maintenance, and the maintenance cost is very low.

With that said, let’s try HooX