A hook and class decorators for composing custom react / preact hooks declaratively as class instances, rather than using a plain composed function. The goal is to provide an alternative and ergonomic classical mechanism for constructing, composing, and extending custom hooks and their returned api's.
import React from 'react'
import { isState, isEffect } from 'use-magic-class'
import { useMagicClass } from 'use-magic-class/react'
class MagicClass {
@isState
public statefulValue = 1
@isEffect<MagicClass>(({ statefulValue }) => (
[statefulValue]
))
public logValue() {
console.log(this.statefulValue)
}
}
const Component = () => {
const magic = useMagicClass(MagicClass)
return (
<button onClick={() => {
magic.statefulValue++
}}>
{magic.statefulValue}
</button>
)
Creates an instance of a class decorated with the decorators in the following section. The decorators map to further hook calls internally, which are combined with the decorated properties / methods to create a composed hook api. See Example or the examples directory for usage.
Decorates a stateful property, that when changed triggers a state update. Just like with setState, only properties that are not strictly equal will trigger an update. Internally maps to a useState
call.
import { isState } from 'use-magic-class'
class State {
@isState
public number = 1
@isState
private string = 'string'
@isState
protected boolean = false
}
Decorates a method to indicate it should be called as an effect. Any dependencies are passed to the decorator either as an array, or as a function that returns an array, and takes the class instance as its argument. Internally maps to a useEffect
call.
import { isEffect } from 'use-magic-class'
class Effect {
public number = 1
@isEffect()
public runEveryTime() {
return () => { /* cleanup logic */ }
}
@isEffect([])
private runOnce() {}
@isEffect<Effect>((effect) => [effect.number])
protected runWhenChanged() {}
}
Decorates a method to indicate it should be called as an layout effect. Any dependencies are passed to the decorator either as an array, or as a function that returns an array, and takes the class instance as its argument. Internally maps to a useLayoutEffect
call.
import { isLayoutEffect } from 'use-magic-class'
class LayoutEffect {
public number = 1
@isLayoutEffect()
public runEveryTime() {
return () => { /* cleanup logic */ }
}
@isLayoutEffect([])
private runOnce() {}
@isLayoutEffect<LayoutEffect>((effect) => [effect.number])
protected runWhenChanged() {}
}
Decorates a property to indicate that it should be populated and updated from context. Optionally takes a transform function which recieves the current context value as its argument. Maps internally to a useContext
call.
import createContext from 'react'
import { isContext } from 'use-magic-class'
const ObjectContext = createContext<{
number: number
}>({number: 1 })
class Context {
@isContext(ObjectContext)
public object: { number: number } | null = null
@isContext(ObjectContext, ({ object }) => object.number)
private number: number | null = null
}
Decorates a readonly computed property to indicate that its return value should be memoized. Any dependencies are passed to the decorator either as an array, or as a function that returns an array, and takes the class instance as its argument. Maps internally to a useMemo
call.
import { isState, isMemo } from 'use-magic-class'
class Memo {
@isState
private number: number = 1
@isMemo<Memo>(({ number }) => [number])
public get object: { number: number } () {
return {
number: this.number
}
}
}
Decorates a nested magic class property. The property should be initialized as an instance of the magic class. Maps internally to a useMagicClass
call.
import { isState, isMagic } from 'use-magic-class'
class State {
@isState
private number: number = 1
}
class Composition {
@isMagic
public state = new State()
}
This project utilizes yarn
and yarn workspaces. To run tests, run from the root directory:
# once
yarn
# and each time
yarn workspace use-magic-class test