All Projects → alloc → Wana

alloc / Wana

Licence: mit
Easy observable state for React ⚡️

Programming Languages

typescript
32286 projects

Projects that are alternatives of or similar to Wana

micro-observables
A simple Observable library that can be used for easy state management in React applications.
Stars: ✭ 78 (-39.06%)
Mutual labels:  hooks, observable
state inspector
State change & method call logger. A debugging tool for instance variables and method calls.
Stars: ✭ 24 (-81.25%)
Mutual labels:  hooks, observable
Rx React Container
Use RxJS in React components, via HOC or Hook
Stars: ✭ 105 (-17.97%)
Mutual labels:  hooks, observable
Hooked Form
Performant 2KB React library to manage your forms
Stars: ✭ 110 (-14.06%)
Mutual labels:  hooks
Lefthook
Fast and powerful Git hooks manager for any type of projects.
Stars: ✭ 1,848 (+1343.75%)
Mutual labels:  hooks
Bach
Compose your async functions with elegance.
Stars: ✭ 117 (-8.59%)
Mutual labels:  observable
React Native Music App
React Native Music Application Example
Stars: ✭ 127 (-0.78%)
Mutual labels:  hooks
Clean State
🐻 A pure and compact state manager, using React-hooks native implementation, automatically connect the module organization architecture. 🍋
Stars: ✭ 107 (-16.41%)
Mutual labels:  hooks
Methodhook
hook java methods
Stars: ✭ 122 (-4.69%)
Mutual labels:  hooks
Hooks
Async middleware for JavaScript and TypeScript
Stars: ✭ 117 (-8.59%)
Mutual labels:  hooks
Hooks
A high-quality & reliable React Hooks library.
Stars: ✭ 7,841 (+6025.78%)
Mutual labels:  hooks
Graphql Hooks
🎣 Minimal hooks-first GraphQL client
Stars: ✭ 1,610 (+1157.81%)
Mutual labels:  hooks
Rxios
A RxJS wrapper for axios
Stars: ✭ 119 (-7.03%)
Mutual labels:  observable
Use Conditional Effect
React.useEffect, except you can pass a comparison function.
Stars: ✭ 111 (-13.28%)
Mutual labels:  hooks
Ajax Hook
🔱 Intercepting browser's AJAX requests which made by XMLHttpRequest.
Stars: ✭ 1,900 (+1384.38%)
Mutual labels:  hooks
Observablearray Rxswift
An array that can emit messages of elements and diffs on it's changing.
Stars: ✭ 108 (-15.62%)
Mutual labels:  observable
React Eva
Effects+View+Actions(React distributed state management solution with rxjs.)
Stars: ✭ 121 (-5.47%)
Mutual labels:  observable
Sound
🔊 A Vue composable for playing sound effects
Stars: ✭ 116 (-9.37%)
Mutual labels:  hooks
React Tensorflow
Tensorflow hooks for React.js
Stars: ✭ 115 (-10.16%)
Mutual labels:  hooks
Rx Http Request
The world-famous HTTP client Request now RxJS compliant, wrote in full Typescript | ES6 for client and server side.
Stars: ✭ 117 (-8.59%)
Mutual labels:  observable

wana

npm Build status codecov Bundle size Code style: Prettier Donate

Observable state with ease. ⚡️

Bring your React components to the next level. ⚛️

  • Transparent proxies (no special classes)
  • Implicit observation (use your objects like normal)
  • Observable objects, arrays, sets, and maps (even custom classes)
  • Automatic reactions to observable changes (see the auto/useAuto/withAuto functions)
  • Support for deep observation (see the watch function)
  • Memoized derivations (see the o/useDerived functions)
  • Prevent unnecessary renders
  • 80% less SLOC than MobX

 

Why build this? The goal of this library is to explore the MobX approach of writing React components by designing a new API from the ground up with React in mind from the get-go. Another goal is to keep a lean core by writing an observability engine from scratch.

Who built this? Alec Larson, the co-author of react-spring and immer. You can support his work by becoming a patron.

 

API Reference

  • o() for making observable objects
  • auto() for reactive effects
  • when() for reactive promises
  • no() for unobserved objects
  • noto() for unobserved scopes
  • watch() for listening to deep changes
  • withAuto() for reactive components
  • useAuto() for easy auto calls in components
  • useO() for observable component state
  • useDerived() for observable getters
  • useChanges() for change listeners
  • useEffects() for reactive mounting/unmounting of effects

 

