Unstated Next README

preface

The authors of this library want to use the React built-in API to implement state management directly. After reading the description of this library, I did not think that the code can play this way. State management is implemented in just a few lines of code using only React Hooks.

After reading it, my first thought was to translate it into Chinese and share it with others. After submitting the Pull Request, the library author merged my translation. At the same time, the author welcomes the translation of README into other languages, the following is the full translation, welcome to correct or Pull Request.

Unstated Next

Never have to think about React state management again, with just 200 bytes of state management solution.

  • React Hooks React Hooks do all your state management.
  • ~200 bytes min+gz.
  • Be familiar with API React is used only, without relying on third-party libraries.
  • Minimum API It only takes 5 minutes to learn.
  • TypeScript write Deducing code is easier, making it easier to write React code.

But the big question is: Is this better than Redux? The answer may be.

  • It is more small. It’s 40 times smaller than Redux.
  • It’s faster. Component performance problems.
  • It’s easier to learn. You must already know React Hooks and Context. Just use them and they get high.
  • Easier integration. Integrate one component at a time and easily integrate with other React libraries.
  • It’s easier to test. Testing reducers is a waste of your time. This library makes it easier to test the React component.
  • It makes it easier to type check. Aim to make most of your types inferable.
  • It’s the smallest. Just React.

It’s up to you!

The installation

npm install --save unstated-next
Copy the code

Example

import React, { useState } from "react"
import { createContainer } from "unstated-next"
import { render } from "react-dom"

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = (a)= > setCount(count - 1)
  let increment = (a)= > setCount(count + 1)
  return { count, decrement, increment }
}

let Counter = createContainer(useCounter)

function CounterDisplay() {
  let counter = Counter.useContainer()
  return (
    <div>
      <button onClick={counter.decrement}>-</button>
      <span>{counter.count}</span>
      <button onClick={counter.increment}>+</button>
    </div>)}function App() {
  return (
    <Counter.Provider>
      <CounterDisplay />
      <CounterDisplay />
    </Counter.Provider>
  )
}

render(<App />, document.getElementById("root"))
Copy the code

API

createContainer(useHook)

import { createContainer } from "unstated-next"

function useCustomHook() {
  let [value, setInput] = useState()
  let onChange = e= > setValue(e.currentTarget.value)
  return { value, onChange }
}

let Container = createContainer(useCustomHook)
// Container === { Provider, useContainer }
Copy the code

<Container.Provider>

function ParentComponent() {
  return (
    <Container.Provider>
      <ChildComponent />
    </Container.Provider>)}Copy the code

Container.useContainer()

function ChildComponent() {
  let input = Container.useContainer()
  return <input value={input.value} onChange={input.onChange} />
}
Copy the code

useContainer(Container)

import { useContainer } from "unstated-next"

function ChildComponent() {
  let input = useContainer(Container)
  return <input value={input.value} onChange={input.onChange} />
}
Copy the code

guide

If you have never used React Hooks before, I do not recommend you read on. Please read the React Hooks documentation on the React website.

First, using React Hooks, you can create a component like this:

function CounterDisplay() {
  let [count, setCount] = useState(0)
  let decrement = (a)= > setCount(count - 1)
  let increment = (a)= > setCount(count + 1)
  return (
    <div>
      <button onClick={decrement}>-</button>
      <p>You clicked {count} times</p>
      <button onClick={increment}>+</button>
    </div>)}Copy the code

Then, if you want to share the logic of the component, you can write it outside the component and define a custom hook:

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = (a)= > setCount(count - 1)
  let increment = (a)= > setCount(count + 1)
  return { count, decrement, increment }
}

function CounterDisplay() {
  let counter = useCounter()
  return (
    <div>
      <button onClick={counter.decrement}>-</button>
      <p>You clicked {counter.count} times</p>
      <button onClick={counter.increment}>+</button>
    </div>)}Copy the code

But in addition to sharing logic, you also want to share state. How do you do that?

At this point, the context comes into play:

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = (a)= > setCount(count - 1)
  let increment = (a)= > setCount(count + 1)
  return { count, decrement, increment }
}

