• Stars
    star
    121
  • Rank 293,924 (Top 6 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 5 years ago
  • Updated over 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A state management library for React combined immutable, mutable and reactive mode

Welcome to bistate 👋

npm version Build Status Documentation Maintenance License: MIT Twitter: guyingjie129

Create the next immutable state tree by simply modifying the current tree

bistate is a tiny package that allows you to work with the immutable state in a more mutable and reactive way, inspired by vue 3.0 reactivity API and immer.

🏠 Homepage

Benefits

bistate is like immer but more reactive

  • Immutability with normal JavaScript objects and arrays. No new APIs to learn!
  • Strongly typed, no string based paths selectors etc.
  • Structural sharing out of the box
  • Deep updates are a breeze
  • Boilerplate reduction. Less noise, more concise code.
  • Provide react-hooks API
  • Small size
  • Reactive

Environment Requirement

  • ES2015 Proxy
  • ES2015 Symbol

Can I Use Proxy?

How it works

Every immutable state is wrapped by a proxy, has a scapegoat state by the side.

immutable state + scapegoat state = bistate

  • the immutable target is freezed by proxy
  • scapegoat has the same value as the immutable target
  • mutate(() => { the_mutable_world }), when calling mutate(f), it will
    • switch all operations to scapegoat instead of the immutable target when executing
    • switch back to the immutable target after executed
    • create the next bistate via scapegoat and target, sharing the unchanged parts
    • we get two immutable states now

Install

npm install --save bistate
yarn add bistate

Usage

Counter

import React from 'react'
// import react-hooks api from bistate/react
import { useBistate, useMutate } from 'bistate/react'

export default function Counter() {
  // create state via useBistate
  let state = useBistate({ count: 0 })

  // safely mutate state via useMutate
  let incre = useMutate(() => {
    state.count += 1
  })

  let decre = useMutate(() => {
    state.count -= 1
  })

  return (
    <div>
      <button onClick={incre}>+1</button>
      {state.count}
      <button onClick={decre}>-1</button>
    </div>
  )
}

TodoApp

function Todo({ todo }) {
  let edit = useBistate({ value: false })
  /**
   * bistate text is reactive
   * we will pass the text down to TodoInput without the need of manually update it in Todo
   * */
  let text = useBistate({ value: '' })

  // create a mutable function via useMutate
  let handleEdit = useMutate(() => {
    edit.value = !edit.value
    text.value = todo.content
  })

  let handleEdited = useMutate(() => {
    edit.value = false
    if (text.value === '') {
      // remove the todo from todos via remove function
      remove(todo)
    } else {
      // mutate todo even it is not a local bistate
      todo.content = text.value
    }
  })

  let handleKeyUp = useMutate(event => {
    if (event.key === 'Enter') {
      handleEdited()
    }
  })

  let handleRemove = useMutate(() => {
    remove(todo)
  })

  let handleToggle = useMutate(() => {
    todo.completed = !todo.completed
  })

  return (
    <li>
      <button onClick={handleRemove}>remove</button>
      <button onClick={handleToggle}>{todo.completed ? 'completed' : 'active'}</button>
      {edit.value && <TodoInput text={text} onBlur={handleEdited} onKeyUp={handleKeyUp} />}
      {!edit.value && <span onClick={handleEdit}>{todo.content}</span>}
    </li>
  )
}

function TodoInput({ text, ...props }) {
  let handleChange = useMutate(event => {
    /**
     * we just simply and safely mutate text at one place
     * instead of every parent components need to handle `onChange` event
     */
    text.value = event.target.value
  })
  return <input type="text" {...props} onChange={handleChange} value={text.value} />
}

API

import { createStore, mutate, remove, isBistate, debug, undebug } from 'bistate'
import { 
  useBistate, 
  useMutate, 
  useBireducer, 
  useComputed, 
  useBinding, 
  view, 
  useAttr, 
  useAttrs 
} from 'bistate/react'

useBistate(array | object, bistate?) -> bistate

receive an array or an object, return bistate.

if the second argument is another bistate which has the same shape with the first argument, return the second argument instead.

let Child = (props: { counter?: { count: number } }) => {
  // if props.counter is existed, use props.counter, otherwise use local bistate.
  let state = useBistate({ count: 0 }, props.counter)

  let handleClick = useMutate(() => {
    state.count += 1
  })

  return <div onClick={handleClick}>{state.count}</div>
}

// use local bistate
<Child />
// use parent bistate
<Child counter={state} />

useMutate((...args) => any_value) -> ((...args) => any_value)

receive a function as argument, return the mutable_function

it's free to mutate any bistates in mutable_function, not matter where they came from(they can belong to the parent component)

useBireducer(reducer, initialState) -> [state, dispatch]

receive a reducer and an initial state, return a pair [state, dispatch]

its' free to mutate any bistates in the reducer funciton

import { useBireducer } from 'bistate/react'

const Test = () => {
  let [state, dispatch] = useBireducer(
    (state, action) => {
      if (action.type === 'incre') {
        state.count += 1
      }

      if (action.type === 'decre') {
        state.count -= 1
      }
    },
    { count: 0 }
  )

  let handleIncre = () => {
    dispatch({ type: 'incre' })
  }

  let handleIncre = () => {
    dispatch({ type: 'decre' })
  }

  // render view
}

useComputed(obj, deps) -> obj

Create computed state

let state = useBistate({ first: 'a', last: 'b' })

// use getter/setter
let computed = useComputed({
  get value() {
    return state.first + ' ' + state.last
  },
  set value(name) {
    let [first, last] = name.split(' ')
    state.first = first
    state.last = last
  }
}, [state.first, state.last])

let handleEvent = useMutate(() => {
  console.log(computed.value) // 'a b'
  // update
  computed.value = 'Bill Gates'

  console.log(state.first) // Bill
  console.log(state.last) // Gates
})

useBinding(bistate) -> obj

Create binding state

A binding state is an object has only one filed { value }

let state = useBistate({ text: 'some text' })

let { text } = useBinding(state)

// don't do this
// access field will trigger a react-hooks
// you should always use ECMAScript 6 (ES2015) destructuring to get binding state
let bindingState = useBinding(state)
if (xxx) xxx = bindingState.xxx

let handleChange = () => {
  console.log(text.value) // some text
  console.log(state.text) // some text
  text.value = 'some new text'
  console.log(text.value) // some new text
  console.log(state.text) // some new text
}

It's useful when child component needs binding state, but parent component state is not.

function Input({ text, ...props }) {
  let handleChange = useMutate(event => {
    /**
     * we just simply and safely mutate text at one place
     * instead of every parent components need to handle `onChange` event
     */
    text.value = event.target.value
  })
  return <input type="text" {...props} onChange={handleChange} value={text.value} />
}

function App() {
  let state = useBistate({ 
    fieldA: 'A', 
    fieldB: 'B', 
    fieldC: 'C'
  })
  let { fieldA, fieldB, fieldC } = useBinding(state)

  return <>
    <Input text={fieldA} />
    <Input text={fieldB} />
    <Input text={fieldC} />
  </>
}

view(FC) -> FC

create a two-way data binding function-component

const Counter = view(props => {
  // Counter will not know the count is local or came from the parent
  let count = useAttr('count', { value: 0 })

  let handleClick = useMutate(() => {
    count.value += 1
  })

  return <button onClick={handleClick}>{count.value}</button>
})

// use local bistate
<Counter />

// create a two-way data binding connection with parent bistate
<Count count={parentBistate.count} />

useAttrs(initValue) -> Record<string, bistate>

create a record of bistate, when the value in props[key] is bistate, connect it.

useAttrs must use in view(fc)

const Test = view(() => {
  // Counter will not know the count is local or came from the parent
  let attrs = useAttrs({ count: { value: 0 } })

  let handleClick = useMutate(() => {
    attrs.count.value += 1
  })

  return <button onClick={handleClick}>{attrs.count.value}</button>
})

// use local bistate
<Counter />

// create a two-way data binding connection with parent bistate
<Count count={parentBistate.count} />

useAttr(key, initValue) -> bistate

a shortcut of useAttrs({ [key]: initValue })[key], it's useful when we want to separate attrs

createStore(initialState) -> { subscribe, getState }

create a store with an initial state

store.subscribe(listener) -> unlisten

subscribe to the store, and return an unlisten function

Every time the state has been mutated, a new state will publish to every listener.

store.getState() -> state

get the current state in the store

let store = createStore({ count: 1 })
let state = store.getState()

let unlisten = store.subscribe(nextState => {
  expect(state).toEqual({ count: 1 })
  expect(nextState).toEqual({ count: 2 })
  unlisten()
})

mutate(() => {
  state.count += 1
})

mutate(f) -> value_returned_by_f

immediately execute the function and return the value

it's free to mutate the bistate in mutate function

remove(bistate) -> void

remove the bistate from its parent

isBistate(input) -> boolean

check if input is a bistate or not

debug(bistate) -> void

enable debug mode, break point when bistate is mutating

undebug(bistate) -> void

disable debug mode

Caveats

  • only supports array and object, other data types are not allowed

  • bistate is unidirectional, any object or array appear only once, no circular references existed

let state = useBistate([{ value: 1 }])

mutate(() => {
  state.push(state[0])
  // nextState[0] is equal to state[0]
  // nextState[1] is not equal to state[0], it's a new one
})
  • can not spread object or array as props, it will lose the reactivity connection in it, should pass the reference
// don't do this
<Todo {...todo} />

// do this instead
<Todo todo={todo} />
  • can not edit state or props via react-devtools, the same problem as above

  • useMutate or mutate do not support async function

const Test = () => {
  let state = useBistate({ count: 0 })

  // don't do this
  let handleIncre = useMutate(async () => {
    let n = await fetchData()
    state.count += n
  })

  // do this instead
  let incre = useMutate(n => {
    state.count += n
  })

  let handleIncre = async () => {
    let n = await fetchData()
    incre(n)
  }

  return <div onClick={handleIncre}>test</div>
}

Author

👤 Jade Gu

🤝 Contributing

Contributions, issues and feature requests are welcome!

Feel free to check issues page.

Show your support

Give a ⭐️ if this project helped you!

📝 License

Copyright © 2019 Jade Gu.

This project is MIT licensed.


This README was generated with ❤️ by readme-md-generator

More Repositories

1

react-lite

An implementation of React v15.x that optimizes for small script size
JavaScript
1,726
star
2

factor-network

A simple factor network implementation written by JavaScript
JavaScript
542
star
3

Lucifier129.github.io

Lucifier129.github.io
JavaScript
410
star
4

react-imvc

An Isomorphic MVC Framework supports both SSR and CSR
JavaScript
266
star
5

Isomorphism-react-todomvc

Isomorphism javascript of todomvc powered by react and express
JavaScript
196
star
6

pull-element

Lightweight, high-performance and smooth pull element effect that support all directions
JavaScript
174
star
7

flappy-bird

flappy-bird game with time travel written by react and create-react-app
JavaScript
95
star
8

promise-aplus-impl

A simple implementation of Promise /A+ Spec
JavaScript
91
star
9

simple-machine-learning-demo

simple machine learning demo
JavaScript
64
star
10

relite

a redux-like library for managing state with simpler api
TypeScript
57
star
11

rxjs-react

make your react-component become reactive with rxjs
JavaScript
55
star
12

isomorphic-cnode

isomorphic-cnode
JavaScript
54
star
13

language-implementation-patterns

《编程语言实现模式》的相关练习
HTML
40
star
14

rust-ray-tracing-demo

The rust implementation of <Ray Tracing in One Weekend>
JavaScript
40
star
15

fetch-imgs

40 行 node.js 代码实现简易的图片爬虫
JavaScript
39
star
16

react-stateful

A simple implementation of React-State-Hook and React-Effect-Hook to show how React-Hooks work
JavaScript
34
star
17

react-use-setup

Implement the mechanism of Vue 3.0 Composition API for React based on React Hooks
TypeScript
30
star
18

coproduct

A small library aims to improve better tagged-unions/discriminated-unions supporting for TypeScript
TypeScript
29
star
19

create-app

configuring once, rendering both client and server.
TypeScript
28
star
20

the-super-tiny-jsx-compiler

the-super-tiny-jsx-compiler
JavaScript
23
star
21

react-imvc-template

react-imvc-template
JavaScript
21
star
22

functional-di

Dependency injection in functional style
TypeScript
18
star
23

next-mvc

An isomorphic mvc framework based on next.js, react, redux and immer
JavaScript
18
star
24

pure-model

A framework for writing model-oriented programming
TypeScript
16
star
25

jiandanimgs

煎蛋网高质量妹子图无广告版
JavaScript
15
star
26

monodic

Monorepo: 多项目单仓库工程管理方案
TypeScript
14
star
27

coeffect

An effect-management library for React
TypeScript
14
star
28

vdom-engine

Rethink and redesign React for Web
JavaScript
14
star
29

youmightnotneedflux

you might not need flux
JavaScript
13
star
30

gvs

Global variables sniffer
JavaScript
13
star
31

learn-webgl

some demos for learning webgl
JavaScript
13
star
32

jplus

jQuery指令插件
JavaScript
13
star
33

refer

redux-like library for handling global state on functional style
JavaScript
12
star
34

react-props

inject props to react component for high performance rendering
JavaScript
11
star
35

create-react-store

Create reactive stores for React App
TypeScript
10
star
36

react-hooks-demo

A demo based on react-hooks for show-case
JavaScript
10
star
37

training

9
star
38

graphql-dynamic

Dynamic, schema-less, directive-driven GraphQL
JavaScript
8
star
39

react-director

es2016-base router for react on decorator style
JavaScript
8
star
40

todo-imvc

todo list app powered by react-imvc
JavaScript
8
star
41

refer-dom

mvvm library base on virtural-dom and flux pattern
JavaScript
7
star
42

fe-starter

frontend-starter
JavaScript
7
star
43

costate

A state management library for react inspired by vue 3.0 reactivity api and immer
TypeScript
7
star
44

react-core-unit-testing

Use to implement your own reactjs
JavaScript
6
star
45

react-page-template

react 多页应用的模板,支持服务端渲染
JavaScript
6
star
46

umd-pack

a tool for creating library bundle file in UMD-style(forked from create-react-app)
JavaScript
5
star
47

sukkula

A state-management library aims to combine the best parts of redux and rxjs
JavaScript
5
star
48

create-react-store-todo-app

A Todo App demo powered by create-react-store
CSS
5
star
49

Agent

代理模式:将函数调用转化为配置模式
JavaScript
5
star
50

retour

an easy way to create isomorphic web app, render your page on both server-side and client-side
JavaScript
4
star
51

rxjs-animation-demo

A demo for rxjs animation
TypeScript
4
star
52

costate-examples

online demo
JavaScript
3
star
53

bistate-examples

demos for https://github.com/Lucifier129/bistate
JavaScript
3
star
54

slidev-slides

Slides via slidev
TypeScript
3
star
55

jade-incubator

Some interesting projects for POC, don't use them in production!
TypeScript
3
star
56

next-mvc-cnode

cnode web app with SSR powered by next-mvc
CSS
2
star
57

rocket-todo-app

A simple todo app powered by rocket and react
Rust
2
star
58

process

javascript 流程管理机制
JavaScript
2
star
59

jMin.js

自己写的手机端微型-类jQuery工具库
JavaScript
2
star
60

react-mvc-component

React Component in MVC style
2
star
61

isomorphism-react-file-system

同构react文件系统(娱乐项目)
JavaScript
1
star
62

jsx-template

simple jsx-template
JavaScript
1
star
63

observe.js

观察对象的属性,当它变化时,调用指定函数(更新:支持事件命名空间)。
JavaScript
1
star
64

webpack-workflow

webpack workflow for SPA
JavaScript
1
star
65

fe-tutorial

1
star
66

rx-view

react meets rxjs
JavaScript
1
star
67

static-server-toy

build static server using node.js in some different style
JavaScript
1
star
68

react-use-setup-examples

JavaScript
1
star
69

Msg

订阅者/发布者模式,自定义消息类型的工具库
JavaScript
1
star
70

esnext-framework

探究基于将来版本的 ECMAScript 框架模式
JavaScript
1
star