Lightweight proxy-based state manager, simple, fast and built with ❤️
⚡ Fast: hand-crafted and optimized to be as fast as possible
🤏 Tiny: 515 bytes in minimal config, 707 bytes with React support
🥧 Simple: ~140 lines of sparse code + ~70 lines for React support
🍳 Easy to use: just modify data, magic will take care of the rest!
⚛️ React & React Native: hooks and selectors, uses modern React 18 API
🔋 Charged: Map support (Set in plans), TypeScript defs and more
🪟 Transparent: original objects are not modified
import {create, objMapIgnoreSpecialsRef, ref} from 'picofly'
// may be not a class but a simple object
class State {
api = null
authToken = null
videos = new Map()
get signedIn() {
return !!this.authToken
}
}
export let createStore = () => {
let state = new State()
// see proxifiers section for options
let app = create(state, objMapIgnoreSpecialsRef)
// ref will prevent proxifing the api
app.api = ref(app, createApi())
return app
}
import {StoreProvider} from 'picofly/react'
import {createStore} from './store'
import VideoList from './video-list'
let app = createStore()
let App = () => {
return (
<StoreProvider value={app}>
<VideoList/>
</StoreProvider>
)
}
// use with hook
import {memo} from 'react'
import {useStore} from 'picofly/react'
import Video from './video'
export default memo(VideoList)
// will be re-rendered only when video added or removed
// because it uses video ids only but not videos data
function VideoList() {
let app = useStore()
let ids = Array.from(app.videos.keys())
let videos = ids.map(id => <Video id={id} key={id}/>)
let addVideo = useCallback(() => {
app.videos.set(Math.random(), {name: 'Cool video', watched: false})
})
return (
<div>
{videos}
<button onClick={addVideo}>ADD</button>
</div>
)
}
// use with selectors
import {useCallback} from 'react'
import {select} from 'picofly/react'
// normally is in business logic
let watchVideo = async (app, id) => {
await app.api.watchVideo(id)
let video = app.videos.get(id)
video.watched = true
}
// selector
let video = (app, props) => ({
video: app.videos.get(props.id),
})
// combine selectors and attach to component
// all props returned from selectors will be merged and passed to component
export default select(
video,
(app, props) => ({
// selectors are called in render context so you can use any hooks inside
onWatched: useCallback(() => watchVideo(app, props.id), [props.id]),
}),
)(Video)
// will be re-rendered only if the video name changed
// because it only depends on it
function Video({
video = {},
onWatched,
}) {
return (
<div>
<span>{video.name}</span>
<button onClick={onWatched}>WATCH</button>
</div>
)
}