let Counter = createContext(null)

function CounterDisplay() {
  let counter = useContext(Counter)
  return (
    <div>
      <button onClick={counter.decrement}>-</button>
      <p>You clicked {counter.count} times</p>
      <button onClick={counter.increment}>+</button>
    </div>)}function App() {
  let counter = useCounter()
  return (
    <Counter.Provider value={counter}>
      <CounterDisplay />
      <CounterDisplay />
    </Counter.Provider>)}Copy the code

It’s great, it’s perfect, and more people should write code like this.

But sometimes you need more structure and specific API design to get it right all the time.

By introducing the createContainer() function, you can use custom hooks as containers and define explicit apis to prevent incorrect use.

import { createContainer } from "unstated-next"

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = (a)= > setCount(count - 1)
  let increment = (a)= > setCount(count + 1)
  return { count, decrement, increment }
}

let Counter = createContainer(useCounter)

function CounterDisplay() {
  let counter = Counter.useContainer()
  return (
    <div>
      <button onClick={counter.decrement}>-</button>
      <p>You clicked {counter.count} times</p>
      <button onClick={counter.increment}>+</button>
    </div>)}function App() {
  return (
    <Counter.Provider>
      <CounterDisplay />
      <CounterDisplay />
    </Counter.Provider>)}Copy the code

Here’s the code before and after:

- import { createContext, useContext } from "react"
+ import { createContainer } from "unstated-next"

  function useCounter() {
    ...
  }

- let Counter = createContext(null)
+ let Counter = createContainer(useCounter)

  function CounterDisplay() {
- let counter = useContext(Counter)
+ let counter = Counter.useContainer()
    return (
      <div>
        ...
      </div>
    )
  }

  function App() {
- let counter = useCounter()
    return (
- 
      
+ 
      
        <CounterDisplay />
        <CounterDisplay />
      </Counter.Provider>
    )
  }
Copy the code

If you’re using TypeScript (and I encourage you to learn more about it), this also helps TypeScript’s built-in inference work better. As long as your custom hook type is perfect, the type is inferred automatically.

prompt

Tip #1: Combine Containers

Because we only used custom React hooks, containers can be combined inside other hooks.

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = (a)= > setCount(count - 1)
  let increment = (a)= > setCount(count + 1)
  return { count, decrement, increment, setCount }
}

let Counter = createContainer(useCounter)

function useResettableCounter() {
  let counter = Counter.useContainer()
  let reset = (a)= > counter.setCount(0)
  return { ...counter, reset }
}
Copy the code

Tip #2: Keep Containers small

This is very useful for keeping containers small and concentrated. This is important if you want to logically split code in containers. Simply move them into your own hooks and save only the state of the containers.

function useCount() {
  return useState(0)}let Count = createContainer(useCount)

function useCounter() {
  let [count, setCount] = Count.useContainer()
  let decrement = (a)= > setCount(count - 1)
  let increment = (a)= > setCount(count + 1)
  let reset = (a)= > setCount(0)
  return { count, decrement, increment, reset }
}
Copy the code

Tip #3: Optimize components

Unstated-next no optimization required. All the optimizations you’re going to do are standard React optimizations.

1) Optimize time-consuming subtrees by splitting components

Before optimization:

function CounterDisplay() {
  let counter = Counter.useContainer()
  return (
    <div>
      <button onClick={counter.decrement}>-</button>
      <p>You clicked {counter.count} times</p>
      <button onClick={counter.increment}>+</button>
      <div>
        <div>
          <div>
            <div>SUPER EXPENSIVE RENDERING STUFF</div>
          </div>
        </div>
      </div>
    </div>)}Copy the code

After the optimization:

function ExpensiveComponent() {
  return (
    <div>
      <div>
        <div>
          <div>SUPER EXPENSIVE RENDERING STUFF</div>
        </div>
      </div>
    </div>)}function CounterDisplay() {
  let counter = Counter.useContainer()
  return (
    <div>
      <button onClick={counter.decrement}>-</button>
      <p>You clicked {counter.count} times</p>
      <button onClick={counter.increment}>+</button>
      <ExpensiveComponent />
    </div>)}Copy the code

