Skip to content

Latest commit

 

History

History
312 lines (253 loc) · 7.38 KB

README.md

File metadata and controls

312 lines (253 loc) · 7.38 KB

English
(Please contribute translations!)

Use Global State Hook

Super tiny state sharing library with hooks

Install

npm install --save global-state-hook

Update 2023:

With React 18 support useSyncExternalStore, you can now write your React App like that:

let textStore3 = createSubscription({ value: "Text 3", value2: "Text 4" })
function Text3() {
  let { state, subscription } = useSyncStore(textStore3)
  return (
    <div>
      <input
        value={state.value}
        onChange={(e) => subscription.updateState({ value: e.target.value })}
      />
    </div>
  )
}
function Text4() {
  let { state, subscription } = useSyncStore(textStore3)
  return (
    <div>
      <input
        value={state.value2}
        onChange={(e) => subscription.updateState({ value2: e.target.value })}
      />
    </div>
  )
}

Update 2022:

With new Reactive pattern, you can now write your React App like that:

const sourceOfTruth = createReactive({
  text1: "Text 1 sync together",
  text2: "Text 2 walk alone.",
})
const Text1 = () => {
  const state = useReactive(sourceOfTruth, ["text1"])
  return (
    <input
      value={state.text1}
      onChange={(e) => (state.text1 = e.target.value)}
    />
  )
}
const Text2 = () => {
  const state = useReactive(sourceOfTruth, ["text2"])
  return (
    <input
      value={state.text2}
      onChange={(e) => (state.text2 = e.target.value)}
    />
  )
}
const ReactiveApp = () => {
  return (
    <div>
      <h1>Reactive pattern:</h1>
      <Text1 />
      <Text2 />
      <Text1 />
    </div>
  )
}

Example

Edit bold-ellis-6rg1t

import React from "react"
import { createSubscription, useSubscription } from "global-state-hook"
import { render } from "react-dom"

const counterSubscription = createSubscription({ count: 0, foo: 10 })

function CounterDisplay() {
  let { state, setState } = useSubscription(counterSubscription)
  return (
    <div>
      <button onClick={() => setState({ count: state.count - 1 })}>-</button>
      <span>{state.count}</span>
      <button onClick={() => setState({ count: state.count + 1 })}>+</button>
    </div>
  )
}
function FooDisplay() {
  // Only update when foo change
  let { state, setState } = useSubscription(counterSubscription, ["foo"])
  return (
    <div>
      <button onClick={() => setState({ foo: state.foo - 1 })}>-</button>
      <span>{state.foo}</span>
      <button onClick={() => setState({ foo: state.foo + 1 })}>+</button>
    </div>
  )
}

function App() {
  return (
    <>
      <CounterDisplay />
      <FooDisplay />
    </>
  )
}

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

API

createSubscription(initialState)

import { createSubscription } from "global-state-hook"

const counterSubscription = createSubscription({ count: 0 })

useSubscription(subscriber)

import { createSubscription, useSubscription } from "global-state-hook"

const counterSubscription = createSubscription({ count: 0 })

// Your custom hook goes here so you can share it to anywhere
const useCounter = () => {
  let { state, setState } = useSubscription(counterSubscription)
  const increment = () => setState({ count: state.count + 1 })
  const decrement = () => setState({ count: state.count + 1 })
  return { count: state.count, increment, decrement }
}

function Counter() {
  let { count, increment, decrement } = useCounter()
  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  )
}

useSubscription(subscriber, pick: string[])

import { createSubscription, useSubscription } from "global-state-hook"

const counterSubscription = createSubscription({ count: 0, foo: 10 })

function Foo() {
  // Only update when foo change
  let { state, setState } = useSubscription(counterSubscription, ["foo"])

  return (
    <div>
      <button onClick={() => setState({ foo: state.foo - 1 })}>-</button>
      <span>{state.foo}</span>
      <button onClick={() => setState({ foo: state.foo + 1 })}>+</button>
    </div>
  )
}

Why use global-state-hook?

It's minimal You only need to learn 2 API: createSubscription, useSubscription.

It's easy to integrate Can integrate with any React library.

It's small Only 50 lines of code for this library.

Guide

Global state is not bad.

It's about how you manage it, global state will not be bad if you do it the right way. You can consider using Context.Provider to provide your global subscription so it can be clean up after the component unmounted. Example:

const TextContext = React.createContext < any > null

const useTextValue = () => {
  const textSubscription = useContext(TextContext)
  let { state, setState } = useSubscription(textSubscription)
  const onChange = (e) => setState({ value: e.target.value })
  return { value: state.value, onChange }
}

function Text() {
  let { value, onChange } = useTextValue()
  return (
    <div>
      <input value={value} onChange={onChange} />
    </div>
  )
}

function TextComponent() {
  const textSubscription = createSubscription({
    value: "The text will sync together",
  })
  return (
    <TextContext.Provider value={textSubscription}>
      <Text />
    </TextContext.Provider>
  )
}

Tip#1: Select your state property that you want to subscribe to

By default some people might recommend you to put only one state to a subscription, for example:

const textSubscription = createSubscription("Your initial state here")

let { state: text, setState: setText } = useSubscription(textSubscription)

// Change the text value:
setText("New text value")

But in case you have a very large Component with many state in it, so what is the proper way to handle? Just see my code below:

import { createSubscription, useSubscription } from "global-state-hook"

const multiStateSubscription = createSubscription({ foo: 1, bar: 2, baz: 3 })

function Foo() {
  // Only update when foo or baz change
  let { state, setState } = useSubscription(multiStateSubscription, [
    "foo",
    "baz",
  ])

  return (
    <div>
      <button onClick={() => setState({ foo: state.foo - 1 })}>-</button>
      <span>{state.foo}</span>
      <span>{state.baz}</span>
      <button onClick={() => setState({ foo: state.foo + 1 })}>+</button>
    </div>
  )
}

Reducer pattern:

For those who still in love with redux, the reducer pattern will fit for you:

const counterSubscription = createSubscription({ count: 0 })
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 }
    case "decrement":
      return { count: state.count - 1 }
    default:
      throw new Error()
  }
}

function Counter() {
  const { state, dispatch } = useReducerSubscription(
    counterSubscription,
    reducer,
  )
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  )
}

Misc

Support debugging with React Developer Tools

React Dev Tools

It's so easy right? :D