Repository: yezyilomo/state-pool Branch: master Commit: 3228011d5254 Files: 76 Total size: 132.5 KB Directory structure: gitextract_iwqcu_93/ ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ └── node.js.yml ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── package.json ├── src/ │ ├── State.ts │ ├── Store.ts │ ├── index.ts │ ├── types.ts │ ├── useReducer.ts │ └── useState.ts ├── tests/ │ ├── store.clear.test.ts │ ├── store.getState.test.ts │ ├── store.getStateValue.test.ts │ ├── store.has.test.ts │ ├── store.items.test.ts │ ├── store.persist.test.ts │ ├── store.remove.test.ts │ ├── store.useReducer.test.ts │ ├── store.useState.test.ts │ ├── subscription.test.ts │ ├── useReducer.test.ts │ └── useState.test.ts ├── tsconfig.json └── website/ ├── .gitignore ├── README.md ├── babel.config.js ├── blog/ │ ├── 2019-05-28-first-blog-post.md │ ├── 2019-05-29-long-blog-post.md │ ├── 2021-08-01-mdx-blog-post.mdx │ ├── 2021-08-26-welcome/ │ │ └── index.md │ └── authors.yml ├── docs/ │ ├── api_reference/ │ │ ├── _category_.json │ │ ├── high_level_api/ │ │ │ ├── _category_.json │ │ │ ├── createStore.md │ │ │ ├── store.clear.md │ │ │ ├── store.getState.md │ │ │ ├── store.getStateValue.md │ │ │ ├── store.has.md │ │ │ ├── store.items.md │ │ │ ├── store.persist.md │ │ │ ├── store.remove.md │ │ │ ├── store.setState.md │ │ │ ├── store.subscribe.md │ │ │ ├── store.useReducer.md │ │ │ └── store.useState.md │ │ ├── intro.md │ │ ├── low_level_api/ │ │ │ ├── _category_.json │ │ │ ├── createState.md │ │ │ ├── useReducer.md │ │ │ └── useState.md │ │ └── typing-state.md │ ├── basic_concepts/ │ │ ├── _category_.json │ │ ├── derived_state.md │ │ ├── managing_subscriptions.md │ │ ├── state_persistence.md │ │ ├── store.md │ │ └── using_store_state.md │ ├── basic_tutorial/ │ │ ├── _category_.json │ │ └── intro.md │ └── introduction/ │ ├── _category_.json │ ├── getting_started.md │ ├── installation.md │ └── motivation.md ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src/ │ ├── components/ │ │ └── HomepageFeatures/ │ │ ├── index.js │ │ └── styles.module.css │ ├── css/ │ │ └── custom.css │ └── pages/ │ ├── index.js │ ├── index.module.css │ └── markdown-page.md └── static/ └── .nojekyll ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ patreon: yezyilomo ================================================ FILE: .github/workflows/node.js.yml ================================================ # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: Node.js CI on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [16.x, 18.x, 19.x, 20.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - run: npm ci - run: npm test # Run tests ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build /dist # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Yezy Ilomo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # [State Pool](https://yezyilomo.github.io/state-pool/) ![Build Status](https://github.com/yezyilomo/state-pool/actions/workflows/node.js.yml/badge.svg?branch=master) [![Build Size](https://img.shields.io/bundlephobia/minzip/state-pool?label=bundle-size&style=flat)](https://bundlephobia.com/result?p=state-pool) [![Version](https://img.shields.io/npm/v/state-pool?style=flat)](https://www.npmjs.com/package/state-pool) [![Downloads](https://img.shields.io/npm/dt/state-pool.svg?style=flat)](https://www.npmjs.com/package/state-pool) Transform your React app with our state management library! Declare global and local states like variables, powered by the magic of React hooks 🪄✨ ## Features & Advantages - Simple, familiar, flexible and very minimal core API but powerful - Built-in support for state persistence - Very easy to learn because its API is very similar to react state hook's API - Support selecting deeply nested state - Support creating state dynamically - Can be used outside react components - Doesn't wrap your app in context providers - Very organized API, You can do almost everything with a single import Want to see how this library is making all that possible? Check out the full documentation at [yezyilomo.github.io/state-pool](https://yezyilomo.github.io/state-pool/) You can also try live examples [Here](https://yezyilomo.github.io/state-pool-examples)
## How it Works 1. Create a state 2. Subscribe a component(s) to the state created 3. If a component wants to update the state, it sends update request 4. When a state receives update request, it performs the update and send signal to all components subscribed to it for them to update themselves(re-render)
## Installation ```sh npm install state-pool ``` Or ```sh yarn add state-pool ```
## Getting Started Using **state-pool** to manage state is very simple, all you need to do is 1. Create and initialize a state by using `createState` 2. Use your state in your component through `useState` hooks These two steps summarises pretty much everything you need to use **state-pool**. Below are few examples showing how to use **state-pool** to manage states. ```jsx // Example 1. import React from 'react'; import { createState } from 'state-pool'; const count = createState(0); // Create "count" state and initialize it with 0 function ClicksCounter(props){ // Use "count" state const [count, setCount] = count.useState(); const incrementCount = (e) => { setCount(count+1) } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ```
The other way to do it is using `useState` from `state-pool` ```jsx // Example 2. import React from 'react'; import { createState, useState } from 'state-pool'; const count = createState(0); // Create "count" state and initialize it with 0 function ClicksCounter(props){ // Use "count" state const [count, setCount] = useState(count); const incrementCount = (e) => { setCount(count+1) } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ```
## What about local state? With **state-pool**, state are just like variables, if declared on a global scope, it’s a global state and if declared on local scope it’s a local state, so the difference between global state and local state in **state-pool** lies where you declare them just like variables. Here is an example for managing local state ```jsx // Example 1. import React from 'react'; import { useState } from 'state-pool'; function ClicksCounter(props){ // Here `useState` hook will create "count" state and initialize it with 0 // Note: the `useState` hook used here is impored from state-pool and not react const [count, setCount] = useState(0); const incrementCount = (e) => { setCount(count+1) } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ```
If you don't want **state-pool's** `useState` to collide with **React's** `useState` you can import `StatePool` and use the hook from there, Here is an example ```jsx // Example 2. import React from 'react'; import StatePool from 'state-pool'; function ClicksCounter(props){ // Here `useState` hook will create "count" state and initialize it with 0 const [count, setCount] = StatePool.useState(0); const incrementCount = (e) => { setCount(count+1) } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ```
## Isn't `StatePool.useState` the same thing as `React.useState`? **Definitely. not!...** Both can be used to manage local state, and that's where the similarity ends. `StatePool.useState` offers more features, for one it offers a simple way to update nested data unlike `React.useState`, it's also flexible as it's used to manage both global state and local state. So you could say `React.useState` is a subset of `StatePool.useState`. Here is an example of `StatePool.useState` in action, updating nested data ```jsx // Example 2. import React from 'react'; import StatePool from 'state-pool'; const user = StatePool.createState({name: "Yezy", age: 25}); function UserInfo(props){ const [user, setUser, updateUser] = StatePool.useState(user); const updateName = (e) => { updateUser(user => { user.name = e.target.value; }); } return (
Name: {user.name}
); } ReactDOM.render(UserInfo, document.querySelector("#root")); ``` With `React.useState` you would need to recreate `user` object when updating `user.name`, but with `StatePool.useState` you don't need that, you just update the value right away. That's one advantage of using `StatePool.useState` but there are many more, you'll learn when going through [**documentation**📝](https://yezyilomo.github.io/state-pool/). ## Store based example If you have many states and you would like to organize them into a store, **state-pool** allows you to do that too and provides a ton of features on top of it. Here are steps for managing state with a store 1. Create a store(which is basically a container for your state) 1. Create and initialize a state by using `store.setState` 2. Use your state in your component through `store.useState` hooks These three steps summarises pretty much everything you need to manage state with a store. Below are few examples of store in action ```jsx // Example 1. import { createStore } from 'state-pool'; const store = createStore(); // Create store for storing our state store.setState("count", 0); // Create "count" state and add it to the store function ClicksCounter(props){ // Use "count" state const [count, setCount] = store.useState("count"); return (
Count: {count}
); } ```
```jsx // Example 2. import { createStore } from 'state-pool'; // Instead of using createStore and store.setState, // you can combine store creation and initialization as follows const store = createStore({"user", {name: "Yezy", age: 25}}); // create store and initialize it with user function UserInfo(props){ const [user, setUser, updateUser] = store.useState("user"); const updateName = (e) => { updateUser(user => { user.name = e.target.value; }); } return (
Name: {user.name}
); } ```
**State-pool** doesn't enforce storing your states in a store, If you don't like using the architecture of store you can still use **state-pool** without it. In **state-pool**, store is just a container for states, so you can still use your states without it, in fact **state-pool** doesn’t care where you store your states as long as you can access them you're good to go.
Pretty cool, right? ## [Documentation 📝](https://yezyilomo.github.io/state-pool/) Full documentation for this project is available at [yezyilomo.github.io/state-pool](https://yezyilomo.github.io/state-pool/), you are advised to read it inorder to utilize this library to the fullest. You can also try live examples [here](https://yezyilomo.github.io/state-pool-examples). ## Running Tests If you've forked this library and you want to run tests use the following command ```sh npm test ``` ================================================ FILE: babel.config.js ================================================ // babel.config.js module.exports = { presets: ['@babel/preset-env', '@babel/preset-react'], }; ================================================ FILE: package.json ================================================ { "name": "state-pool", "private": true, "version": "0.10.2", "description": "Transform your React app with our state management library! Declare global and local states like variables, powered by the magic of React hooks 🪄✨", "license": "MIT", "homepage": "https://github.com/yezyilomo/state-pool/blob/master/README.md", "repository": "https://github.com/yezyilomo/state-pool", "bugs": "https://github.com/yezyilomo/state-pool/issues", "keywords": [ "state", "immutable" ], "authors": [ "Yezileli Ilomo (https://github.com/yezyilomo)" ], "main": "index.js", "module": "esm/index.js", "types": "index.d.ts", "files": [ "**" ], "sideEffects": false, "scripts": { "prebuild": "shx rm -rf dist", "build": "tsc", "postbuild": "npm run copy", "test": "shx rm -rf dist && tsc && jest ./dist/test && shx rm -rf dist", "copy": "shx mkdir dist/esm && shx cp -r dist/src/* dist/esm && shx mv dist/src/* dist && shx rm -rf dist/{src,tests} && shx cp package.json README.md LICENSE dist && json -I -f dist/package.json -e \"this.private=false; this.devDependencies=undefined; this.scripts=undefined;\"", "publish:dist": "npm run build && cd dist && npm publish" }, "exports": { "./package.json": "./package.json", ".": { "types": "./index.d.ts", "module": "./esm/index.js", "import": "./esm/index.js", "default": "./index.js" } }, "dependencies": { "immer": ">=10.0.0" }, "peerDependencies": { "react": ">=16.8.3" }, "devDependencies": { "@babel/preset-env": "latest", "@babel/preset-react": "latest", "@testing-library/react-hooks": "latest", "@types/jest": "latest", "@babel/eslint-parser": "latest", "babel-jest": "latest", "jest": "latest", "json": "latest", "react": "latest", "react-test-renderer": "latest", "shx": "latest", "typescript": "latest" } } ================================================ FILE: src/State.ts ================================================ import { produce, nothing } from "immer"; import { StateInitializer, Selector, Patcher, Reducer, Unsubscribe, StateModifier, StateUpdater, SetState, UpdateState, } from './types'; import _useState from "./useState"; import _useReducer from "./useReducer"; type Refresh = () => void // Might subscribe to derived/selected state, so we use cuz it might not always be type Observer = (newState: ST) => void // Subscribe to a state type Subscriber = { observer: Observer, selector: Selector, refresh?: Refresh } export default class State { private value: T; private subscribers: Array>; constructor(initialValue: StateInitializer | T) { if (typeof initialValue === "function") { this.value = (initialValue as StateInitializer)(); } else { this.value = initialValue as T; } this.subscribers = []; } getValue(selector?: Selector): T | ST { if (selector) { return selector(this.value); } return this.value; } refresh(): void { this.subscribers.forEach(subscriber => { if (subscriber.refresh) { subscriber.refresh(); } }); } setValue( newValue: T | StateUpdater ): void; setValue( newValue: ST | StateUpdater, config: { selector?: Selector } ): void; setValue( newValue: ST | StateUpdater, config: { selector?: Selector, patcher?: Patcher }, ): void; setValue( newValue: (T | ST) | StateUpdater, config: { selector?: Selector, patcher?: Patcher } = {}, ): void { if (newValue === undefined) { this.__updateValue( (draftVal) => nothing as undefined, // nothing is equivalent to undefined config ) } else if (typeof newValue === 'function') { const reducer = newValue as StateUpdater this.setValue( reducer(this.getValue(config.selector)), config ) } else { this.__updateValue( (draftVal) => newValue, config ) } } updateValue( stateModifier: StateModifier ): void; updateValue( stateModifier: StateModifier, config: { selector?: Selector } ): void; updateValue( stateModifier: StateModifier, config: { selector?: Selector, patcher?: Patcher } ): void; updateValue( stateModifier: StateModifier | StateModifier, config: { selector?: Selector, patcher?: Patcher } = {}, ): void { const stateModifierWrapper = function (draftState) { // This wrapper is for disabling setting returned value // We don't allow returned value to be set(just return undefined) stateModifier(draftState) } this.__updateValue(stateModifierWrapper, config) } private __updateValue(stateUpdater: StateUpdater | StateModifier, config): void { // No need to do a lot of type checking here bcuz this is an internal method const selector = config.selector as Selector; const patcher = config.patcher as Patcher; const oldState = this.value; let newState: T; if (selector && patcher) { // Update the selected node first and get its value const selectedNodeValue: ST = produce(selector(oldState), stateUpdater) as ST; // Here we're patching back the updated selected node to the main state newState = produce( oldState, (draftCurrentState: T) => { // Avoid setting returns patcher(draftCurrentState, selectedNodeValue); } ) } else { newState = produce(oldState, stateUpdater); } this.value = newState; if (newState !== oldState) { // There's a new update this.subscribers.forEach(subscriber => { if (subscriber.selector(newState) !== subscriber.selector(oldState)) { // Node value has changed subscriber.observer( subscriber.selector(newState) ); } }); } } subscribe(itemToSubscribe: Subscriber | Observer): Unsubscribe { let _itemToSubscribe; if (typeof itemToSubscribe === 'function') { const selector: Selector = (state) => state; _itemToSubscribe = { observer: itemToSubscribe as Observer, selector: selector } as Subscriber; } else { _itemToSubscribe = itemToSubscribe as Subscriber; } if (this.subscribers.indexOf(_itemToSubscribe) === -1) { // Subscribe a component to this state this.subscribers.push(_itemToSubscribe); }; const unsubscribe = () => { this.subscribers = this.subscribers.filter( subscriber => (subscriber !== _itemToSubscribe) ); } return unsubscribe; } select(selector: Selector): DerivedState { return createDerivedState(this, selector); } useState(config?: {}): [ state: T, setState: SetState, updateState: UpdateState, stateObject: State ]; useState( config: { selector: Selector }, ): [ state: ST, setState: SetState, updateState: UpdateState, stateObject: State ]; useState( config: { selector: Selector, patcher: Patcher }, ): [ state: ST, setState: SetState, updateState: UpdateState, stateObject: State ] useState( config: { selector?: Selector, patcher?: Patcher } = {}, ): [ state: T | ST, setState: SetState, updateState: UpdateState, stateObject: State ] { return _useState(this, config); } useReducer<_T extends T, A>( reducer: Reducer<_T, A>, config?: {} ): [ state: _T, dispatch: (action: A) => void, stateObject: State<_T> ]; useReducer( reducer: Reducer, config: { selector: Selector } ): [ state: ST, dispatch: (action: A) => void, stateObject: State ]; useReducer( reducer: Reducer, config: { selector: Selector, patcher: Patcher } ): [ state: ST, dispatch: (action: A) => void, stateObject: State ] useReducer( reducer: Reducer, config: { selector?: Selector, patcher?: Patcher } = {} ): [ state: unknown, dispatch: (action: unknown) => void, stateObject: State ] { return _useReducer(reducer, this, config); } } export class DerivedState { State: State; selector: Selector; constructor(State: State, selector: Selector) { this.State = State; this.selector = selector; } getValue(): ST { return this.State.getValue(this.selector) as ST; } subscribe(observer?: Observer, refresh?: Refresh): Unsubscribe { const itemToSubscribe: Subscriber = { observer: observer, selector: this.selector, refresh: refresh } return this.State.subscribe(itemToSubscribe); } } export function createDerivedState(State: State, selector: Selector): DerivedState { return new DerivedState(State, selector); } export function createState(initialValue: StateInitializer | T): State { return new State(initialValue); } ================================================ FILE: src/Store.ts ================================================ import State, { createState } from './State'; import { StateInitializer, Selector, Patcher, Reducer, SetState, Unsubscribe, UpdateState } from './types'; // Observer function for a store type StoreObserver = (key: string, value: unknown) => void type PersistenceConfig = { saveState: (key: string, state: unknown, isInitialSet: boolean) => void, loadState: (key: string, noState: Empty) => unknown, removeState?: (key: string) => void, clear?: () => void, PERSIST_ENTIRE_STORE?: boolean } type StoreState = { state: State, unsubscribe: Unsubscribe, persist: boolean } type StoreInitializer = { [key: string]: unknown } | (() => { [key: string]: unknown }); const notImplementedErrorMsg = [ `You must implement 'loadState' and 'saveState' to be able `, 'to save state to your preffered storage. E.g \n', 'store.persist({ \n', ' saveState: function(key, state, isInitialSet){/*Code to save state to your storage*/}, \n', ' loadState: function(key, noState){/*Code to load state from your storage*/} \n', '}) \n' ].join(""); class Empty { } // Class for empty state/value const EMPTY = new Empty(); class PersistentStorage { SHOULD_PERSIST_BY_DEFAULT: boolean = false; loadState(key: string, noState: Empty): unknown { throw TypeError(notImplementedErrorMsg); } saveState(key: string, state: unknown, isInitialSet?: boolean) { throw TypeError(notImplementedErrorMsg); } removeState: (key: string) => void clear: () => void } export default class Store { private states: Map>; private subscribers: Array; private persistentStorage: PersistentStorage; constructor(storeInitializer?: StoreInitializer) { this.states = new Map(); this.subscribers = []; this.persistentStorage = new PersistentStorage(); if (storeInitializer) { if (typeof storeInitializer === "function") { storeInitializer = storeInitializer(); } for (let key in storeInitializer) { if (storeInitializer.hasOwnProperty(key)) { this.setState(key, storeInitializer[key]); } } } } subscribe(observer: StoreObserver): () => void { if (this.subscribers.indexOf(observer) === -1) { // Subscribe a component to this store this.subscribers.push(observer); } const unsubscribe = () => { this.subscribers = this.subscribers.filter( subscriber => subscriber !== observer ); } return unsubscribe } private onStoreUpdate(key: string, value: unknown): void { this.subscribers.forEach(subscriber => { subscriber(key, value); }); } persist(config: PersistenceConfig): void { if (config.saveState) { this.persistentStorage.saveState = config.saveState; } if (config.loadState) { this.persistentStorage.loadState = config.loadState; } if (config.removeState) { this.persistentStorage.removeState = config.removeState; } if (config.clear) { this.persistentStorage.clear = config.clear; } if (config.PERSIST_ENTIRE_STORE) { this.persistentStorage.SHOULD_PERSIST_BY_DEFAULT = config.PERSIST_ENTIRE_STORE; } } setState( key: string, initialValue: StateInitializer | T, { persist }: { persist?: boolean } = {} ): void { const shouldPersist: boolean = persist === undefined ? this.persistentStorage.SHOULD_PERSIST_BY_DEFAULT : persist; let shouldSaveToPersistentStorage = false; if (shouldPersist) { // Try to load state from persistent storage const savedState = this.persistentStorage.loadState(key, EMPTY); if (savedState !== EMPTY) { // We have a saved state, so we use it as the initialValue initialValue = savedState as T; } else { // We don't have a saved state so we have to set/save it // Here we set this flag to true but we'll save later after creating a state successfully shouldSaveToPersistentStorage = true; } } const onStateChange = (newValue: unknown) => { // Note key & persist variables depends on scope this.onStoreUpdate(key, newValue); if (shouldPersist) { this.persistentStorage.saveState(key, newValue, false); } } // Create a state const state: State = createState(initialValue); const unsubscribe = state.subscribe({ observer: onStateChange, selector: (st) => st }); const storeState = { "state": state, "unsubscribe": unsubscribe, "persist": shouldPersist } // Add state to the store this.states.set(key, storeState); if (shouldSaveToPersistentStorage) { // Saving state to persistent storage after we've created it successfully this.persistentStorage.saveState(key, state.getValue(), true); } } getState( key: string, config: { default?: StateInitializer | T, persist?: boolean } = {} ): State { let defaultValue; if (config.hasOwnProperty('default')) { // Use has set default explicitly defaultValue = config.default } else { // No default value defaultValue = EMPTY; } // Get key based state if (!this.has(key)) { // state is not found if (defaultValue !== EMPTY) { // Default value is found // Create a state and use defaultValue as the initial value this.setState( key, defaultValue as (StateInitializer | T), // Make sure we don't pass EMPTY value { persist: config.persist } ); } else { // state is not found and the default value is not specified const errorMsg = [ `There is no state with the key '${key}', `, `You are either trying to access a `, `state that doesn't exist or it was deleted.` ]; throw Error(errorMsg.join("")); } } return this.states.get(key).state as State; } has(key: string) { // Check if we have a state in a store return this.states.has(key); } items(): Array<[key: string, state: unknown, persist: boolean]> { const storeItems = []; this.states.forEach((storeState, key) => { storeItems.push([key, storeState.state.getValue(), storeState.persist]); }) return storeItems; } getStateValue(key: string, selector?): T | ST { const state = this.getState(key); return state.getValue(selector); } clear(fn?: () => void): void { // Copy store const storeCopy = this.states; // Clear store this.states = new Map(); if (this.persistentStorage.clear) { try { this.persistentStorage.clear() } catch (error) { // Ignore errors in clearing states } } if (fn) { // Run store re-initialization fn(); } storeCopy.forEach((oldState, key) => { // Unsubscribe from an old state oldState.unsubscribe() // Notify subscribers to a store that a state has been removed if (this.has(key)) { const newState = this.getState(key); this.onStoreUpdate(key, newState.getValue()); } // Rerender all components using this state oldState.state.refresh(); }) } remove(Statekey: string | string[], fn?: () => void): void { let keys: string[] = []; if (typeof Statekey === 'string') { keys = [Statekey]; } else { keys = Statekey; } const StatesToRemove: Map> = new Map(); keys.forEach(key => { // Copy state to remove from a store StatesToRemove.set(key, this.states.get(key)); // Remove state from a store this.states.delete(key); if ( StatesToRemove.get(key).persist && // Is state persisted this.persistentStorage.removeState // Is removeState Implemented ) { try { this.persistentStorage.removeState(key); } catch (error) { // Ignore error in removing state } } }); if (fn) { // Run state re-initialization fn(); } StatesToRemove.forEach((oldState, key) => { // Unsubscribe from an old state oldState.unsubscribe() // Notify subscribers to a store that a state has been removed if (this.has(key)) { const newState = this.getState(key); this.onStoreUpdate(key, newState.getValue()); } // Rerender all components depending on this state oldState.state.refresh(); }) } useState( key: string, config?: { default?: StateInitializer | T, persist?: boolean } ): [ state: T, setState: SetState, updateState: UpdateState, stateObject: State ]; useState( key: string, config: { selector: Selector, default?: StateInitializer | T, persist?: boolean }, ): [ state: ST, setState: SetState, updateState: UpdateState, stateObject: State ]; useState( key: string, config: { selector: Selector, patcher: Patcher, default?: StateInitializer | T, persist?: boolean }, ): [ state: ST, setState: SetState, updateState: UpdateState, stateObject: State ] useState( key: string, config: { selector?: Selector, patcher?: Patcher, default?: unknown, persist?: boolean } = {} ): [ state: unknown, setState: SetState, updateState: UpdateState, stateObject: State ] { const storeStateConfig = config as { default?, persist?}; const stateConfig = config as { selector?, patcher?}; const state = this.getState(key, storeStateConfig); return state.useState(stateConfig); } useReducer( reducer: Reducer, key: string, config?: { default?: StateInitializer | T, persist?: boolean } ): [ state: T, dispatch: (action: A) => void, stateObject: State ]; useReducer( reducer: Reducer, key: string, config: { selector?: Selector, default?: StateInitializer | T | never, persist?: boolean } ): [ state: ST, dispatch: (action: A) => void, stateObject: State ]; useReducer( reducer: Reducer, key: string, config: { selector?: Selector, patcher?: Patcher, default?: StateInitializer | T | never, persist?: boolean } ): [ state: ST, dispatch: (action: A) => void, stateObject: State ] useReducer( reducer: Reducer, key: string, config: { selector?: Selector, patcher?: Patcher, default?: unknown, persist?: boolean } = {} ): [ state: unknown, dispatch: unknown, stateObject: State ] { const storeStateConfig = config as { default?, persist?}; const stateConfig = config as { selector?, patcher?}; const state = this.getState(key, storeStateConfig); return state.useReducer(reducer, stateConfig); } } export function createStore(storeInitializer?: StoreInitializer): Store { // Create a store for key based state return new Store(storeInitializer); } ================================================ FILE: src/index.ts ================================================ import State, { createState, DerivedState, createDerivedState } from './State'; import Store, { createStore } from './Store'; import useReducer from './useReducer'; import useState from './useState'; export { State, createState, Store, createStore, useState, useReducer, DerivedState, createDerivedState, }; const StatePool = { State, createState, Store, createStore, useState, useReducer, DerivedState, createDerivedState, } export default StatePool; ================================================ FILE: src/types.ts ================================================ // Note; In all cases ST stands for type of selected state and T for base/original state // Function to call to unsubscribe from a state or a store export type Unsubscribe = () => void // Reducer tha's passed in useReducer hook export type Reducer = (state: ST, action: A) => ST // What's important is the type of what's returned, lib is the one passing state, so noworries export type Selector = (state: any) => ST // What's important is the type of value(to set), the lib is the one passing state, so noworries export type Patcher = (state: any, value: ST) => void // Function for initializing state export type StateInitializer = () => T // What's important is the export type of what's returned, lib is the one passing state export type StateUpdater = (state: ST) => ST // Given the obj to modify, it does modifications and return nothing export type StateModifier = (state: ST) => void // Setter returned by useState hook export type SetState = (newState: ST | StateUpdater) => void // Updater returned by useState hook export type UpdateState = (updater: StateModifier) => void ================================================ FILE: src/useReducer.ts ================================================ import React from 'react'; import State, { createState } from './State'; import { Patcher, Reducer, Selector, StateInitializer } from './types'; function useStateObject(state) { // This is for internal use only const localStateRef = React.useMemo(() => { if (state instanceof State) { // We have a global state, so we'll simply pass it out return null; } else { // We have a local state, so we create state obj and keep its ref for futer renders return createState(state); } }, []) let stateObject: State; if (localStateRef instanceof State) { stateObject = localStateRef; } else { stateObject = state as State; } return stateObject; } export default function useReducer( reducer: Reducer, state: State | StateInitializer | T, config?: {} ): [ state: T, dispatch: (action: A) => void, stateObject: State ]; export default function useReducer( reducer: Reducer, state: State | StateInitializer | T, config: { selector: Selector } ): [ state: ST, dispatch: (action: A) => void, stateObject: State ]; export default function useReducer( reducer: Reducer, state: State | StateInitializer | T, config: { selector: Selector, patcher: Patcher } ): [ state: ST, dispatch: (action: A) => void, stateObject: State ]; export default function useReducer( reducer: Reducer, state: State | unknown, config: { selector?: Selector, patcher?: Patcher } = {} ): [ state: unknown, dispatch: unknown, stateObject: State ] { const [, setState] = React.useState(null); const isMounted = React.useRef(false); const stateObject = useStateObject(state); const currentState = stateObject.getValue(config.selector); function reRender() { // re-render if the component is mounted if (isMounted.current) { setState({}); } } function observer(newState) { if (currentState === newState) { // Do nothing because the selected state is up-to-date } else { reRender(); } } const subscription = { observer: observer, selector: config.selector ? config.selector : (state) => state, // Select the whole state if selector is not specified refresh: reRender } React.useEffect(() => { const unsubscribe = stateObject.subscribe(subscription); isMounted.current = true; return () => { unsubscribe(); isMounted.current = false; } }, [currentState, State]) function dispatch(action: unknown) { const newState = reducer(currentState, action); stateObject.setValue(newState, config); } return [currentState, dispatch, stateObject] } ================================================ FILE: src/useState.ts ================================================ import React from 'react'; import useReducer from './useReducer'; import State, { createState } from './State'; import { Patcher, Selector, SetState, StateInitializer, UpdateState } from './types'; export default function useState( state: State | StateInitializer | T, config?: {} ): [ state: T, setState: SetState, updateState: UpdateState, stateObject: State ]; export default function useState( state: State | StateInitializer | T, config: { selector: Selector }, ): [ state: ST, setState: SetState, updateState: UpdateState, stateObject: State ]; export default function useState( state: State | StateInitializer | T, config: { selector: Selector, patcher: Patcher }, ): [ state: ST, setState: SetState, updateState: UpdateState, stateObject: State ]; export default function useState( state: State | unknown, config: { selector?: Selector, patcher?: Patcher } = {}, ): [ state: unknown, setState: SetState, updateState: UpdateState, stateObject: State ] { function reducer(currentState, newState) { return newState; } const [stateValue, setStateValue, stateObject] = useReducer(reducer, state, config); function updateStateValue(updater): void { stateObject.updateValue(updater, config); } return [stateValue, setStateValue, updateStateValue, stateObject]; } ================================================ FILE: tests/store.clear.test.ts ================================================ import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; import { createStore } from '../src/'; const store = createStore(); store.setState("count", 0); test('should clear the entire store and initialize `count` with 5', () => { const hook1 = renderHook(() => store.useState("count")) act(() => { hook1.result.current[1](count => 1) }) act(() => { store.clear(()=>{ store.setState("count", 5); }) }) expect(hook1.result.current[0]).toStrictEqual(5) }) const store2 = createStore(); store2.setState("count", 0); test('should clear the entire store and initialize `age` with 18', () => { const hook2 = renderHook(() => store2.useState("age", {default: 18})); act(() => { hook2.result.current[1](age => age + 2) }) act(() => { store2.clear(()=>{ }) }) expect(hook2.result.current[0]).toStrictEqual(18) }) ================================================ FILE: tests/store.getState.test.ts ================================================ import StatePool, { State } from '../src/'; const store = StatePool.createStore({count: 0}); test('should get user values', () => { const isStateInstance = store.getState("count") instanceof State; expect(isStateInstance).toStrictEqual(true) }) ================================================ FILE: tests/store.getStateValue.test.ts ================================================ import React from 'react'; import StatePool from '../src/'; const store = StatePool.createStore({ user: { name: "Yezy", age: 20 } }); test('should get user values', () => { const selector = (user): string => user.name; expect(store.getStateValue("user")).toStrictEqual({ name: "Yezy", age: 20 }) expect(store.getStateValue("user", selector)).toStrictEqual("Yezy") }) ================================================ FILE: tests/store.has.test.ts ================================================ import StatePool from '../src/'; const store = StatePool.createStore({ count: 0 }); test('should get user values', () => { const inStore = store.has("count"); const notInStore = store.has("user"); expect(inStore).toStrictEqual(true); expect(notInStore).toStrictEqual(false); }) ================================================ FILE: tests/store.items.test.ts ================================================ import StatePool from '../src/'; const store = StatePool.createStore({ width: 10, height: 15 }); test('should get user values', () => { const [item1, item2] = store.items() expect(item1).toStrictEqual(["width", 10, false]); expect(item2).toStrictEqual(["height", 15, false]); }) ================================================ FILE: tests/store.persist.test.ts ================================================ import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; import { createStore } from '../src/'; const store = createStore(); let storage: {count?: number, age?: number} = { "count": 10 // Pre-set count state to storage } store.persist({ saveState: (key, value) => { storage[key] = value; }, loadState: (key, noState) => { if (storage.hasOwnProperty(key)) { return storage[key] } return noState; }, removeState: (key) => { delete storage[key]; }, clear: () => { storage = {}; // Clear a store } }) store.setState("count", 0, {persist: true}); test('should update count', () => { const { result } = renderHook(() => store.useState("count")) act(() => { result.current[1](count => count + 1) // Will use the value from storage instead of 0 }) expect(result.current[0]).toStrictEqual(11) }) test('should create `age` state with the given default value', () => { const { result } = renderHook(() => store.useState("age", {default: 18, persist: true})); // age state will be saved to a store expect(storage.age).toStrictEqual(18); act(() => { result.current[1](age => age + 2); // Will use the default value 18 }) expect(result.current[0]).toStrictEqual(20); expect(storage.age).toStrictEqual(20); }) test('should remove count state from store and storage', () => { const { result } = renderHook(() => store.useState("count")) act(() => { store.remove("count", () => { store.setState("count", 5, {persist: true}); }); // This will remove count state from a store and reset it }) expect(result.current[0]).toStrictEqual(5); expect(storage.count).toStrictEqual(5); expect(storage.age).toStrictEqual(20); }) test('should clear both store and storage', () => { const { result } = renderHook(() => store.useState("count")) act(() => { store.clear(() => { store.setState("count", 10, {persist: true}); }); // This will clear a store and reset count state }) expect(result.current[0]).toStrictEqual(10); expect(storage.count).toStrictEqual(10); expect(storage.age).toStrictEqual(undefined); }) // Test PERSIST_ENTIRE_STORE = True const store2 = createStore(); let storage2: {count?: number, age?: number, weight?: number} = { "count": 10 // Pre-set count state to storage2 } store2.persist({ PERSIST_ENTIRE_STORE: true, saveState: (key, value) => { storage2[key] = value; }, loadState: (key, noState) => { if (storage2.hasOwnProperty(key)) { return storage2[key] } return noState; }, removeState: (key) => { delete storage2[key]; }, clear: () => { storage2 = {}; // Clear a store2 } }) store2.setState("count", 0); store2.setState("weight", 50, {persist: false}) // Won't save this to permanent storage test('should update count', () => { const { result } = renderHook(() => store2.useState("count")) act(() => { result.current[1](count => count + 1) // Will use the value from storage2 instead of 0 }) expect(result.current[0]).toStrictEqual(11) }) test('should not save weight to storage2', () => { const { result } = renderHook(() => store2.useState("weight")) act(() => { result.current[1](55) }) expect(result.current[0]).toStrictEqual(55) expect(storage2.weight).toStrictEqual(undefined); }) test('should create `age` state with the given default value', () => { const { result } = renderHook(() => store2.useState("age", {default: 18})); // age state will be saved to a store2 expect(storage2.age).toStrictEqual(18); act(() => { result.current[1](age => age + 2); // Will use the default value 18 }) expect(result.current[0]).toStrictEqual(20); expect(storage2.age).toStrictEqual(20); }) ================================================ FILE: tests/store.remove.test.ts ================================================ import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; import { createStore } from '../src/'; const store = createStore(); store.setState("count", 0); test('should remove `count` state and re-initialize it with 5', () => { const hook1 = renderHook(() => store.useState("count")) act(() => { hook1.result.current[2](count => 1) }) act(() => { store.remove("count", ()=>{ store.setState("count", 5); }) }) expect(hook1.result.current[0]).toStrictEqual(5) }) const store2 = createStore(); store2.setState("count", 0); test('should remove `age` state and re-initialize it with 18', () => { const hook2 = renderHook(() => store2.useState("age", {default: 18})); act(() => { hook2.result.current[2](age => age + 2) }) act(() => { store2.remove("age") }) expect(hook2.result.current[0]).toStrictEqual(18) }) ================================================ FILE: tests/store.useReducer.test.ts ================================================ import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; import { createStore } from '../src/'; const store = createStore(); store.setState("count", 0); test('should update count', () => { const reducer = (state, newState) => newState; const { result } = renderHook(() => store.useReducer(reducer, "count")) act(() => { result.current[1](1) }) expect(result.current[0]).toStrictEqual(1) }) test('should create `age` state with the given default value', () => { const reducer = (state: number, newState: number): number => newState; const { result } = renderHook(() => store.useReducer(reducer, "age", {default: 18})); act(() => { result.current[1](result.current[0] + 2) }) expect(result.current[0]).toStrictEqual(20) }) ================================================ FILE: tests/store.useState.test.ts ================================================ import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; import StatePool from '../src/'; const store = StatePool.createStore({ count: 0 }); // initialize store during instantiation test('should update count', () => { const { result } = renderHook(() => store.useState("count")) act(() => { result.current[1](count => 1) }) expect(result.current[0]).toStrictEqual(1) }) test('should create `age` state with the given default value', () => { const { result } = renderHook(() => store.useState("age", { default: 18 })); act(() => { result.current[1](age => age + 2) }) expect(result.current[0]).toStrictEqual(20) }) store.setState<{ name: string, age: number }>("user", { name: "Yezy", age: 20 }); test('should update name', () => { const selector = (user): string => user.name; const patcher = (user, name: string) => { user.name = name } const { result } = renderHook(() => store.useState("user", { selector, patcher })) act(() => { result.current[1]((name) => "Yezy Ilomo") }) expect(result.current[0]).toStrictEqual("Yezy Ilomo") }) test('should create `birthYear` state with the given state initializer on default', () => { const { result } = renderHook(() => store.useState("birthYear", { default: () => 1995 })); act(() => { result.current[1](birthYear => birthYear + 2) }) expect(result.current[0]).toStrictEqual(1997) }) ================================================ FILE: tests/subscription.test.ts ================================================ import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; import { createState, useState, createStore } from '../src/'; const count = createState(0); let testVal1 = 0; test('should update testVal1 through a subscriber', () => { const { result } = renderHook(() => useState(count)) act(() => { count.subscribe((value) => { testVal1 = ++testVal1; }) result.current[1](count => 1) }) expect(testVal1).toStrictEqual(1); }) const user = createState({ name: "Yezy", weight: 65 }); let testVal2 = 0; test('should increment testVal2 twice through subscribers', () => { const { result } = renderHook(() => useState(user)) act(() => { user.select(user => user.weight).subscribe((value) => { testVal2 = ++testVal2; }) user.select(user => user.name).subscribe((value) => { testVal2 = ++testVal2; }) result.current[2](user => { user.weight = 66; user.name = "Ilomo"; }); }) expect(testVal2).toStrictEqual(2); }) const store = createStore(); store.setState("count", 0); let testVal3 = 0; test('should update testVal3 through subscribers', () => { const { result } = renderHook(() => store.useState("count")) act(() => { store.subscribe((key, value) => { testVal3 = ++testVal3; }) result.current[1](count => 1) }) expect(testVal3).toStrictEqual(1); }) ================================================ FILE: tests/useReducer.test.ts ================================================ import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; import { useReducer, createState } from '../src/'; const count = createState(0); test('should update count', () => { const reducer = (state, action) => action; const { result } = renderHook(() => useReducer(reducer, count)); act(() => { result.current[1](1) }) expect(result.current[0]).toStrictEqual(1) }) const user = createState({ name: "Yezy", age: 20 }); test('should update name', () => { const reducer = (state: string, action: string): string => action; const selector = (user): string => user.name; const patcher = (user, name: string) => { user.name = name } const { result } = renderHook(() => useReducer(reducer, user, { selector, patcher })) act(() => { result.current[1]("Yezy Ilomo") }) expect(result.current[0]).toStrictEqual("Yezy Ilomo") }) test('Test local state: should update count', () => { const reducer = (state, action) => action; const { result } = renderHook(() => useReducer(reducer, 0)); act(() => { result.current[1](1) }) expect(result.current[0]).toStrictEqual(1) }) test('Test local state: should update name', () => { const reducer = (state: string, action: string): string => action; const selector = (user): string => user.name; const patcher = (user, name: string) => { user.name = name } const { result } = renderHook(() => useReducer(reducer, { name: "Yezy", age: 20 }, { selector, patcher })) act(() => { result.current[1]("Ilomo") }) expect(result.current[0]).toStrictEqual("Ilomo") }) test('Test local state with state initializer: should update count', () => { const reducer = (state, action) => action; const { result } = renderHook(() => useReducer(reducer, () => 0)); act(() => { result.current[1](1) }) expect(result.current[0]).toStrictEqual(1) }) test('Should return state object as the last item in array', () => { const reducer = (state, action) => action; const { result } = renderHook(() => useReducer(reducer, 0)); act(() => { result.current[1](1) }) expect(result.current[2].getValue()).toStrictEqual(1) }) ================================================ FILE: tests/useState.test.ts ================================================ import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; import { useState, createState } from '../src/'; const count = createState(0); test('should update count', () => { const { result } = renderHook(() => useState(count)) act(() => { result.current[1](count => ++count) }) expect(result.current[0]).toStrictEqual(1) }) test('should set count', () => { const { result } = renderHook(() => useState(count)) act(() => { result.current[1](5) }) expect(result.current[0]).toStrictEqual(5) }) const user = createState<{ name: string, age: number }>({ name: "Yezy", age: 20 }); test('should update name', () => { const selector = (user) => user.name; const patcher = (user, name) => { user.name = name } const { result } = renderHook(() => useState(user, { selector, patcher })) act(() => { result.current[1]((name) => "Yezy Ilomo") }) expect(result.current[0]).toStrictEqual("Yezy Ilomo") }) test('should set name', () => { // If you specify the type of what you're selecting & patcher it'll automatically know what to return const selector = (user) => user.name; const patcher = (user, name) => { user.name = name } const { result } = renderHook(() => useState(user, { selector, patcher })) act(() => { result.current[1]("Ilomo Yezy") }) expect(result.current[0]).toStrictEqual("Ilomo Yezy") }) test('should update name without patcher', () => { const selector = (user) => user.name; const { result } = renderHook(() => user.useState({ selector })) act(() => { result.current[2]((usr) => { usr.name = "Ilomo" }) }) expect(result.current[0]).toStrictEqual("Ilomo") }) test('Test local state: should update count', () => { const { result } = renderHook(() => useState(0)) act(() => { result.current[1](count => ++count) }) expect(result.current[0]).toStrictEqual(1) }) test('Test local state: should set name', () => { // If you specify the type of what you're selecting & patcher it'll automatically know what to return const selector = (user) => user.name; const patcher = (user, name) => { user.name = name } const { result } = renderHook(() => useState({ name: "Yezy", age: 27 }, { selector, patcher })) act(() => { result.current[1]("Ilomo Yezy") }) expect(result.current[0]).toStrictEqual("Ilomo Yezy") }) test('Test local state with state initializer: should update count', () => { const { result } = renderHook(() => useState(() => 0)) act(() => { result.current[1](count => ++count) }) expect(result.current[0]).toStrictEqual(1) }) test('Should return state object as the last item in array', () => { const { result } = renderHook(() => useState(0)) act(() => { result.current[1](count => ++count) }) expect(result.current[3].getValue()).toStrictEqual(1) }) ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "ES5", "module": "commonjs", "allowJs": true, "outDir": "dist", "strict": false, "jsx": "preserve", "declaration": true, "esModuleInterop": true, "moduleResolution": "node", "baseUrl": ".", "paths": { "state-pool": [ "./src/index.ts" ] } }, "include": [ "src/**/*", "tests/**/*" ], "exclude": [ "node_modules", "dist" ] } ================================================ FILE: website/.gitignore ================================================ # Dependencies /node_modules # Production /build # Generated files .docusaurus .cache-loader # Misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: website/README.md ================================================ # Website This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. ### Installation ``` $ yarn ``` ### Local Development ``` $ yarn start ``` This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. ### Build ``` $ yarn build ``` This command generates static content into the `build` directory and can be served using any static contents hosting service. ### Deployment Using SSH: ``` $ USE_SSH=true yarn deploy ``` Not using SSH: ``` $ GIT_USER= yarn deploy ``` If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. ================================================ FILE: website/babel.config.js ================================================ module.exports = { presets: [require.resolve('@docusaurus/core/lib/babel/preset')], }; ================================================ FILE: website/blog/2019-05-28-first-blog-post.md ================================================ --- slug: first-blog-post title: First Blog Post authors: name: Gao Wei title: Docusaurus Core Team url: https://github.com/wgao19 image_url: https://github.com/wgao19.png tags: [hola, docusaurus] --- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet ================================================ FILE: website/blog/2019-05-29-long-blog-post.md ================================================ --- slug: long-blog-post title: Long Blog Post authors: endi tags: [hello, docusaurus] --- This is the summary of a very long blog post, Use a `` comment to limit blog post size in the list view. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet ================================================ FILE: website/blog/2021-08-01-mdx-blog-post.mdx ================================================ --- slug: mdx-blog-post title: MDX Blog Post authors: [slorber] tags: [docusaurus] --- Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/). :::tip Use the power of React to create interactive blog posts. ```js ``` ::: ================================================ FILE: website/blog/2021-08-26-welcome/index.md ================================================ --- slug: welcome title: Welcome authors: [slorber, yangshun] tags: [facebook, hello, docusaurus] --- [Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog). Simply add Markdown files (or folders) to the `blog` directory. Regular blog authors can be added to `authors.yml`. The blog post date can be extracted from filenames, such as: - `2019-05-30-welcome.md` - `2019-05-30-welcome/index.md` A blog post folder can be convenient to co-locate blog post images: ![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg) The blog supports tags as well! **And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config. ================================================ FILE: website/blog/authors.yml ================================================ endi: name: Endilie Yacop Sucipto title: Maintainer of Docusaurus url: https://github.com/endiliey image_url: https://github.com/endiliey.png yangshun: name: Yangshun Tay title: Front End Engineer @ Facebook url: https://github.com/yangshun image_url: https://github.com/yangshun.png slorber: name: Sébastien Lorber title: Docusaurus maintainer url: https://sebastienlorber.com image_url: https://github.com/slorber.png ================================================ FILE: website/docs/api_reference/_category_.json ================================================ { "label": "API Reference", "position": 4 } ================================================ FILE: website/docs/api_reference/high_level_api/_category_.json ================================================ { "label": "High Level API", "position": 2 } ================================================ FILE: website/docs/api_reference/high_level_api/createStore.md ================================================ --- sidebar_position: 1 --- # createStore Store is a container for state, it comes with several methods which are used to manage states in it including `store.setState`, `store.getState`, `store.remove`, `store.clear`, `store.useState`, `store.useReducer` and `store.subscribe`. Store is created by using `createStore` API as ```js import { createStore } from 'state-pool'; const store = createStore(); // Or with initialization as const store = createStore({"state1": value1, "state2": value2, ...}); ``` ================================================ FILE: website/docs/api_reference/high_level_api/store.clear.md ================================================ --- sidebar_position: 8 --- # store.clear This is used to clear an entire store if you don't need all states in it anymore or you want to reload/reset all states. It accepts a function to run after clearing the store. :::important The function runs before components subscribed to all states in a store rerenders. ::: ```js // Signature store.clear(fn: Function) ``` Below is an example showing how to use it ```jsx import React from 'react'; import { createStore } from 'state-pool'; const store = createStore(); const user = { name: "Yezy", age: 25, email: "yezy@me.com" } const profile = { url: "https://yezyilomo.me", rating: 5 } store.setState("user", user); store.setState("profile", profile); function UserInfo(props){ const [user, setUser, updateUser] = store.useState("user"); const updateName = (e) => { updateUser(user => { user.name = e.target.value; }); } const reinitializeStore = () => { const user = {name: "Yezy", age: 25, email: "yezy@me.com"} const profile = {url: "https://yezyilomo.me", rating: 5} store.setState("user", user); store.setState("profile", profile); } const resetStore = (e) => { store.clear(initializeStore); } return (
Name: {user.name}
); } ReactDOM.render(UserInfo, document.querySelector("#root")); ``` From the code above, when you click `Reset Store` button `store.clear` will remove all states from the store and create them again by executing `initializeStore`. This might come in handy when you need to clear all data when user logs out of your application. **NOTE:** both `store.remove` and `store.clear` when executed causes all components subscribed to states which are removed to rerender. ================================================ FILE: website/docs/api_reference/high_level_api/store.getState.md ================================================ --- sidebar_position: 5 --- # store.getState `store.getState` is used to get a state object from a store, it accepts one required parameter which is a key(string) and another optional parameters which is the configuration object(available configurations are `default` and `persist` works just like in `store.setState`). When called, `store.getState` returns a state object. ```js // Signature store.getState(key: String, config?: {default: Any, persist: Boolean}) ``` Here is how to use it ```js const State = store.getState(key); ``` ================================================ FILE: website/docs/api_reference/high_level_api/store.getStateValue.md ================================================ --- sidebar_position: 12 --- # store.getStateValue This method is used to get state value directly from a store given its key, it's equivalent of calling `store.getState(key).getValue(selector?)`. You can pass `selector` too if you want to select part of the state. ```js const userName = store.getStateValue("user", (user) => user.name); ``` ================================================ FILE: website/docs/api_reference/high_level_api/store.has.md ================================================ --- sidebar_position: 10 --- # store.has `store.has` is a method for checking if a state is available in a store. If you try to get a state from a store with `store.getState(key)` and it’s not available, state-pool will throw an error, so we need a good way to handle this, i.e check if a state is available first, then access it, This is where `store.has` swoops in. Here is an example of `store.has` in action ```js // We want to access user state in a store but first we have to check if it's available if(store.has("user")) { const user = store.getState("user"); } else { // There is not user state in a store } ``` ================================================ FILE: website/docs/api_reference/high_level_api/store.items.md ================================================ --- sidebar_position: 11 --- # store.items `store.items` is used to iterate over an entire store, it returns an array containing `key`, `value` and `persist` for each state in a store. Here is how to use it. ```js store.items().map(([key, value, persist]) => { // Do whatever you want with these values }) ``` ================================================ FILE: website/docs/api_reference/high_level_api/store.persist.md ================================================ --- sidebar_position: 9 --- # store.persist Sometimes you might want to save your states in a permanent storage probably because you might not want to lose them when your application is closed(i.e you want to retain them when your application starts). **State pool** makes it very easy to save your states in your preferred permanent storage, all you need to do is: 1. Tell **state-pool** how to save your state by using `store.persist` 2. Use `persist` configuration to tell state-pool to save your state in your preferred storage when creating your state. When telling **state-pool** how to save state to a permanent storage we need to implement four functions which are 1. `saveState`: This is for saving your state to your preferred permanent storage, it should accept a `key` as the first parameter, `value` as the second parameter and `isInitialSet` as the third parameter, the third parameter is boolean which tells if the state is being saved for the first time(initial set) or it's just an update. This function is called automatically when `store.setState` is executed and when the state changes 2. `loadState`: This is used for loading state from your preferred permanent storage, it should accept a `key` as the first parameter and `noState` as the second parameter which is a constant(empty) to return if the state is not available from your permanent storage. This function is called when `store.setState` needs an initial value from your storage to populate a state 3. `removeState`: This is used for removing state from a permanent storage, it should accept a `key` as the only parameter. This function is called when `store.remove` is executed 4. `clear`: This is used for clearing an entire permanent storage, it doesn’t accept any parameter. This function is called when `store.clear` is executed. Now the way to implement these is by calling `store.persist` and pass them as shown below ```js store.persist({ saveState: function(key, value, isInitialSet){/*your code to save state */}, loadState: function(key, noState){/*your code to load state */}, removeState: function(key){/*your code to remove state */}, clear: function(){/*your code to clear storage */} }) ``` After implementing these four functions you're good to go, you won’t need to worry about calling them, **state-pool** will be doing that for you automatically so that you can focus on using your states. As discussed earlier both `store.setState`, `store.useState` and `store.useReducer` accepts an optional configuration parameter, `persist` being one of configurations, this is the one which is used to tell **state-pool** whether to save your state to a permanent storage or not. i.e ```js store.setState( key: String, initialState: Any, config?: {persist: Boolean} ) ``` ```js store.useState( key: String, config?: {default: Any, persist: Boolean, ...otherConfigs} ) ``` ```js store.useReducer( reducer: Function, key: String, config?: {default: Any, persist: Boolean, ...otherConfigs} ) ``` By default the value of `persist` in all cases is false(which means it doesn't save states to a permanent storage), so if you want to activate it, you have to set it to be true. What's even better about **state-pool** is that you get the freedom to choose what to save in your permanent storage and what's not to, so you don't need to save the whole store in your permanent storage, but if you want to save the whole store you can use `PERSIST_ENTIRE_STORE` configuration. Below is an example showing how you could implement state persistance in local storage. ```js import { createStore } from 'state-pool'; const store = createStore(); function debounce(func, timeout) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { func.apply(this, args); }, timeout); }; } store.persist({ PERSIST_ENTIRE_STORE: true, // Use this only if you want to persist the entire store saveState: function (key, value, isInitialSet) { const doStateSaving = () => { try { const serializedState = JSON.stringify(value); window.localStorage.setItem(key, serializedState); } catch { // Ignore write errors } } if (isInitialSet) { // Here we don't debounce saving state since it's the initial set // so it's called only once and we need our storage to be updated // right away doStateSaving(); } else { // Here we debounce saving state because it's the update and this function // is called every time the store state changes. However, it should not // be called too often because it triggers the expensive `JSON.stringify` operation. const DEBOUNCE_TIME = 1000 // In milliseconds // Debounce doStateSaving before calling it const processStateSaving = debounce(doStateSaving, DEBOUNCE_TIME); processStateSaving() // save State } }, loadState: function (key, noState) { try { const serializedState = window.localStorage.getItem(key); if (serializedState === null) { // No state saved return noState; } return JSON.parse(serializedState); } catch (err) { // Failed to load state return undefined } }, removeState: function (key) { window.localStorage.removeItem(key); }, clear: function () { window.localStorage.clear(); } }) ``` :::important When you set `PERSIST_ENTIRE_STORE = true`, **state-pool** will be persisting all your states to the permanent storage by default unless you explicitly specify `persist = false` when initializing your state. ::: ================================================ FILE: website/docs/api_reference/high_level_api/store.remove.md ================================================ --- sidebar_position: 7 --- # store.remove This is used to remove a state from a store if you don't need it anymore or you want to reload/reset it. It accepts a key for a state or a list of keys to remove and a function to run after removal. :::important The function runs before components subscribed to removed state(s) re-renders. ::: ```js // Signature store.remove(key: String, fn: Function) ``` Below is an example showing how to use it ```jsx import React from 'react'; import { createStore } from 'state-pool'; const store = createStore(); store.setState("count", 0); function ClicksCounter(props){ const [count, setCount, updateCount] = store.useState("count"); const incrementCount = (e) => { setCount(count+1); } const reinitializeCountState = () => { store.setState("count", 0); } const resetCounter = (e) => { store.remove("count", reinitializeCountState) } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ``` From an example above, when you click `Reset` button `store.remove` will remove `count` state and create it again by executing `initializeStore`. **NOTE:** If we had more than one state to delete we could do ```js store.remove([key1, key2, key3, ...], initializeStore); ``` ================================================ FILE: website/docs/api_reference/high_level_api/store.setState.md ================================================ --- sidebar_position: 2 --- # store.setState This is used to create a state and map it to a key so that you won't need to use it directly, instead you use a key to get it. `store.setState` takes two required parameters, a key(string) to map to a state object and the initial value or state initializer, In addition to those two parameters it takes a third optional parameter which is the configuration object. `persist` is the only available config which is the flag to determine whether to save/persist state in a permanent storage or not. ```js // Signature store.setState(key: String, initialState: Any, config?: {persist: Boolean}); // Or with lazy state initializer store.setState(key: String, stateInitializer: () => Any, config?: {persist: Boolean}) ``` Here is how to use it ```js const userState = { name: "Yezy", age: 25, email: "yezy@me.com" } store.setState("user", userState); ``` :::important `store.setState` should be used outside of a react component. ::: ================================================ FILE: website/docs/api_reference/high_level_api/store.subscribe.md ================================================ --- sidebar_position: 6 --- # store.subscribe If you want to listen to changes in a store you can subscribe to it by using `store.subscribe`. it accepts an observer function. For example ```js // Subscribe to store changes const unsubscribe = store.subscribe(function(key: String, value: Any){ // key is the key for a state that has changed // value is the new value of a state }) // You can unsubscribe by calling the result unsubscribe(); ``` If you want to subscribe to a single state you can use ```js // Subscribe to store changes const unsubscribe = store.getState(key).subscribe(function(value){ // value is the new value of a state }) // You can unsubscribe by calling the result unsubscribe(); ``` You can even subscribe to a deeply nested state by using a selector as ```js store.getState(key).subscribe({ observer: function(value){ // value is the new value of a state }, selector: function(value){ return selected_state }) }) ``` With this observer function will only be called when the selected state changes. Another way to subscribe to nested state or derived state is to call `select` on a state then subscribe to it as ```js store.getState(key).select(state => selected_state).subscribe(value =>{ // Do your thing here } ) ``` ================================================ FILE: website/docs/api_reference/high_level_api/store.useReducer.md ================================================ --- sidebar_position: 4 --- # store.useReducer This is an alternative to `store.useState`, it works just like `React.useReducer` hook(If you’re familiar with `React.useReducer`, you already know how this works). It accepts a reducer and a key for the state as parameters, it returns the current state paired with a dispatch method plus the state object, but in most cases you won't be using `stateObject` so you'll be okay with just `[state, dispatch]`.. In addition to the two parameters it also accept another optinal perameter which is the configuration object, available configurations are `default`, `persist`, `selector` & `patcher` they work exactly the same just like in `store.useState`. ```js // Signature store.useReducer( reducer: Function, key: String, config?: {default: Any, persist: Boolean, selector: Function, patcher: Function} ) ``` Below is a simple example showing how to use `store.useReducer` hook ```js const initialState = { name: "Yezy", age: 25, email: "yezy@me.com" } store.setState("user", initialState); function myReducer(state, action){ // This could be any reducer // Do whatever you want to do here return newState } function Component(props){ const [name, dispatch] = store.useReducer(myReducer, "user"); // Other stuff ... } ``` Below is an example with `selector` and `patcher` parameters ```js const initialState = { name: "Yezy", age: 25, email: "yezy@me.com" } store.setState("user", initialState); function myReducer(state, action){ // This could be any reducer // Do whatever you want to do here return newState } function UserInfo(props){ const selector = (user) => user.name; const patcher = (user, name) => {user.name = name}; const [name, dispatch] = store.useReducer(myReducer, "user", {selector, patcher}); // Other stuff } ``` ================================================ FILE: website/docs/api_reference/high_level_api/store.useState.md ================================================ --- sidebar_position: 3 --- # store.useState `store.useState` is a hook that's used to get a state from a store, it's a way for a component to subscribe to a state from a store. `store.useState` works just like `React.useState` hook but it accepts a key for the state and returns an array of `[state, setState, updateState, stateObject]` rather than `[state, setState]`, In most cases you won't be using `stateObject` so you'll be okay with `[state, setState, updateState]`. In addition to the key parameter it also accept another optional parameter which is the config object, available configurations are `default`, `persist`, `selector` & `patcher`, these will be discussed in detail later. ```js // Signature store.useState( key: String, config?: {default: Any, persist: Boolean, selector: Function, patcher: Function} ) // Or with lazy state initializer as store.useState( key: String, config?: {default: () => Any, persist: Boolean, selector: Function, patcher: Function} ) ``` Below is an example showing how to use `store.useState` hook ```js const initialState = { name: "Yezy", age: 25, email: "yezy@me.com" } store.setState("user", initialState); function Component(props){ const [user, setUser, updateUser] = store.useState("user"); // Other stuff } ``` Here `updateUser` is a higher order function which accepts another function for updating user as an argument(this another functions takes user(old state) as the argument). So to update any value on user you could do ```js updateUser(function(user){ user.name = "Yezy Ilomo"; user.age = 26; }) ``` Or you could just use `setUser` instead of `updateUser` i.e ```js setUser({name: "Yezy Ilomo", age: 26, email: "yezy@me.com"}); ``` Or ```js setUser(function(user){ return { name: "Yezy Ilomo", age: 26, email: user.email } }) ``` As stated earlier `store.useState` takes a second optional parameter which is a configuration object, available configurations are: - `default` - This is used to specify the default value if you want `store.useState` to create a state if it doesn't find the one for the key specified in the first argument. For example ```js const [user, setUser, updateUser] = store.useState("user", {default: null}); // You can also use lazy initialization on `default` option as const [user, setUser, updateUser] = store.useState( "user", {default: () => { // your expensive computation here return null // Return your initial state }}); ``` This piece of code means, get the state for the key "user" if it's not available in a store, create one and assign it the value `null`. - Also in addition to `default` configuration there is `persist` configuration which is the flag to determine whether to save/persist state in your preferred storage or not. Here `persist` configuration is only used if `store.useState` is going to create state dynamically(by using `default` config).
Other allowed configurations are `selector` & `patcher`. These are used for specifying a way to select deeply nested state and update it. - `selector` should be a function which takes one parameter which is the state and returns a selected value. The purpose of this is to subscribe to a deeply nested state or derived state. - `patcher` should be a function which takes two parameters, the first is the state and the second is the selected value. The purpose of this is to merge back the selected value to the state once it's updated. Example. ```jsx const initialState = { name: "Yezy", age: 25, email: "yezy@me.com" } store.setState("user", initialState); function UserName(props){ const selector = (user) => user.name; // Subscribe to user.name only const patcher = (user, name) => {user.name = name}; // Update user.name const [name, setName] = store.useState("user", {selector: selector, patcher: patcher}); const handleNameChange = (e) => { setName(e.target.value); } return (
Name: {name}
); } ``` ================================================ FILE: website/docs/api_reference/intro.md ================================================ --- sidebar_position: 1 --- # Intro State pool API is divided into two parts. 1. High Level API(Store based API) 2. Low Level API(Non-Store based API) In a high level API, states are stored in a container that we call "store". With high level API, State pool allows you to create as many stores as you want and use them anywhere in your application, it doesn't enforce having a single central store. Here is a simple example using high level API ```jsx import React from 'react'; import { createStore } from 'state-pool'; // Create a store const store = createStore(); // Create count state and initialize it with 0 const count = store.setState("count", 0); function ClicksCounter(props){ // Use count state const [count, setCount] = store.useState("count"); const incrementCount = (e) => { setCount(count+1); } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ```
On the other hand, low level API doesn't use the concept of a store, You simply store your state wherever you want. In low level API **state-pool** doesn't care where you store your state as long as you can access them, for-instance I could choose to store my state in a global variable and it would still work just fine. So basically the low level API gives you a way to create and use state, it doesn't matter where you store them as long as you can access them. Here is the same example as the previous one re-written using low level API ```jsx import React from 'react'; import { createState } from 'state-pool'; // Create count state and initialize it with 0 const count = createState(0); function ClicksCounter(props){ // Use count state const [count, setCount] = count.useState(); const incrementCount = (e) => { setCount(count+1); } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ``` Or if you want to import `useState` from **state-pool** ```jsx import React from 'react'; import { createState, useState } from 'state-pool'; // Create count state and initialize it with 0 const count = createState(0); function ClicksCounter(props){ // Use count state const [count, setCount] = useState(count); const incrementCount = (e) => { setCount(count+1); } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ``` Now let's explore these two APIs ================================================ FILE: website/docs/api_reference/low_level_api/_category_.json ================================================ { "label": "Low Level API", "position": 3 } ================================================ FILE: website/docs/api_reference/low_level_api/createState.md ================================================ --- sidebar_position: 1 --- # createState This is the basic unit of **state-pool**, it's a function which is used to create a state object, it accepts one parameter which is the initial value or initializer function. ```js // Signature createState(initialValue: Any) // Or createState(initializer: () => Any) ``` Here is how to use it ```js import { createState } from 'state-pool'; const userName = createState("Yezy"); // Or if you have expensive computations during initialization you could use lazy initialization function lazyInitializer(){ // Perform expensive computation here return "Yezy"; // Return your initial state } const userName = createState(lazyInitializer); ``` Here are some of the mostly used methods available in a state object. - `useState`: This is used to subscribe a component to a state, it's a hook which should be used inside a react component. ```js // Signature state.useState(config?: {selector: Function, patcher: Function}); ``` - `useReducer`: This too is used to subscribe a component to a state, it's also a hook which should be used inside a react component. ```js // Signature state.useReducer(reducer: Function, config?: {selector: Function, patcher: Function}); ``` - `getValue`: This is used to get the value of a state ```js // Signature state.getValue(selector?: Function); ``` - `setValue`: This is used to set the value of a state ```js // Signature state.setValue(value | stateUpdater: Any | Function, config?: {selector, patcher}); ``` - `updateValue`: This is used to update the value of a state ```js // Signature state.updateValue(updater: Function, config?: {selector, patcher}); ``` - `subscribe`: This is used to listen to all changes from a state ```js // Signature state.subscribe(observer: Function | Subscription: {observer, selector}); ``` - `select`: This is used to derive another state or select a deeply nested state ```js // Signature state.select(selector: Function); ``` This returns `DerivedState` that you can subscribe to by calling `subscribe` on it as ```js // Signature state.select(selector: Function).subscribe(observer: Function); ``` Below is an example showing all of them in action ```js import { createState } from 'state-pool'; const count = createState(0); count.useState() // This subscribe its component to count state count.getValue() // This will give 0 count.setValue(1) // This set the value of count to 1 // This will print whenever count change const unsubscribe = count.subscribe(val => console.log(val)) unsubscribe() // This will unsubscribe the observer above // An example for nested state const user = createState({name: "Yezy", weight: 65}); user.updateValue(user => {user.weight += 1}) // This will increment the weight // Select user name and subscribe to it, // this will be printing whenever user name changes user.select(user => user.name).subscribe(name => console.log(name)); ``` :::important `createState` should be used outside of a react component. ::: :::tip `createState` is used to implement `store.setState` API. ::: ================================================ FILE: website/docs/api_reference/low_level_api/useReducer.md ================================================ --- sidebar_position: 3 --- # useReducer This is an alternative to `useState`, it works just like `React.useReducer` hook(If you’re familiar with `React.useReducer`, you already know how this works). It accepts a reducer and a state object or initial state or state initializer as parameters, it returns the current state paired with a dispatch method plus the state object, but in most cases you won't be using `stateObject` so you'll be okay with `[state, dispatch]`. In addition to the two parameters it also accept another optinal perameter which is the configuration object, available configurations are `selector` and `patcher` they work exactly the same just like in `useState`. ```js // Signature useReducer( reducer: Function, state: State, config?: {selector: Function, patcher: Function} ) // Or in local state as useReducer( reducer: Function, initialState: Any, config?: {selector: Function, patcher: Function} ) // Or with lazy state initializer useReducer( reducer: Function, stateInitializer: () => Any, config?: {selector: Function, patcher: Function} ) ``` Below is a simple example showing how to use `useReducer` ```js const initialState = { name: "Yezy", age: 25, email: "yezy@me.com" } const user = createState(initialState); function myReducer(state, action){ // This could be any reducer // Do whatever you want to do here return newState } function Component(props){ const [name, dispatch] = useReducer(myReducer, user); // Other stuff ... } ``` Below is the same example with `selector` and `patcher` parameters ```js const initialState = { name: "Yezy", age: 25, email: "yezy@me.com" } const user = createState(initialState); function myReducer(state, action){ // This could be any reducer // Do whatever you want to do here return newState } function UserInfo(props){ const selector = (user) => user.name; const patcher = (user, name) => {user.name = name}; const [name, dispatch] = useReducer(myReducer, user, {selector, patcher}); // Other stuffs } ``` # Using useReducer to manage local state Just like in `useState`, `useReducer` can be used to manage local state too. Here is an example for managing local state with `useReducer` ```jsx import React from 'react'; import { useReducer } from 'state-pool'; const myReducer = (state, action) => { // Your computaton here return action; } function ClicksCounter(props){ // Here `useReducer` hook will create "count" state and initialize it with 0 // Note: the `useReducer` hook used here is impored from state-pool and not react const [count, dispatch] = useReducer(myReducer, 0); const incrementCount = (e) => { dispatch(count+1) } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ```
If you don't want **state-pool's** `useState` to collide with **React's** `useState` you can import `StatePool` and use the hook from there, :::tip `useState` hook is derived from `useReducer` hook, also this hook is used to implement `store.useReducer`. ::: ================================================ FILE: website/docs/api_reference/low_level_api/useState.md ================================================ --- sidebar_position: 2 --- # useState `useState` is a hook that used within a react component to subscribe to a state. `useState` works just like `React.useState` hook but it accepts a state object or initial state or state initializer and returns an array of `[state, setState, updateState, stateObject]` rather than `[state, setState]`, In most cases you won't be using `stateObject` so you'll be okay with `[state, setState, updateState]`. In addition to a state object parameter it also accept another optional parameter which is the config object, available configurations are `selector` & `patcher`, these parameters works exactly the same as in `store.useState`. We could say `useState` is a low level implementation of `store.useState`. ```js // Signature useState(state: State, config?: {selector: Function, patcher: Function}) // Or in local state as useState(initialState: Any, config?: {selector: Function, patcher: Function}) // Or with lazy state initializer useState(stateInitializer: () => Any, config?: {selector: Function, patcher: Function}) ``` Below is a simple example showing how to use `useState` hook ```jsx import React from 'react'; import { createState, useState } from 'state-pool'; const initialState = { name: "Yezy", age: 25, email: "yezy@me.com" } const user = createState(initialState); function Component(props){ const [user, setUser, updateUser] = useState(user); // Other stuff ... } ``` Below is the same example with `selector` and `patcher` configurations ```jsx const initialState = { name: "Yezy", age: 25, email: "yezy@me.com" } const user = createState(initialState); function UserName(props){ const selector = (user) => user.name; // Subscribe to user.name only const patcher = (user, name) => {user.name = name}; // Update user.name const [name, setName] = useState(user, {selector: selector, patcher: patcher}); const handleNameChange = (e) => { setName(e.target.value); } return (
Name: {name}
); } ``` # Using useState to manage local state With **state-pool**, state are just like variables, if declared on a global scope, it’s a global state and if declared on local scope it’s a local state, so the difference between global state and local state in **state-pool** lies where you declare them just like variables. Here is an example for managing local state with `useState` ```jsx import React from 'react'; import { useState } from 'state-pool'; function ClicksCounter(props){ // Here `useState` hook will create "count" state and initialize it with 0 // Note: the `useState` hook used here is impored from state-pool and not react const [count, setCount] = useState(0); const incrementCount = (e) => { setCount(count+1) } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ```
If you don't want **state-pool's** `useState` to collide with **React's** `useState` you can import `StatePool` and use the hook from there, Here is an example ```jsx // Example 2. import React from 'react'; import StatePool from 'state-pool'; function ClicksCounter(props){ // Here `useState` hook will create "count" state and initialize it with 0 const [count, setCount] = StatePool.useState(0); const incrementCount = (e) => { setCount(count+1) } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ``` Here is an example with nested data ```jsx import { useState } from 'state-pool'; function UserName(props){ const [user, setUser, updateUser] = useState({name: "Yezy", age: 25, email: "yezy@me.com"}); const handleNameChange = (e) => { updateUser((user) => { user.name = e.target.value }) } const handleAgeChange = (e) => { updateUser((user) => { user.age = e.target.value }) } return (
Name: {user.name}
Age: {user.age}
); } ``` :::tip `useState` is used to implement `store.useState` hook. ::: ================================================ FILE: website/docs/api_reference/typing-state.md ================================================ --- sidebar_position: 4 --- # Typing State All state related functions support implicity and explicity typing. Examples ```ts store.setState('count', 0); store.useState('count'); store.useReducer(reducer, 'count'); // For none key based const count = createState(0); useState(count); useReducer(reducer, count); // Typing with selector store.setState<{name: string, age: number}>('user', {name: 'Yezy', age: 25}); store.useState('user', {selector: user => user.name}); store.useState('age', {selector: user => user.age}); store.useReducer(reducer, 'user', {selector: user => user.name}); store.useReducer(reducer, 'user', {selector: user => user.age}); // For none key based const user = createState<{name: string, age: number}>({name: 'Yezy', age: 25}); useState(user, {selector: user => user.name}); useState(user, {selector: user => user.age}); useReducer(reducer, user, {selector: user => user.name}); useReducer(reducer, user, {selector: user => user.age}); ```