o ⚡️

The o function wraps an object with an observable proxy (sorry, no IE11 support). These proxies are transparent, which means they look and act just like the object they wrap. The only difference is that now they're observable!

Even custom class instances can be wrapped with an observable proxy!

Passing the same object into o twice always returns the same proxy.

By wrapping a function with o, you get an observable getter, which memoizes its result until one of its observed values is changed. Calling an observable getter triggers an observation! To prevent leaks, call the dispose method before releasing an observable getter.

Passing an observable getter into o is a no-op.

Passing a primitive value into o is a no-op.

Note: Nested objects are not made observable. You'll need to wrap them with o calls too.

import { o } from 'wana'

const state: any[] = o([])
const state = o({ a: 1 })
const state = o(new Set())
const state = o(new Map())

 

auto ⚡️

The auto function runs the given effect immediately and tracks which observables are used. Upon changes to any of those observables, auto repeats the same process.

It does its best to react only to changes that affect its outcome.

import { o, auto } from 'wana'

const state = o({ count: 0 })

const observer = auto(() => {
  console.log(state.count % 2 ? 'odd' : 'even')
}) // logs "even"

state.count++ // logs "odd"
state.count++ // logs "even"

// Remember to call "dispose" to stop observing.
observer.dispose()

The auto function accepts a config object:

  • sync?: boolean
  • onError?: (this: Auto, error: Error) => void

By default, reactions are batched using the microtask queue. When sync is true, batching is skipped entirely.

By default, auto errors are rethrown. To customize error handling, provide an onError callback.

auto(effect, {
  onError(error) {
    // The `run` method lets you replace the effect.
    this.run(newEffect)
  }
})

 

when ️️⚡️

The when function creates a promise that resolves when the given condition returns true.

Any observable access within the condition is tracked. The condition is rerun whenever a change is detected.

import { o, when } from 'wana'

const obj = o({ count: 0 })
const promise = when(() => obj.count > 1)

obj.count++ // "promise" stays pending
obj.count++ // "promise" is resolved

The promise is rejected when the condition throws an error.

 

no ⚡️

The no function takes any observable object and returns the underlying object that isn't observable.

import { o, auto, no } from 'wana'

const obj = o({ a: 1, b: 2 })
auto(() => {
  // This will only be logged once.
  console.log(no(obj).a + no(obj).b)
})

// This change will not be observed.
obj.a = 2

Pass a function to wrap it with a new function that disables implicit observation for each call. If the wrapped function is used as a method, it has its this value preserved.

const state = o({
  count: 1,
})

const increment = no((n: number) => {
  state.count = state.count + n
})

auto(() => {
  increment(1) // Nothing will be observed in here.
})

assert(state.count == 2)

Pass anything else and you get the same value back.

 

noto ⚡️

The noto function (pronounced "not oh") is the exact opposite of the auto function. The function you pass to noto is called immediately (with implicit observation disabled) and whatever you return is passed through. Your function is never called again after that.

import { o, auto, noto } from 'noto'

const state = o({ count: 0 })

// Create an auto observer.
auto(() => {
  // Do something you want observed.
  noto(() => {
    // Do something you don't want observed.
  })
})

It's also useful inside methods of an observable object:

const state = o({
  count: 0,
  // Calling "increment" in an observable scope does
  // *not* result in "count" being observed.
  increment() {
    noto(() => this.count++)
  }
})

 

watch ⚡️

The watch function lets you listen for deep changes within an observable object.

import { o, watch } from 'wana'

const obj = o({ arr: o([]) })
const observer = watch(obj, change => console.log('changed:', change))

// Every observable object in `obj` is watched.
obj.x = true
obj.arr.push(1)

// You can even add new observables!
const foo = o({})
obj.arr.push(foo)
foo.x = true

// Call "dispose" to stop observing.
observer.dispose()

Note: When an object is made observable after being added to a watched object, it won't be watched. Be sure you pass objects to o() before adding them to a watched object!

 

withAuto ⚛️

The withAuto function wraps a React component, giving it the ability to track which observables are used during render. Upon changes to any of those observables, withAuto re-renders the component.

For convenience, you can add a ref argument to your component, and withAuto will wrap it with React.forwardRef for you. ✨