2) Use useMemo() to optimize time-consuming operations

Before optimization:

function CounterDisplay(props) {
  let counter = Counter.useContainer()

  // This value is recalculated every time 'counter' changes, which is time-consuming
  let expensiveValue = expensiveComputation(props.input)

  return (
    <div>
      <button onClick={counter.decrement}>-</button>
      <p>You clicked {counter.count} times</p>
      <button onClick={counter.increment}>+</button>
    </div>)}Copy the code

After the optimization:

function CounterDisplay(props) {
  let counter = Counter.useContainer()

  // This value is recalculated only when the input changes
  let expensiveValue = useMemo((a)= > {
    return expensiveComputation(props.input)
  }, [props.input])

  return (
    <div>
      <button onClick={counter.decrement}>-</button>
      <p>You clicked {counter.count} times</p>
      <button onClick={counter.increment}>+</button>
    </div>)}Copy the code

3) Use react.Memo () and useCallback() to reduce re-rendering times

Before optimization:

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = (a)= > setCount(count - 1)
  let increment = (a)= > setCount(count + 1)
  return { count, decrement, increment }
}

let Counter = createContainer(useCounter)

function CounterDisplay(props) {
  let counter = Counter.useContainer()
  return (
    <div>
      <button onClick={counter.decrement}>-</button>
      <p>You clicked {counter.count} times</p>
      <button onClick={counter.increment}>+</button>
    </div>)}Copy the code

After the optimization:

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = useCallback((a)= > setCount(count - 1), [count])
  let increment = useCallback((a)= > setCount(count + 1), [count])
  return { count, decrement, increment }
}

let Counter = createContainer(useCounter)

let CounterDisplayInner = React.memo(props= > {
  return (
    <div>
      <button onClick={props.decrement}>-</button>
      <p>You clicked {props.count} times</p>
      <button onClick={props.increment}>+</button>
    </div>)})function CounterDisplay(props) {
  let counter = Counter.useContainer()
  return <CounterDisplayInner {. counter} / >
}
Copy the code

An Unstated relationship

I think this library is a successor to the spirit of Unstated. Because I believed React was excellent in state management, and the only thing it lacked was easy sharing of state and logic, I created Unstated. The Unstated I created was a minimal solution to share state and logic with React.

However, React can do a much better job of sharing state and logic using Hooks. I even think Unstated becomes an unnecessary abstraction.

However, I think a lot of developers are struggling to understand how to use React Hooks to share state and logic so that applications can share state. It may just be a matter of documentation and community dynamics, but I think a new API can bridge that psychological gap.

This API is Unstated Next. It is not the smallest API for sharing state and logic in React, but rather the smallest API for understanding how to share state and logic in React.

I always took sides with React. I hope React wins. I want the community to abandon state management libraries like Redux and find a better way to use the React built-in toolchain.

If you don’t want to use Unstated, you just want to use React itself, and I really encourage you to do that. Write a blog post about it! To talk about it! Spread your knowledge in the community.

fromunstatedThe migration

I intentionally released it as a separate package because it was a complete rewrite of the original API. This way, you can install and migrate step by step.

Please provide me with feedback on this migration process, because over the next few months I want to get that feedback and do two things:

  • Make sure thatunstated-nextmeetunstatedAll requirements of the user.
  • Make sure thatunstatedConsumer code can be completely migrated tounstated-next.

I can add apis to either repository to make things easier for developers. For unstated-next, I will make sure that the new API is as small as possible, and at the same time, I will try to keep the library as small as possible.

In the future, I might merge unstated-Next into the main version of Unstated. Unstate-next is still there so you can install unstated@2 and unstate-next. When you are done migrating, you can update to unstated@3 and also remove unststation-next (make sure to update all your introductions, it should just be a find and replace process).

Although this is a major API change, I want you to make this migration as easy as possible. I’m using the latest React Hooks API to optimize for you instead of using the original Unstated.Container code. Feel free to provide feedback on how to do it better.

Starting nusr. Making. IO /