# Blue print for typing hooks **Note:** `T` is for base/original state type, `ST` for selected state type and `A` for reducer action. ```ts // useState.js (For useState) const [state: T, setState: SetState, updateState: UpdateState, stateObj: State] = useState(state: State | T) const [state: ST, setState: SetState, updateState: UpdateState, stateObj: State] = useState(state: State | T, { selector: Selector }) const [state: ST, setState: SetState, updateState: UpdateState, stateObj: State] = useState(state: State | T, { selector: Selector, patcher: Patcher }) // State.js (For state.useState) const [state: T, setState: SetState, updateState: UpdateState, stateObj: State] = state.useState() const [state: ST, setState: SetState, updateState: UpdateState, stateObj: State] = state.useState({ selector: Selector }) const [state: ST, setState: SetState, updateState: UpdateState, stateObj: State] = state.useState({ selector: Selector, patcher: Patcher }) // Store.js (For store.useState) const [state: T, setState: SetState, updateState: UpdateState, stateObj: State] = store.useState(key, { default: T }); const [state: ST, setState: SetState, updateState: UpdateState, stateObj: State] = store.useState(key, { selector: Selector, default: T }) const [state: ST, setState: SetState, updateState: UpdateState, stateObj: State] = store.useState(key, { selector: Selector, patcher: Patcher, default: T }) // useReducer.js (For useReducer) type Reducer = (state: T, action: A) => T const [state: T, dispatch: (action: A) => void, stateObj: State] = useReducer(type Reducer = Reducer, state: State | T) type Reducer = (state: T, action: A) => T const [state: ST, dispatch: (action: A) => void, stateObj: State] = useReducer(type Reducer = Reducer, state: State | T, { selector: Selector }) type Reducer = (state: ST, action: A) => ST const [state: ST, dispatch: (action: A) => void, stateObj: State] = useReducer(type Reducer = Reducer, state: State | T, { selector: Selector, patcher: Patcher }) // State.js (For state.useReducer) type Reducer = (state: T, action: A) => T const [state: T, dispatch: (action: A) => void, stateObj: State] = state.useReducer(type Reducer = Reducer) type Reducer = (state: T, action: A) => T const [state: ST, dispatch: (action: A) => void, stateObj: State] = state.useReducer(type Reducer = Reducer, { selector: Selector }) type Reducer = (state: ST, action: A) => ST const [state: ST, dispatch: (action: A) => void, stateObj: State] = state.useReducer(type Reducer = Reducer, { selector: Selector, patcher: Patcher }) // Store.js (For store.useReducer) type Reducer = (state: T, action: A) => T const [state: T, dispatch: (action: A) => void, stateObj: State] = store.useReducer(type Reducer = Reducer, key, { default: T }); type Reducer = (state: T, action: A) => T const [state: ST, dispatch: (action: A) => void, stateObj: State] = store.useReducer(type Reducer = Reducer, key, { selector: Selector, default: T }) type Reducer = (state: ST, action: A) => ST const [state: ST, dispatch: (action: A) => void, stateObj: State] = store.useReducer(type Reducer = Reducer, key, { selector: Selector, patcher: Patcher, default: T }) ``` ================================================ FILE: website/docs/basic_concepts/_category_.json ================================================ { "label": "Basic Concepts", "position": 2 } ================================================ FILE: website/docs/basic_concepts/derived_state.md ================================================ --- sidebar_position: 3 --- # Derived & Nested State With state pool you can subscribe to deeply nested or derived state. Both `store.useState` and `store.useReducer` accepts an optional configuration parameter with which you can pass `selector` & `patcher` options that are used to derive and update state. Here is a simple example showing how to use `selector` & `Patcher` options ```jsx // With store API const user = createState({ name: "Yezy", age: 25, email: "yezy@me.com" }); function UserName(props){ const selector = (user) => user.name; // Subscribe to user.name only const patcher = (user, name) => {user.name = name}; // Update user.name const [name, setName] = user.useState({selector: selector, patcher: patcher}); const handleNameChange = (e) => { setName(e.target.value); } return (
Name: {name}
); } ``` Or with store API ```jsx // With store API store.setState("user", { name: "Yezy", age: 25, email: "yezy@me.com" }); function UserName(props){ const selector = (user) => user.name; // Subscribe to user.name only const patcher = (user, name) => {user.name = name}; // Update user.name const [name, setName] = store.useState("user", {selector: selector, patcher: patcher}); const handleNameChange = (e) => { setName(e.target.value); } return (
Name: {name}
); } ``` Here `selector` & `patcher` are used for specifying a way to select deeply nested state(derive new state) and update it. - `selector` should be a function which takes one parameter which is the state and returns a selected value. The purpose of this is to subscribe to a deeply nested or derived state. - `patcher` should be a function which takes two parameters, the first is the state and the second is the selected value. The purpose of this is to merge back the selected value to the state once it's updated. ================================================ FILE: website/docs/basic_concepts/managing_subscriptions.md ================================================ --- sidebar_position: 4 --- # Managing Subscriptions If you want to listen to changes from a state you can subscribe to it by using `state.subscribe`. it accepts an observer function. For example ```js const state = createState(value); // Subscribe to state changes const unsubscribe = state.subscribe(function(value){ // value is the new value of a state }) // You can unsubscribe by calling the result unsubscribe(); ``` You can even subscribe to a deeply nested state by using a selector as ```js state.subscribe({ observer: function(value){ // value is the new value of a state }, selector: function(value){ return selected_state } }) ``` With this, observer function will only be called when the selected state changes. Another way to subscribe to nested state or derived state is to call `select` on a state then subscribe to it as ```js state.select(state => selected_state).subscribe(value =>{ // Do your thing here } ) ``` # Subscriptions in a store If you want to listen to changes in an entire store you can subscribe to it by using `store.subscribe`. it accepts an observer function. For example ```js // Subscribe to store changes const unsubscribe = store.subscribe(function(key: String, value: Any){ // key is the key for a state that has changed // value is the new value of a state }) // You can unsubscribe by calling the result unsubscribe(); ``` If you want to subscribe to a single state you can use ```js // Subscribe to store changes const unsubscribe = store.getState(key).subscribe(function(value){ // value is the new value of a state }) // You can unsubscribe by calling the result unsubscribe(); ``` You can even subscribe to a deeply nested state by using a selector as ```js store.getState(key).subscribe({ observer: function(value){ // value is the new value of a state }, selector: function(value){ return selected_state }) }) ``` With this, observer function will only be called when the selected state changes. Another way to subscribe to nested state or derived state is to call `select` on a state then subscribe to it as ```js store.getState(key).select(state => selected_state).subscribe(value =>{ // Do your thing here } ) ``` ================================================ FILE: website/docs/basic_concepts/state_persistence.md ================================================ --- sidebar_position: 5 --- # State Persistence State pool has a built in support for state persistence through store API, it makes saving your states in your preferred permanent storage very easy, all you need to do is tell state pool how to save, load, clear and remove your state from your preferred storage by using `store.persist` API. The way to implement these is by calling `store.persist` and pass them as shown below ```js store.persist({ saveState: function(key, value, isInitialSet){/*your code to save state */}, loadState: function(key, noState){/*your code to load state */}, removeState: function(key){/*your code to remove state */}, clear: function(){/*your code to clear storage */} }) ``` After implementing these four functions you're good to go, you won’t need to worry about calling them, **state-pool** will be doing that for you automatically so that you can focus on using your states. Both `store.setState`, `store.useState` and `store.useReducer` accepts an optional configuration parameter, `persist`, this is the one which is used to tell **state-pool** whether to save your state to a permanent storage or not. i.e ```js store.setState( key: String, initialState: Any, config?: {persist: Boolean} ) ``` ```js store.useState( key: String, config?: {default: Any, persist: Boolean, ...otherConfigs} ) ``` ```js store.useReducer( reducer: Function, key: String, config?: {default: Any, persist: Boolean, ...otherConfigs} ) ``` By default the value of `persist` in all cases is `false`(which means it doesn't save states to a permanent storage), so if you want to activate it, you have to set it to be true. What's even better about **state-pool** is that you get the freedom to choose what to save in your permanent storage, so you don't need to save the whole store in your permanent storage, but if you want to save the whole store you can use `PERSIST_ENTIRE_STORE` configuration. Below is an example showing how you could implement state persistance in local storage. ```js import { createStore } from 'state-pool'; const store = createStore(); function debounce(func, timeout) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { func.apply(this, args); }, timeout); }; } store.persist({ PERSIST_ENTIRE_STORE: true, // Use this only if you want to persist the entire store saveState: function (key, value, isInitialSet) { const doStateSaving = () => { try { const serializedState = JSON.stringify(value); window.localStorage.setItem(key, serializedState); } catch { // Ignore write errors } } if (isInitialSet) { // Here we don't debounce saving state since it's the initial set // so it's called only once and we need our storage to be updated // right away doStateSaving(); } else { // Here we debounce saving state because it's the update and this function // is called every time the store state changes. However, it should not // be called too often because it triggers the expensive `JSON.stringify` operation. const DEBOUNCE_TIME = 1000 // In milliseconds // Debounce doStateSaving before calling it const processStateSaving = debounce(doStateSaving, DEBOUNCE_TIME); processStateSaving() // save State } }, loadState: function (key, noState) { try { const serializedState = window.localStorage.getItem(key); if (serializedState === null) { // No state saved return noState; } return JSON.parse(serializedState); } catch (err) { // Failed to load state return undefined } }, removeState: function (key) { window.localStorage.removeItem(key); }, clear: function () { window.localStorage.clear(); } }) ``` :::important When you set `PERSIST_ENTIRE_STORE = true`, **state-pool** will be persisting all your states to the permanent storage by default unless you explicitly specify `persist = false` when initializing your state. ::: ================================================ FILE: website/docs/basic_concepts/store.md ================================================ --- sidebar_position: 1 --- # Store A store is a container for states. Store implements and encapsulates everything you need to easily manage your states including `store.setState`, `store.getState`, `store.useState`, `store.subscribe` and other functionalities. A store is created by using `createStore` API as ```js import { createStore } from 'state-pool'; const store = createStore(); ``` ## Adding states to a store Since a store is just a container for our state, we would eventually need to add states to it. Store provides `store.setState` API which is used to create a state and add it to a store by mapping it to a key. `store.setState` takes two required parameters, a key(string) to map to a state object and the initial value, In addition to those two parameters it takes a third optional parameter which is the configuration object. ```js // Signature store.setState(key: String, initialState: Any, config?: {persist: Boolean}) ``` Here is an example showing how to use `store.setState` ```js store.setState("count", 0); ```
Another way to add state in a store is using `createStore`, in this way we're creating a store and initializing it at the same time. i.e ```js import { createStore } from 'state-pool'; const store = createStore({"count": 0}); ``` ```js // createStore Signature createStore({key1: stateValue1, key1, stateValue2, ...}) ``` :::important `store.setState` should be used outside of a react component to make sure that it's executed only once. If you use it inside a react component make sure it's executed only once in order to avoid overriding state in a store. ::: ================================================ FILE: website/docs/basic_concepts/using_store_state.md ================================================ --- sidebar_position: 2 --- # Using Store State After creating a store and setting states to it we need to use our states in components. Here a store provides `store.useState` hook which is used to consume a state from a store in a component, it's basically a way for a component to subscribe to a state from a store. `store.useState` works just like `React.useState` hook but it accepts a key for the state to use and returns an array of `[state, setState, updateState]` rather than `[state, setState]`. In addition to the key parameter it also accept another optional parameter which is the config object, available configurations are `default`, `persist`, `selector` & `patcher`, these are discussed in detail on [`store.useState` API](/docs/api_reference/high_level_api/store.useState). ```js // Signature store.useState( key: String, config?: {default: Any, persist: Boolean, selector: Function, patcher: Function} ) ``` Below is an example showing how to use `store.useState` hook ```js store.setState("user", {name: "Yezy", email: "yezy@me.com"}); function Component(props){ const [user, setUser, updateUser] = store.useState("user"); // Other stuff } ``` Here `updateUser` is a higher order function which accepts another function for updating user as an argument(this another functions takes user(old state) as the argument). So to update any value on user you could do ```js updateUser(function(user){ user.name = "Yezy Ilomo"; user.email = "hello@yezy.com"; }) ``` Or you could just use `setUser` instead of `updateUser` i.e ```js setUser({name: "Yezy Ilomo", email: "hello@yezy.com"}); ``` Or ```js setUser(function(user){ return { name: "Yezy Ilomo", email: "hello@yezy.com" } }) ```
Another way to use store state is through `store.useReducer` which is an alternative to `store.useState`, it works just like `React.useReducer` hook(If you’re familiar with `React.useReducer`, you already know how this works). `store.useReducer` accepts a reducer and a key for the state as parameters, it returns the current state paired with a dispatch method. In addition to the two parameters it also accept another optinal perameter which is the configuration object, available configurations are `default`, `persist`, `selector` & `patcher`, they work exactly the same just like in `store.useState`. Below is a simple example showing how to use it ```js store.setState("user", {name: "Yezy", email: "yezy@me.com"}); function myReducer(state, action){ // This could be any reducer // Do whatever you want to do here return newState } function Component(props){ const [name, dispatch] = store.useReducer(myReducer, "user"); // Other stuff ... } ``` You can learn more about `store.useReducer` on [`store.useReducer` API section](/docs/api_reference/high_level_api/store.useReducer)
:::tip Both `store.useState` and `store.useReducer` accepts an optional `default` configuration, this is used to specify the default value if you want `store.useState` or `store.useReducer` to create a state if it doesn't find the one for the key specified in the first argument. For example ```js const [user, setUser] = store.useState("user", {default: null}); ``` This piece of code means, get the state for the key "user" if it's not available in a store, create one and assign it the value `null`. So state pool can create state dynamically. ::: ================================================ FILE: website/docs/basic_tutorial/_category_.json ================================================ { "label": "Basic Tutorial", "position": 3 } ================================================ FILE: website/docs/basic_tutorial/intro.md ================================================ --- sidebar_position: 1 --- # Intro ## Coming soon.... ================================================ FILE: website/docs/introduction/_category_.json ================================================ { "label": "Introduction", "position": 1 } ================================================ FILE: website/docs/introduction/getting_started.md ================================================ --- sidebar_position: 3 --- # Getting Started Using **state-pool** to manage state is very simple, all you need to do is 1. Create and initialize a state by using `createState` 2. Use your state in your component through `useState` hooks These two steps summarises pretty much everything you need to use **state-pool**. Below are few examples showing how to use **state-pool** to manage states. ```jsx // Example 1. import React from 'react'; import { createState } from 'state-pool'; const count = createState(0); // Create "count" state and initialize it with 0 function ClicksCounter(props){ // Use "count" state const [count, setCount] = count.useState(); const incrementCount = (e) => { setCount(count+1) } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ```
The other way to do it is using `useState` from `state-pool` ```jsx // Example 2. import React from 'react'; import { createState, useState } from 'state-pool'; const count = createState(0); // Create "count" state and initialize it with 0 function ClicksCounter(props){ // Use "count" state const [count, setCount] = useState(count); const incrementCount = (e) => { setCount(count+1) } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ```
# What about local state? With **state-pool**, state are just like variables, if declared on a global scope, it’s a global state and if declared on local scope it’s a local state, so the difference between global state and local state in **state-pool** lies where you declare them just like variables. Here is an example for managing local state ```jsx // Example 1. import React from 'react'; import { useState } from 'state-pool'; function ClicksCounter(props){ // Here `useState` hook will create "count" state and initialize it with 0 // Note: the `useState` hook used here is impored from state-pool and not react const [count, setCount] = useState(0); const incrementCount = (e) => { setCount(count+1) } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ```
If you don't want **state-pool's** `useState` to collide with **React's** `useState` you can import `StatePool` and use the hook from there, Here is an example ```jsx // Example 2. import React from 'react'; import StatePool from 'state-pool'; function ClicksCounter(props){ // Here `useState` hook will create "count" state and initialize it with 0 const [count, setCount] = StatePool.useState(0); const incrementCount = (e) => { setCount(count+1) } return (
Count: {count}
); } ReactDOM.render(ClicksCounter, document.querySelector("#root")); ```
# Isn't `StatePool.useState` the same thing as `React.useState`? **Definitely. not!...** Both can be used to manage local state, and that's where the similarity ends. `StatePool.useState` offers more features, for one it offers a simple way to update nested data unlike `React.useState`, it's also flexible as it's used to manage both global state and local state. So you could say `React.useState` is a subset of `StatePool.useState`. Here is an example of `StatePool.useState` in action, updating nested data ```jsx // Example 2. import React from 'react'; import StatePool from 'state-pool'; const user = StatePool.createState({name: "Yezy", age: 25}); function UserInfo(props){ const [user, setUser, updateUser] = StatePool.useState(user); const updateName = (e) => { updateUser(user => { user.name = e.target.value; }); } return (
Name: {user.name}
); } ReactDOM.render(UserInfo, document.querySelector("#root")); ``` With `React.useState` you would need to recreate `user` object when updating `user.name`, but with `StatePool.useState` you don't need that, you just update the value right away. That's one advantage of using `StatePool.useState` but there are many more, you'll learn when going through [**documentation**📝](https://yezyilomo.github.io/state-pool/). # Store based example If you have many states and you would like to organize them into a store, **state-pool** allows you to do that too and provides a ton of features on top of it. Here are steps for managing state with a store 1. Create a store(which is basically a container for your state) 1. Create and initialize a state by using `store.setState` 2. Use your state in your component through `store.useState` hooks These three steps summarises pretty much everything you need to manage state with a store. Below are few examples of store in action ```jsx // Example 1. import { createStore } from 'state-pool'; const store = createStore(); // Create store for storing our state store.setState("count", 0); // Create "count" state and add it to the store function ClicksCounter(props){ // Use "count" state const [count, setCount] = store.useState("count"); return (
Count: {count}
); } ```
```jsx // Example 2. import { createStore } from 'state-pool'; // Instead of using createStore and store.setState, // you can combine store creation and initialization as follows const store = createStore({"user", {name: "Yezy", age: 25}}); // create store and initialize it with user function UserInfo(props){ const [user, setUser, updateUser] = store.useState("user"); const updateName = (e) => { updateUser(user => { user.name = e.target.value; }); } return (
Name: {user.name}
); } ```
**State-pool** doesn't enforce storing your states in a store, If you don't like using the architecture of store you can still use **state-pool** without it. In **state-pool**, store is just a container for states, so you can still use your states without it, in fact **state-pool** doesn’t care where you store your states as long as you can access them you're good to go.
Pretty cool, right? ================================================ FILE: website/docs/introduction/installation.md ================================================ --- sidebar_position: 2 --- # Installation ```bash npm install state-pool ``` Or ```bash yarn add state-pool ``` ================================================ FILE: website/docs/introduction/motivation.md ================================================ --- sidebar_position: 1 --- # Motivation ![Build Status](https://github.com/yezyilomo/state-pool/actions/workflows/node.js.yml/badge.svg?branch=master) [![Build Size](https://img.shields.io/bundlephobia/minzip/state-pool?label=bundle-size&style=flat)](https://bundlephobia.com/result?p=state-pool) [![Version](https://img.shields.io/npm/v/state-pool?style=flat)](https://www.npmjs.com/package/state-pool) [![Downloads](https://img.shields.io/npm/dt/state-pool.svg?style=flat)](https://www.npmjs.com/package/state-pool) Transform your React app with our state management library! Declare global and local states like variables, powered by the magic of React hooks 🪄✨ ## Features & Advantages - Simple, familiar, flexible and very minimal core API but powerful - Built-in support for state persistence - Very easy to learn because its API is very similar to react state hook's API - Support selecting deeply nested state - Support creating state dynamically - Can be used outside react components - Doesn't wrap your app in context providers - Very organized API, You can do almost everything with a single import ## State Flow 1. Create a state 2. Subscribe a component(s) to the state created 3. If a component wants to update the state, it sends update request 4. When a state receives update request, it performs the update and send signal to all components subscribed to it for them to update themselves(re-render) You can try live examples [Here](https://yezyilomo.github.io/state-pool-examples) ================================================ FILE: website/docusaurus.config.js ================================================ // @ts-check // Note: type annotations allow type checking and IDEs autocompletion const lightCodeTheme = require('prism-react-renderer/themes/dracula'); const darkCodeTheme = require('prism-react-renderer/themes/dracula'); /** @type {import('@docusaurus/types').Config} */ const config = { title: 'State Pool', tagline: 'Transform your React app with our state management library! Declare global and local states like variables, powered by the magic of React hooks 🪄✨', url: 'https://yezyilomo.github.io', baseUrl: '/state-pool/', onBrokenLinks: 'throw', onBrokenMarkdownLinks: 'warn', favicon: 'img/favicon.png', organizationName: 'yezyilomo', // Usually your GitHub org/user name. projectName: 'state-pool', // Usually your repo name. presets: [ [ 'classic', /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { sidebarPath: require.resolve('./sidebars.js'), // Please change this to your repo. editUrl: 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', }, blog: { showReadingTime: true, // Please change this to your repo. editUrl: 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', }, theme: { customCss: require.resolve('./src/css/custom.css'), }, }), ], ], themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ navbar: { title: 'State Pool', logo: { alt: 'State Pool Logo', src: 'img/logo.svg', }, items: [ { type: 'doc', docId: 'introduction/motivation', position: 'left', label: 'Docs', }, /*{to: '/blog', label: 'Blog', position: 'left'},*/ { position: 'left', label: 'Concepts', to: '/docs/basic_concepts/store', }, { href: 'https://github.com/yezyilomo/state-pool', label: 'GitHub', position: 'right', }, ], }, footer: { style: 'dark', links: [ { title: 'Docs', items: [ { label: 'Tutorial', to: '/docs/basic_tutorial/intro', }, ], }, { title: 'Community', items: [ { label: 'Twitter', href: 'https://twitter.com/yezyilomo', }, ], }, { title: 'More', items: [ /* { label: 'Blog', to: '/blog', }, */ { label: 'GitHub', href: 'https://github.com/yezyilomo/state-pool/', }, ], }, ], copyright: `Copyright © ${new Date().getFullYear()} State Pool, Inc. Built with Docusaurus.`, }, prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, }, }), }; module.exports = config; ================================================ FILE: website/package.json ================================================ { "name": "website", "version": "0.0.0", "private": true, "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { "@docusaurus/core": "2.2.0", "@docusaurus/preset-classic": "2.2.0", "@mdx-js/react": "^1.6.22", "clsx": "^1.1.1", "prism-react-renderer": "^1.2.1", "react": "^17.0.1", "react-dom": "^17.0.1" }, "browserslist": { "production": [ ">0.5%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } ================================================ FILE: website/sidebars.js ================================================ /** * Creating a sidebar enables you to: - create an ordered group of docs - render a sidebar for each doc of that group - provide next/previous navigation The sidebars can be generated from the filesystem, or explicitly defined here. Create as many sidebars as you want. */ // @ts-check /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { // By default, Docusaurus generates a sidebar from the docs folder structure tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], // But you can create a sidebar manually /* tutorialSidebar: [ { type: 'category', label: 'Tutorial', items: ['hello'], }, ], */ }; module.exports = sidebars; ================================================ FILE: website/src/components/HomepageFeatures/index.js ================================================ import React from 'react'; import clsx from 'clsx'; import styles from './styles.module.css'; const FeatureList = [ { title: 'Simple and Familiar', Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, description: ( <> State pool comes with a very simple and intuitive API which follows built in state management patterns in React. ), }, { title: 'Very Minimal Core API', Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, description: ( <> State pool comes with a very minimal API which enables developers to manage global state easly without struggling. ), }, { title: 'Powerful', Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, description: ( <> Despite having minimal API, State pool is very powerful. ), }, ]; function Feature({Svg, title, description}) { return (

{title}

{description}

); } export default function HomepageFeatures() { return (
{FeatureList.map((props, idx) => ( ))}
); } ================================================ FILE: website/src/components/HomepageFeatures/styles.module.css ================================================ .features { display: flex; align-items: center; padding: 2rem 0; width: 100%; } .featureSvg { height: 200px; width: 200px; } ================================================ FILE: website/src/css/custom.css ================================================ /** * Any CSS included here will be global. The classic template * bundles Infima by default. Infima is a CSS framework designed to * work well for content-centric websites. */ /* You can override the default Infima variables here. */ :root { --ifm-color-primary: #2e8555; --ifm-color-primary-dark: #29784c; --ifm-color-primary-darker: #277148; --ifm-color-primary-darkest: #205d3b; --ifm-color-primary-light: #33925d; --ifm-color-primary-lighter: #359962; --ifm-color-primary-lightest: #3cad6e; --ifm-code-font-size: 95%; } /* For readability concerns, you should choose a lighter palette in dark mode. */ [data-theme='dark'] { --ifm-color-primary: #25c2a0; --ifm-color-primary-dark: #21af90; --ifm-color-primary-darker: #1fa588; --ifm-color-primary-darkest: #1a8870; --ifm-color-primary-light: #29d5b0; --ifm-color-primary-lighter: #32d8b4; --ifm-color-primary-lightest: #4fddbf; } .docusaurus-highlight-code-line { background-color: rgba(0, 0, 0, 0.1); display: block; margin: 0 calc(-1 * var(--ifm-pre-padding)); padding: 0 var(--ifm-pre-padding); } [data-theme='dark'] .docusaurus-highlight-code-line { background-color: rgba(0, 0, 0, 0.3); } ================================================ FILE: website/src/pages/index.js ================================================ import React from 'react'; import clsx from 'clsx'; import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import styles from './index.module.css'; import HomepageFeatures from '@site/src/components/HomepageFeatures'; function HomepageHeader() { const {siteConfig} = useDocusaurusContext(); return (

{siteConfig.title}

{siteConfig.tagline}

Get started - 5min ⏱️
); } export default function Home() { const {siteConfig} = useDocusaurusContext(); return (
); } ================================================ FILE: website/src/pages/index.module.css ================================================ /** * CSS files with the .module.css suffix will be treated as CSS modules * and scoped locally. */ .heroBanner { padding: 4rem 0; text-align: center; position: relative; overflow: hidden; } @media screen and (max-width: 996px) { .heroBanner { padding: 2rem; } } .buttons { display: flex; align-items: center; justify-content: center; } ================================================ FILE: website/src/pages/markdown-page.md ================================================ --- title: Markdown page example --- # Markdown page example You don't need React to write simple standalone pages. ================================================ FILE: website/static/.nojekyll ================================================