Note: Class components are not supported.

import { o, withAuto } from 'wana'

const MyView = withAuto(props => (
  <div>{props.user.name}</div>
))

const user = o({ name: 'Alec' })
const view = <MyView user={user} /> // renders "Alec"

user.name = 'Alice' // renders "Alice"

 

useAuto ⚛️

The useAuto hook calls auto within a useEffect callback, allowing you to run an effect in response to observable changes.

import { useAuto } from 'wana'

const MyView = props => {
  useAuto(() => {
    console.log(props.user.name)
  })
  return null
}

const user = o({ name: 'John Lennon' })
const view = <MyView user={user} /> // logs "John Lennon"

user.name = 'Yoko Ono' // logs "Yoko Ono"

By passing two functions, the 1st function will be observed and the 2nd function will run whenever the 1st function's result is a new value. The 2nd function is never observed.

// Usually, you will ignore the returned `Auto` instance, but it can be useful in some cases.
const auto = useAuto(
  // Derive a value from 1+ observables.
  () => props.user.name,
  // Run an effect when the derived value is changed.
  name => {
    console.log(name)
  },
  // Pass dependencies to avoid running on rerender.
  // The memoized value is reset when a dependency changes.
  [props.user]
)

 

useO ⚛️

The useO hook memoizes an observable object. When a non-observable object is passed, it gets wrapped with the o function.

import { useO } from 'wana'

const state = useO({ a: 1 })

assert(state.a == 1)

When a deps array is passed, its values are compared with the previous render. If any values have changed, the memoized state is replaced with whatever object you passed in. When no deps array is passed, the state is never replaced.

const state = useO(new Set(), deps)

When a function is passed, the function is called as if it were passed to React.useMemo, which means it will only be called again if any values in the deps array are changed.

const state = useO(() => [1, 2, 3], deps)

assert(state[0] == 1)

When a plain object is passed, its properties are scanned for "observable getters" (created with useDerived or o). In this case, these functions are converted into actual getters.

const state = useO({
  a: 1,
  b: useDerived(() => state.a + 1)
})

assert(state.b == 2)

 

useDerived ⚛️

The useDerived hook creates an observable getter. You can pass a deps array as the last argument if you want to mix non-observable props into the memoized value.

import { o, useDerived, useAuto } from 'wana'

const state = o({ count: 0 })

const MyView = props => {
  const foo = useDerived(() => state.count + props.foo, [props.foo])
  useAuto(() => {
    console.log('foo:', foo())
  })
  return <div />
}

 

useChanges ⚛️

The useChanges hook lets you listen for Change events on an observable object. Only shallow changes are reported.

import { o, useChanges } from 'wana'

const state = o({ count: 0 })

const MyView = () => {
  useChanges(state, console.log)
  return null
}

 

useEffects ⚛️

The useEffects hook works differently depending on the type of object you pass it.

For arrays and sets, an effect is mounted for each unique value. When a unique value is added, an effect is mounted. When removed, its effect is unmounted, using the cleanup function returned by its effect.

import { o, useEffects } from 'wana'

const arr = o([ 1, 2 ])

const MyView = () => {
  useEffects(arr, value => {
    console.log('Added value:', value)
    return () => {
      console.log('Removed value:', value)
    }
  })
  return null
}

For maps and plain objects, an effect is mounted for each unique key. When a unique key is added, an effect is mounted. When removed, its effect is unmounted, using the cleanup function returned by its effect. Whenever a key's value is replaced, the effect is remounted.

import { o, useEffects } from 'wana'

const obj = o({ a: 1, b: 2 })

const MyView = () => {
  useEffects(obj, (value, key) => {
    console.log('Added key/value:', key, '=>', value)
    return () => {
      console.log('Removed key/value:', key, '=>', value)
    }
  })
  return null
}

 

Donate

If you love this library, please donate! I have no income currently, because I'm working full-time on a startup. Any amount is greatly appreciated. 🥰

  • ETH: 0xa446626195bbe4d0697e729c1433a86fB6Cf66cF
  • BTC: 17vYtAUPKXzubMEnNcN8SiuFgicrd5Rp9A
  • KIN: GBU7RDRD7VDVT254RR6PGMBJESXQVDHJ5CGGODZKRXM2P4MP3G5QSAMH
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].