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/)

[](https://bundlephobia.com/result?p=state-pool)
[](https://www.npmjs.com/package/state-pool)
[](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:

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