) => ({
7 | get: () => s.value,
8 | increment: () => s.set(p => p + 1)
9 | })
10 |
11 | // The following 2 functions can be exported now:
12 | export const accessGlobalState = () => wrapState(globalState)
13 | export const useGlobalState = () => wrapState(useHookstate(globalState))
14 |
15 | // And here is how it can be used outside of a component ...
16 | setInterval(() => accessGlobalState().increment(), 3000)
17 | // ... and inside of a component
18 | export const ExampleComponent = () => {
19 | const state = useGlobalState();
20 | return
21 | Counter value: {state.get()} (watch +1 every 3 seconds)
22 | state.increment()}>Increment
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/core/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import url from '@rollup/plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url(),
28 | resolve(),
29 | typescript({
30 | tsconfig: 'tsconfig.prod.json',
31 | rollupCommonJSResolveHack: true,
32 | clean: true
33 | }),
34 | commonjs()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/docs/index/src/examples/local-async-state.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHookstate } from '@hookstate/core';
3 |
4 | export const ExampleComponent = () => {
5 | const resourcePath = 'https://raw.githubusercontent.com/avkonst/hookstate/master/CNAME';
6 | const fetchResource = () => fetch(resourcePath)
7 | .then(r => r.text())
8 | const state = useHookstate(fetchResource);
9 |
10 | if (state.promised) {
11 | return Loading {resourcePath}
;
12 | }
13 |
14 | if (state.error) {
15 | return Failed to load {resourcePath}
16 | {state.error.toString()}
17 | state.set(fetchResource)}>Retry
18 |
19 | }
20 |
21 | return Loaded {resourcePath}
22 | {state.value}
23 | state.set(fetchResource)}>Reload
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/docs/index/src/examples/plugin-localstored.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHookstate } from '@hookstate/core';
3 | import { localstored } from '@hookstate/localstored';
4 |
5 | export const ExampleComponent = () => {
6 | const state = useHookstate([{ name: 'First Task' }],
7 | localstored({
8 | // key is optional,
9 | // if it is not defined, the extension requires and
10 | // uses the identifier from the @hookstate/identifiable
11 | key: 'state-key'
12 | }))
13 |
14 | return <>
15 | {state.map((taskState, taskIndex) => {
16 | return
17 | taskState.name.set(e.target.value)}
20 | />
21 |
22 | })}
23 | state.merge([{ name: 'Untitled' }])}>
24 | Add task
25 |
26 | >
27 | }
28 |
--------------------------------------------------------------------------------
/plugins/logged/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import url from '@rollup/plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url(),
28 | resolve(),
29 | typescript({
30 | tsconfig: 'tsconfig.prod.json',
31 | rollupCommonJSResolveHack: true,
32 | clean: true
33 | }),
34 | commonjs()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/plugins/broadcasted/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import url from '@rollup/plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url(),
28 | resolve(),
29 | typescript({
30 | tsconfig: 'tsconfig.prod.json',
31 | rollupCommonJSResolveHack: true,
32 | clean: true
33 | }),
34 | commonjs()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/plugins/clonable/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import url from '@rollup/plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url(),
28 | resolve(),
29 | typescript({
30 | tsconfig: 'tsconfig.prod.json',
31 | rollupCommonJSResolveHack: true,
32 | clean: true
33 | }),
34 | commonjs()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/plugins/comparable/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import url from '@rollup/plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url(),
28 | resolve(),
29 | typescript({
30 | tsconfig: 'tsconfig.prod.json',
31 | rollupCommonJSResolveHack: true,
32 | clean: true
33 | }),
34 | commonjs()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/plugins/devtools/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import url from '@rollup/plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url(),
28 | resolve(),
29 | typescript({
30 | tsconfig: 'tsconfig.prod.json',
31 | rollupCommonJSResolveHack: true,
32 | clean: true
33 | }),
34 | commonjs()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/plugins/localstored/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import url from '@rollup/plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url(),
28 | resolve(),
29 | typescript({
30 | tsconfig: 'tsconfig.prod.json',
31 | rollupCommonJSResolveHack: true,
32 | clean: true
33 | }),
34 | commonjs()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/plugins/validation/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import url from '@rollup/plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url(),
28 | resolve(),
29 | typescript({
30 | tsconfig: 'tsconfig.prod.json',
31 | rollupCommonJSResolveHack: true,
32 | clean: true
33 | }),
34 | commonjs()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/plugins/identifiable/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import url from '@rollup/plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url(),
28 | resolve(),
29 | typescript({
30 | tsconfig: 'tsconfig.prod.json',
31 | rollupCommonJSResolveHack: true,
32 | clean: true
33 | }),
34 | commonjs()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/plugins/initializable/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import url from '@rollup/plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url(),
28 | resolve(),
29 | typescript({
30 | tsconfig: 'tsconfig.prod.json',
31 | rollupCommonJSResolveHack: true,
32 | clean: true
33 | }),
34 | commonjs()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/plugins/serializable/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import url from '@rollup/plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url(),
28 | resolve(),
29 | typescript({
30 | tsconfig: 'tsconfig.prod.json',
31 | rollupCommonJSResolveHack: true,
32 | clean: true
33 | }),
34 | commonjs()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/plugins/snapshotable/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import url from '@rollup/plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url(),
28 | resolve(),
29 | typescript({
30 | tsconfig: 'tsconfig.prod.json',
31 | rollupCommonJSResolveHack: true,
32 | clean: true
33 | }),
34 | commonjs()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/plugins/subscribable/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import url from '@rollup/plugin-url'
6 |
7 | import pkg from './package.json'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: pkg.main,
14 | format: 'cjs',
15 | exports: 'named',
16 | sourcemap: true
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | exports: 'named',
22 | sourcemap: true
23 | }
24 | ],
25 | plugins: [
26 | external(),
27 | url(),
28 | resolve(),
29 | typescript({
30 | tsconfig: 'tsconfig.prod.json',
31 | rollupCommonJSResolveHack: true,
32 | clean: true
33 | }),
34 | commonjs()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "noUnusedLocals": false,
16 | "experimentalDecorators": true,
17 | "newLine": "LF",
18 | "allowJs": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "strict": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "module": "esnext",
26 | "moduleResolution": "node",
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": false,
30 | "jsx": "preserve"
31 | },
32 | "include": [
33 | "src", "src/__tests__"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/plugins/logged/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "noUnusedLocals": false,
16 | "experimentalDecorators": true,
17 | "newLine": "LF",
18 | "allowJs": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "strict": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "module": "esnext",
26 | "moduleResolution": "node",
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": false,
30 | "jsx": "preserve"
31 | },
32 | "include": [
33 | "src", "src/__tests__"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/nx.json:
--------------------------------------------------------------------------------
1 | {
2 | "implicitDependencies": {
3 | "package.json": {
4 | "dependencies": "*",
5 | "devDependencies": "*"
6 | },
7 | "nx.json": "*"
8 | },
9 | "npmScope": "@hookstate",
10 | "tasksRunnerOptions": {
11 | "default": {
12 | "runner": "@nrwl/workspace/tasks-runners/default",
13 | "options": {
14 | "cacheableOperations": ["build", "test", "lint", "e2e"],
15 | "parallel": 1
16 | }
17 | }
18 | },
19 | "affected": {
20 | "defaultBase": "master"
21 | },
22 | "targetDependencies": {
23 | "build": [
24 | {
25 | "target": "build",
26 | "projects": "dependencies"
27 | }
28 | ],
29 | "test": [
30 | {
31 | "target": "build",
32 | "projects": "dependencies"
33 | }
34 | ],
35 | "test:ci": [
36 | {
37 | "target": "build",
38 | "projects": "dependencies"
39 | }
40 | ],
41 | "start": [
42 | {
43 | "target": "build",
44 | "projects": "dependencies"
45 | }
46 | ],
47 | "release": [
48 | {
49 | "target": "build",
50 | "projects": "self"
51 | }
52 | ]
53 | },
54 | "defaultProject": "core"
55 | }
56 |
--------------------------------------------------------------------------------
/plugins/broadcasted/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "noUnusedLocals": false,
16 | "experimentalDecorators": true,
17 | "newLine": "LF",
18 | "allowJs": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "strict": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "module": "esnext",
26 | "moduleResolution": "node",
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": false,
30 | "jsx": "preserve"
31 | },
32 | "include": [
33 | "src", "src/__tests__"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/plugins/clonable/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "noUnusedLocals": false,
16 | "experimentalDecorators": true,
17 | "newLine": "LF",
18 | "allowJs": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "strict": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "module": "esnext",
26 | "moduleResolution": "node",
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": false,
30 | "jsx": "preserve"
31 | },
32 | "include": [
33 | "src", "src/__tests__"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/plugins/comparable/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "noUnusedLocals": false,
16 | "experimentalDecorators": true,
17 | "newLine": "LF",
18 | "allowJs": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "strict": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "module": "esnext",
26 | "moduleResolution": "node",
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": false,
30 | "jsx": "preserve"
31 | },
32 | "include": [
33 | "src", "src/__tests__"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/plugins/devtools/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "noUnusedLocals": false,
16 | "experimentalDecorators": true,
17 | "newLine": "LF",
18 | "allowJs": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "strict": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "module": "esnext",
26 | "moduleResolution": "node",
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": false,
30 | "jsx": "preserve"
31 | },
32 | "include": [
33 | "src", "src/__tests__"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/plugins/identifiable/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "noUnusedLocals": false,
16 | "experimentalDecorators": true,
17 | "newLine": "LF",
18 | "allowJs": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "strict": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "module": "esnext",
26 | "moduleResolution": "node",
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": false,
30 | "jsx": "preserve"
31 | },
32 | "include": [
33 | "src", "src/__tests__"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/plugins/localstored/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "noUnusedLocals": false,
16 | "experimentalDecorators": true,
17 | "newLine": "LF",
18 | "allowJs": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "strict": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "module": "esnext",
26 | "moduleResolution": "node",
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": false,
30 | "jsx": "preserve"
31 | },
32 | "include": [
33 | "src", "src/__tests__"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/plugins/serializable/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "noUnusedLocals": false,
16 | "experimentalDecorators": true,
17 | "newLine": "LF",
18 | "allowJs": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "strict": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "module": "esnext",
26 | "moduleResolution": "node",
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": false,
30 | "jsx": "preserve"
31 | },
32 | "include": [
33 | "src", "src/__tests__"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/plugins/snapshotable/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "noUnusedLocals": false,
16 | "experimentalDecorators": true,
17 | "newLine": "LF",
18 | "allowJs": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "strict": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "module": "esnext",
26 | "moduleResolution": "node",
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": false,
30 | "jsx": "preserve"
31 | },
32 | "include": [
33 | "src", "src/__tests__"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/plugins/subscribable/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "noUnusedLocals": false,
16 | "experimentalDecorators": true,
17 | "newLine": "LF",
18 | "allowJs": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "strict": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "module": "esnext",
26 | "moduleResolution": "node",
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": false,
30 | "jsx": "preserve"
31 | },
32 | "include": [
33 | "src", "src/__tests__"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/plugins/validation/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "noUnusedLocals": false,
16 | "experimentalDecorators": true,
17 | "newLine": "LF",
18 | "allowJs": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "strict": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "module": "esnext",
26 | "moduleResolution": "node",
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": false,
30 | "jsx": "preserve"
31 | },
32 | "include": [
33 | "src", "src/__tests__"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/plugins/initializable/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "sourceMap": true,
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "noUnusedLocals": false,
16 | "experimentalDecorators": true,
17 | "newLine": "LF",
18 | "allowJs": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "allowSyntheticDefaultImports": true,
22 | "strict": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "suppressImplicitAnyIndexErrors": true,
25 | "module": "esnext",
26 | "moduleResolution": "node",
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": false,
30 | "jsx": "preserve"
31 | },
32 | "include": [
33 | "src", "src/__tests__"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/docs/index/docs/30x-writing-an-extension.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: writing-extension
3 | title: Writing your own extension
4 | sidebar_label: Writing an extension
5 | ---
6 |
7 | import { PreviewSample } from '../src/PreviewSample'
8 |
9 | An extension is effectively a factory of a set of callbacks. All callbacks are optional.
10 |
11 | `onCreate` callback returns implementation for extension methods and properties which are added to a State object, where this extension is activated. If your extension does not add any properties or methods, do not provide `onCreate` callback or return `{}` from it.
12 |
13 | Here is an example of an extension which has got all possible callbacks and prints console logs when callbacks are called. It also defines an extension method and an extension property. The example is relatively long, because we provided extensive comments and mentioned other available capabilities,
14 | which we did not use in this instance.
15 |
16 | For more information, check out how the existing standard plugins are implemented. In case of any issues, just raise a ticket on Github.
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/docs/index/src/examples/plugin-subscribable.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHookstate } from '@hookstate/core';
3 | import { subscribable } from '@hookstate/subscribable';
4 |
5 | export const ExampleComponent = () => {
6 | const state = useHookstate({ a: 1, b: 1 }, subscribable())
7 |
8 | React.useEffect(() => state.subscribe(
9 | (v) => console.log('updated any counter', JSON.stringify(v))), [])
10 | React.useEffect(() => state.a.subscribe(
11 | (v) => console.log('updated counter A', JSON.stringify(v))), [])
12 | React.useEffect(() => state.b.subscribe(
13 | (v) => console.log('updated counter B', JSON.stringify(v))), [])
14 |
15 | return <>
16 | Open console to see the subscription effect
17 | Counter A: {state.a.value}
18 | Counter B: {state.b.value}
19 | state.a.set(p => p + 1)}>
20 | Increment Counter A
21 |
22 | state.b.set(p => p + 1)}>
23 | Increment Counter B
24 |
25 | >
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Andrey Konstantinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/demos/todolist/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Andrey
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/plugins/clonable/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Andrey Konstantinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/plugins/comparable/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Andrey Konstantinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/plugins/devtools/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Andrey Konstantinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/plugins/logged/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Andrey Konstantinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/plugins/validation/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Andrey Konstantinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/plugins/broadcasted/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Andrey Konstantinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/plugins/identifiable/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Andrey Konstantinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/plugins/initializable/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Andrey Konstantinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/plugins/localstored/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Andrey Konstantinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/plugins/serializable/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Andrey Konstantinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/plugins/snapshotable/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Andrey Konstantinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/plugins/subscribable/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Andrey Konstantinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/index/src/examples/performance-demo-large-form.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHookstate, State } from '@hookstate/core';
3 |
4 | export const ExampleComponent = () => {
5 | const state = useHookstate(Array.from(Array(5000).keys()).map(i => `Field #${i + 1} value`));
6 | return <>
7 |
8 | {state.map((taskState, taskIndex) =>
9 |
10 | )}
11 | >
12 | }
13 |
14 | function FieldEditor(props: { fieldState: State }) {
15 | const scopedState = useHookstate(props.fieldState);
16 | return
17 | Last render at: {(new Date()).toISOString()} scopedState.set(e.target.value)}
20 | />
21 |
22 | }
23 |
24 | function JsonDump(props: { state: State }) {
25 | const scopedState = useHookstate(props.state);
26 | return
27 | Last render at: {(new Date()).toISOString()} (JSON dump of the first 10 fields )
28 | : {JSON.stringify(scopedState.get().slice(0, 10), undefined, 4)}
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/docs/index/docs/12-using-with-non-json.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: state-with-non-json-objects
3 | title: Using state with complex non-JSON serializable data
4 | sidebar_label: Using state with non-JSON data
5 | ---
6 |
7 | It is possible to have states (root level or nested) to hold instances of custom classes, including standard classes like Date. Accessing these values, will automatically enable `noproxy` option for the [State.get](typedoc-hookstate-core#get) function. It means these values will be tracked by Hookstate for rerendering purpose as whole instances, ie. using one property of such a value, means the entire object is used.
8 |
9 | If a state value holds a function, but the value is an instance of Object class, then it is required to set noproxy explicitly before accessing the function, for example:
10 |
11 | ```tsx
12 | let state = useHookstate({ callback: () => {} })
13 | state.get({ noproxy: true }).callback()
14 | ```
15 |
16 | If you use extensions, such as `localstored`, which requires to serialize and deserialize the state value, you may need to add [serializable extension](/docs/extensions-overview) to the state and define how a custom class value should be serialized and deserialized.
17 |
--------------------------------------------------------------------------------
/docs/index/src/pages/styles.module.css:
--------------------------------------------------------------------------------
1 | /**
2 | * CSS files with the .module.css suffix will be treated as CSS modules
3 | * and scoped locally.
4 | */
5 |
6 | .heroTitle {
7 | text-transform: uppercase;
8 | padding: 2rem 0;
9 | padding-bottom: 1rem;
10 | }
11 |
12 | .heroBanner {
13 | /* background-size: 100%;
14 | background-position: 0;
15 | background-image: linear-gradient( rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5) ), url('https://static.vecteezy.com/system/resources/previews/000/642/562/non_2x/abstract-futuristic-hexagon-shape-pattern-connection-in-gradient-blue-technology-background-vector.jpg'); */
16 | background-color: rgb(11, 57, 102);
17 | padding: 4rem 0;
18 | text-align: center;
19 | position: relative;
20 | overflow: hidden;
21 | }
22 |
23 | @media screen and (max-width: 966px) {
24 | .heroBanner {
25 | padding: 2rem;
26 | }
27 | }
28 |
29 | .buttons {
30 | display: flex;
31 | align-items: center;
32 | justify-content: center;
33 | }
34 |
35 | .features {
36 | display: flex;
37 | align-items: center;
38 | padding: 2rem 0;
39 | width: 100%;
40 | }
41 |
42 | .featureImage {
43 | height: 200px;
44 | width: 200px;
45 | }
46 |
47 | .getStarted {
48 | background-color: lightgreen;
49 | }
--------------------------------------------------------------------------------
/docs/demos/todolist/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/todolist",
3 | "version": "4.0.0-rc3",
4 | "private": true,
5 | "homepage": ".",
6 | "main": "build/App.js",
7 | "module": "build/App.es.js",
8 | "jsnext:main": "build/App.es.js",
9 | "types": "build/App.d.ts",
10 | "dependencies": {
11 | "@hookstate/core": "workspace:*",
12 | "@hookstate/devtools": "workspace:*",
13 | "@types/jest": "28.1.4",
14 | "@types/node": "18.0.3",
15 | "@types/react": "18.0.15",
16 | "@types/react-dom": "18.0.6",
17 | "npm-check-updates": "^15.2.1",
18 | "react": "18.2.0",
19 | "react-dom": "18.2.0",
20 | "react-scripts": "5.0.1",
21 | "tslib": "^2.4.0",
22 | "typescript": "4.7.4",
23 | "serve": "13.0.4"
24 | },
25 | "scripts": {
26 | "start": "react-scripts start",
27 | "build": "tsc -p tsconfig.lib.json",
28 | "build:app": "react-scripts build",
29 | "serve": "serve build",
30 | "update:deps": "ncu -u"
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | }
44 | }
--------------------------------------------------------------------------------
/docs/index/blog/2020-05-29-cool-performance-demo-from-moonpiano.md:
--------------------------------------------------------------------------------
1 | ---
2 | slug: cool-hookstate-performance-demo-from-moonpiano-application
3 | title: Performance demo from the Moonpiano application
4 | author: avkonst
5 | # author_title: Front End Engineer @ Facebook
6 | author_url: https://github.com/avkonst
7 | # author_image_url: https://avatars0.githubusercontent.com/u/1315101?s=400&v=4
8 | tags: [hookstate, performance, applications]
9 | ---
10 |
11 | import ReactPlayer from 'react-player'
12 |
13 | Thanks to [@praisethemoon](https://github.com/praisethemoon) for creating such an impressive performance demo of the [Moonpiano application](https://moonpiano.praisethemoon.org/) powered by Hookstate. Here is the [scoped state](https://hookstate.js.org/docs/scoped-state) and [state usage tracking](https://hookstate.js.org/docs/performance-intro) technologies of the Hookstate library acting in full power:
14 |
15 |
16 |
17 | ###
18 |
19 | You can read [the full story](https://praisethemoon.org/hookstate-how-one-small-react-library-saved-moonpiano/) about boosting the performance of frequent state changes in the Moonpiano application, which is based on React for rendering and Hookstate for state management.
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/workspace",
3 | "version": "4.0.0",
4 | "description": "The workspace for @hookstate.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "engines": {
17 | "node": ">=16",
18 | "pnpm": ">=7.18.2"
19 | },
20 | "scripts": {
21 | "build": "nx run-many --target=build --all",
22 | "test": "nx run-many --target=test --all",
23 | "test:ci": "nx run-many --target=test:ci --all",
24 | "update:deps": "nx run-many --target=update:deps --all",
25 | "clean": "nx run-many --target=clean --all",
26 | "release": "nx run-many --target=release --all",
27 | "nx:w": "nodemon node_modules/@nrwl/cli/bin/nx.js"
28 | },
29 | "devDependencies": {
30 | "@nrwl/cli": "15.3.3",
31 | "@nrwl/workspace": "15.3.3",
32 | "jest": "28.1.2",
33 | "nodemon": "^2.0.16",
34 | "nx": "15.3.3",
35 | "typescript": "4.6.2"
36 | }
37 | }
--------------------------------------------------------------------------------
/docs/index/docs/02-global-state.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: global-state
3 | title: Global state
4 | sidebar_label: Global state
5 | ---
6 |
7 | import { PreviewSample } from '../src/PreviewSample'
8 |
9 | ## Creating and using global state
10 |
11 | Create the state and use it within and outside a React component. Few lines of code. No boilerplate!
12 |
13 |
14 |
15 | The state is created by [hookstate](typedoc-hookstate-core#hookstate). The first argument is the initial state value. The result value is an instance of [State](typedoc-hookstate-core#state),
16 | which **can be** used directly to get and set the state value outside a React component.
17 |
18 | When you need to use the state in a functional React component,
19 | pass the created state to [useHookstate](typedoc-hookstate-core#usehookstate) function
20 | and use the returned result in the component's logic.
21 | The returned result is an instance of [State](typedoc-hookstate-core#state) too,
22 | which **must be** used within a React component (during rendering
23 | or in effects) and/or it's children components.
24 |
25 | Read more about [hookstate](typedoc-hookstate-core#hookstate) and [useHookstate](typedoc-hookstate-core#usehookstate) in the [API reference](typedoc-hookstate-core).
26 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "peacock.color": "#42b883",
3 | "workbench.colorCustomizations": {
4 | "activityBar.background": "#65c89b",
5 | "activityBar.activeBackground": "#65c89b",
6 | "activityBar.activeBorder": "#945bc4",
7 | "activityBar.foreground": "#15202b",
8 | "activityBar.inactiveForeground": "#15202b99",
9 | "activityBarBadge.background": "#945bc4",
10 | "activityBarBadge.foreground": "#e7e7e7",
11 | "titleBar.activeBackground": "#42b883",
12 | "titleBar.inactiveBackground": "#42b88399",
13 | "titleBar.activeForeground": "#15202b",
14 | "titleBar.inactiveForeground": "#15202b99",
15 | "statusBar.background": "#42b883",
16 | "statusBarItem.hoverBackground": "#359268",
17 | "statusBar.foreground": "#15202b",
18 | "sash.hoverBorder": "#65c89b",
19 | "statusBarItem.remoteBackground": "#42b883",
20 | "statusBarItem.remoteForeground": "#15202b",
21 | "commandCenter.border": "#15202b99"
22 | },
23 | "peacock.remoteColor": "#42b883",
24 | "cSpell.words": [
25 | "core",
26 | "fullfilled",
27 | "hookstate",
28 | "unmount",
29 | "untracked"
30 | ],
31 | "jest.jestCommandLine": "pnpm jest"
32 | }
--------------------------------------------------------------------------------
/docs/index/src/examples/plugin-broadcasted.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHookstate } from '@hookstate/core';
3 | import { broadcasted } from '@hookstate/broadcasted';
4 |
5 | export const ExampleComponent = () => {
6 | const state = useHookstate([{ name: 'First Task' }],
7 | broadcasted({
8 | // topic is optional,
9 | // if it is not defined, the extension requires and
10 | // uses the identifier from the @hookstate/identifiable
11 | topic: 'my-sync-channel-topic',
12 | onLeader: () => { // optional
13 | window.console.log('This tab is a leader now')
14 | // attach persistence, remote synchronization plugins,
15 | // or any other actions which needs to be done with a state
16 | // only by one tab
17 | }
18 | }))
19 |
20 | return <>
21 | {state.map((taskState, taskIndex) => {
22 | return
23 | taskState.name.set(e.target.value)}
26 | />
27 |
28 | })}
29 | state.merge([{ name: 'Untitled' }])}>
30 | Add task
31 |
32 | >
33 | }
34 |
--------------------------------------------------------------------------------
/docs/demos/strictmode/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/strictmode",
3 | "version": "4.0.0-rc3",
4 | "private": true,
5 | "dependencies": {
6 | "@hookstate/core": "workspace:*",
7 | "@testing-library/jest-dom": "^5.16.4",
8 | "@testing-library/react": "^13.3.0",
9 | "@testing-library/user-event": "^14.2.1",
10 | "@types/jest": "^28.1.4",
11 | "@types/node": "^18.0.3",
12 | "@types/react": "^18.0.15",
13 | "@types/react-dom": "^18.0.6",
14 | "npm-check-updates": "15.2.1",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-scripts": "5.0.1",
18 | "serve": "14.1.2",
19 | "typescript": "^4.7.4",
20 | "web-vitals": "^2.1.4"
21 | },
22 | "scripts": {
23 | "start": "react-scripts start",
24 | "build": "react-scripts build",
25 | "serve": "serve -s build",
26 | "test": "react-scripts test",
27 | "eject": "react-scripts eject",
28 | "update:deps": "ncu -u"
29 | },
30 | "eslintConfig": {
31 | "extends": [
32 | "react-app",
33 | "react-app/jest"
34 | ]
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | }
48 | }
--------------------------------------------------------------------------------
/docs/index/src/examples/with-use-effect.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHookstate } from '@hookstate/core';
3 |
4 | export const ExampleComponent = () => {
5 | const state = useHookstate<{ field1: boolean, field2: boolean, field3?: {} }>({
6 | field1: false,
7 | field2: false,
8 | field3: undefined
9 | });
10 | React.useEffect(() => {
11 | console.log('#1 effect called')
12 | if (state.field1.value && state.field3.value) {
13 | console.log('#1 effect called: if => true')
14 | }
15 | }, [state.field1, state.field3])
16 |
17 | React.useEffect(() => {
18 | console.log('#2 effect called')
19 | if (state.field2.value && state.field3.value) {
20 | console.log('#2 effect called: if => true')
21 | }
22 | }, [state.field2, state.field3])
23 |
24 | let hide = { stealth: true }
25 | console.log(`rendered: field1: ${state.field1.get(hide)}, field2: ${state.field2.get(hide)}, field3: ${state.field3.get(hide)}`)
26 |
27 | return <>
28 | Checkout console logs when the effect callback runs
29 | state.field1.set(p => !p)}>Invert field1
30 | state.field2.set(p => !p)}>Invert field2
31 | state.field3.set(p => p ? undefined : {})}>Invert field3
32 | >
33 | }
--------------------------------------------------------------------------------
/docs/index/.netlify-redirects:
--------------------------------------------------------------------------------
1 | # The following is neccessary to reduce site duplication and improve SEO result
2 | # Redirect default Netlify subdomain to primary domain
3 | https://hookstate.netlify.com/* https://hookstate.js.org/:splat 301!
4 |
5 | # version.json is cached in the bundle, but checkline path is unknown for the bundle
6 | # this path is used by the app to check internet connectivity
7 | /checkline /version.json 200
8 |
9 | /demo-todolist/* /docs/getting-started 200
10 | /demo/* /docs/getting-started 200
11 |
12 | # route old code sample links:
13 | /global-getting-started /docs/global-state
14 | /global-getting-started-interface /docs/exporting-state
15 | /local-getting-started /docs/local-state
16 | /local-complex-from-documentation /docs/scoped-state
17 | /local-async-state /docs/asynchronous-state
18 | /local-complex-tree-structure /docs/recursive-state
19 | /performance-demo-large-table /docs/performance-frequent-updates
20 | /performance-demo-large-form /docs/performance-large-state
21 | /global-multiple-consumers-statefragment /docs/using-without-statehook
22 | /plugin-initial /docs/extensions-snapshotable
23 | /plugin-initial-statefragment /docs/extensions-snapshotable
24 | /plugin-touched /docs/extensions-snapshotable
25 | /plugin-persistence /docs/extensions-persistence
26 | /docs/extensions-persistence /docs/extensions-localstored
27 | /plugin-validation /docs/extensions-validation
28 | /plugin-untracked /docs/extensions
29 | /docs/extensions-touched /docs/extensions
30 |
31 | # the application handles routes internally, including non-existing pages
32 | /* /index.html 200
33 |
--------------------------------------------------------------------------------
/docs/index/sidebars.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | module.exports = {
9 | someSidebar: {
10 | Introduction: [
11 | 'getting-started',
12 | 'migrating-to-v4',
13 | 'global-state',
14 | 'local-state',
15 | 'nested-state',
16 | 'scoped-state',
17 | 'nullable-state',
18 | 'asynchronous-state',
19 | 'recursive-state',
20 | 'exporting-state',
21 | 'state-with-non-json-objects',
22 | 'state-with-useeffect',
23 | 'state-without-usestate',
24 | 'server-side-rendering',
25 | ],
26 | Performance: [
27 | 'performance-intro',
28 | 'performance-large-state',
29 | 'performance-frequent-updates',
30 | 'performance-batched-updates',
31 | 'performance-preact',
32 | ],
33 | Extensions: [
34 | 'extensions-overview',
35 | 'extensions-validation',
36 | 'extensions-snapshotable',
37 | 'extensions-broadcasted',
38 | 'extensions-localstored',
39 | 'extensions-identifiable',
40 | 'extensions-subscribable',
41 | 'writing-extension'
42 | ],
43 | 'Development Tools': ['devtools'],
44 | 'API Reference': [
45 | 'typedoc-hookstate-core',
46 | 'exceptions'
47 | ],
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/docs/demos/todolist/src/components/SettingsState.ts:
--------------------------------------------------------------------------------
1 | import { hookstate, useHookstate } from '@hookstate/core';
2 | import { devtools } from '@hookstate/devtools';
3 |
4 | const settingsState = hookstate({
5 | isEditableInline: true,
6 | isScopedUpdateEnabled: true,
7 | isHighlightUpdatesEnabled: true
8 | }, devtools({ key: 'settings' }))
9 |
10 | export function useSettingsState() {
11 | const state = useHookstate(settingsState)
12 |
13 | // This function wraps the state by an interface,
14 | // i.e. the state link is not accessible directly outside of this module.
15 | // The state for tasks in TasksState.ts exposes the state directly.
16 | // Both options are valid and you need to use one or another,
17 | // depending on your circumstances. Apply your engineering judgement
18 | // to choose the best option. If unsure, exposing the state directly
19 | // like it is done in the TasksState.ts is a safe bet.
20 | return ({
21 | get isEditableInline() {
22 | return state.isEditableInline.get()
23 | },
24 | toggleEditableInline() {
25 | state.isEditableInline.set(p => !p)
26 | },
27 | get isScopedUpdateEnabled() {
28 | return state.isScopedUpdateEnabled.get()
29 | },
30 | toggleScopedUpdate() {
31 | state.isScopedUpdateEnabled.set(p => !p)
32 | },
33 | get isHighlightUpdateEnabled() {
34 | return state.isHighlightUpdatesEnabled.get()
35 | },
36 | toggleHighlightUpdate() {
37 | state.isHighlightUpdatesEnabled.set(p => !p)
38 | }
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/docs/demos/todolist/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TasksViewer } from './components/TasksViewer';
3 | import { SettingsViewer } from './components/SettingsViewer';
4 | import { TasksTotal } from './components/TasksTotal';
5 |
6 | const App: React.FC = () => {
7 | return (
8 |
9 |
10 |
18 |
19 |
20 | This is
Hookstate demo application.
26 | Source code is on
GitHub .
32 |
33 |
34 |
35 |
37 | Loading initial state asynchronously...
38 |
39 | }>
40 |
41 |
42 |
43 |
44 |
45 |
46 | );
47 | }
48 |
49 | export default App;
50 |
--------------------------------------------------------------------------------
/docs/index/docs/08-using-without-statehook.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: state-without-usestate
3 | title: Using state without useHookstate hook
4 | sidebar_label: Using state without useHookstate
5 | ---
6 |
7 | import { PreviewSample } from '../src/PreviewSample'
8 |
9 | You can use Hookstate without a hook. It is particularly useful for integration with old class-based React components.
10 | It works with [global](./global-state), [local](./local-state), [nested](./nested-state) and [scoped](./scoped-state) states the same way.
11 |
12 | The following example demonstrates how to use a global state without [useHookstate](typedoc-hookstate-core#useHookstate) hook:
13 |
14 |
15 |
16 | And the following components are identical in behavior:
17 |
18 | Functional component:
19 |
20 | ```tsx
21 | const globalState = hookstate('');
22 |
23 | const MyComponent = () => {
24 | const state = useHookstate(globalState);
25 | return state.set(e.target.value)} />;
27 | }
28 | ```
29 |
30 | Functional component without a hook:
31 |
32 | ```tsx
33 | const globalState = hookstate('');
34 |
35 | const MyComponent = () => {
36 | state => state.set(e.target.value)}>
38 | }
39 | ```
40 |
41 | Class-based component:
42 |
43 | ```tsx
44 | const globalState = hookstate('');
45 |
46 | class MyComponent extends React.Component {
47 | render() {
48 | return {
49 | state => state.set(e.target.value)}>
51 | }
52 | }
53 | }
54 | ```
55 |
--------------------------------------------------------------------------------
/core/src/is-shallow-equal.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copied from fbjs is-shallow-equal
3 | */
4 |
5 | const hasOwnProperty = Object.prototype.hasOwnProperty;
6 |
7 | /**
8 | * inlined Object.is polyfill to avoid requiring consumers ship their own
9 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
10 | */
11 | function is(x: any, y: any): boolean {
12 | // SameValue algorithm
13 | if (x === y) { // Steps 1-5, 7-10
14 | // Steps 6.b-6.e: +0 != -0
15 | // Added the nonzero y check to make Flow happy, but it is redundant
16 | return x !== 0 || y !== 0 || 1 / x === 1 / y;
17 | } else {
18 | // Step 6.a: NaN == NaN
19 | return x !== x && y !== y;
20 | }
21 | }
22 |
23 | /**
24 | * Performs equality by iterating through keys on an object and returning false
25 | * when any key has values which are not strictly equal between the arguments.
26 | * Returns true when the values of all keys are strictly equal.
27 | */
28 | export function shallowEqual(objA: any, objB: any): boolean {
29 | if (is(objA, objB)) {
30 | return true;
31 | }
32 |
33 | if (typeof objA !== 'object' || objA === null ||
34 | typeof objB !== 'object' || objB === null) {
35 | return false;
36 | }
37 |
38 | const keysA = Object.keys(objA);
39 | const keysB = Object.keys(objB);
40 |
41 | if (keysA.length !== keysB.length) {
42 | return false;
43 | }
44 |
45 | // Test for A's keys different from B.
46 | for (let i = 0; i < keysA.length; i++) {
47 | if (
48 | !hasOwnProperty.call(objB, keysA[i]) ||
49 | !is(objA[keysA[i]], objB[keysA[i]])
50 | ) {
51 | return false;
52 | }
53 | }
54 |
55 | return true;
56 | }
--------------------------------------------------------------------------------
/docs/demos/todolist/src/components/TasksState.ts:
--------------------------------------------------------------------------------
1 | import { hookstate, useHookstate } from '@hookstate/core';
2 | import { devtools } from '@hookstate/devtools';
3 |
4 | export interface Task {
5 | id: string;
6 | name: string;
7 | done: boolean;
8 | }
9 |
10 | const state = hookstate(new Promise((resolve, reject) => {
11 | // Emulate asynchronous loading of the initial state data.
12 | // The real application would run some fetch request,
13 | // to get the initial data from a server.
14 | setTimeout(() => resolve([
15 | {
16 | id: '1',
17 | name: 'Discover Hookstate',
18 | done: true,
19 | }, {
20 | id: '2',
21 | name: 'Replace Redux by Hookstate',
22 | done: false,
23 | }, {
24 | id: '3',
25 | name: 'Enjoy simpler code and faster application',
26 | done: false,
27 | }
28 | ]), 3000)
29 | }), devtools({ key: 'tasks' }))
30 |
31 | export function useTasksState() {
32 | // This function exposes the state directly.
33 | // i.e. the state is accessible directly outside of this module.
34 | // The state for settings in SettingsState.ts wraps the state by an interface.
35 | // Both options are valid and you need to use one or another,
36 | // depending on your circumstances. Apply your engineering judgement
37 | // to choose the best option. If unsure, exposing the state directly
38 | // like it is done below is a safe bet.
39 | return useHookstate(state)
40 | }
41 |
42 | // for example purposes, let's update the state outside of a React component
43 | setTimeout(() => state[state.length].set({
44 | id: '100',
45 | name: 'Spread few words about Hookstate',
46 | done: false
47 | }), 10000)
--------------------------------------------------------------------------------
/core/replace-in-typedoc.js:
--------------------------------------------------------------------------------
1 | let replace = require('replace')
2 |
3 | replace({
4 | regex: '# @hookstate/core',
5 | replacement: "",
6 | paths: ['dist/typedoc.md'],
7 | recursive: false,
8 | silent: false,
9 | })
10 |
11 | // 'Ƭ [*][*]([A-Za-z0-9]+)[*][*]: [*](.*)[*]' 'Ƭ **$1**: *`$2`*' dist/typedoc.md && replace 'Ƭ [*][*]State[*][*]: [*](.*)[*]' 'Ƭ **State**: *[StateMixin](#interfacesstatemixinmd) & `S extends object` ? `{ readonly [K in keyof Required]: State }` : [StateMethods](#interfacesstatemethodsmd)*' dist/typedoc.md && replace '[(]statemethods.md#\\[self\\][)]' '(#self)' dist/typedoc.md && replace '[(]statemixin.md#\\[self\\][)]' '(#self-1)' dist/typedoc.md && replace '[(]statemixindestroy.md#\\[self\\][)]' '(#self-2)' dist/typedoc.md && replace '# @hookstate/core' '' dist/typedoc.md && replace '' '\n---\nid: typedoc-hookstate-core\ntitle: API @hookstate/core\n---' dist/typedoc.md && replace '\n\n(---)' '$1' dist/typedoc.md && mv dist/typedoc.md ../docs/index/docs/typedoc-hookstate-core.md",
12 |
13 | replace({
14 | regex: '> ',
15 | replacement: "/>",
16 | paths: ['dist/typedoc.md'],
17 | recursive: false,
18 | silent: false,
19 | })
20 |
21 |
22 | replace({
23 | regex: ' ',
24 | replacement: "\n---\nid: typedoc-hookstate-core\ntitle: API @hookstate/core\n---",
25 | paths: ['dist/typedoc.md'],
26 | recursive: false,
27 | silent: false,
28 | })
29 |
30 | replace({
31 | regex: '\n\n(---)',
32 | replacement: "$1",
33 | paths: ['dist/typedoc.md'],
34 | recursive: false,
35 | silent: false,
36 | })
37 |
38 | replace({
39 | regex: '[(][a-zA-Z_]+[.]md#',
40 | replacement: "(#",
41 | paths: ['dist/typedoc.md'],
42 | recursive: false,
43 | silent: false,
44 | })
45 |
--------------------------------------------------------------------------------
/core/README.md:
--------------------------------------------------------------------------------
1 |
2 | Hookstate
3 |
4 |
5 |
6 | The most straightforward, extensible and incredibly fast state management that is based on React state hook.
7 |
8 |
9 |
10 |
11 | Why? •
12 | Docs / Samples •
13 | Demo application •
14 | Extensions •
15 | Release notes
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ## Support
43 |
44 | **Any questions? Just ask by raising a github ticket.**
45 |
--------------------------------------------------------------------------------
/docs/demos/todolist/src/components/TasksTotal.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useTasksState } from './TasksState';
3 | import { useSettingsState } from './SettingsState';
4 |
5 | export function TasksTotal() {
6 | // Use both global stores in the same component.
7 | // Note: in fact, it could be even one state object
8 | // with functions accessing different nested segments of the state data.
9 | // It would perform equally well.
10 | const tasksState = useTasksState();
11 | const settingsState = useSettingsState();
12 |
13 | // This is the trick to obtain different color on every run of this function
14 | var colors = ['#ff0000', '#00ff00', '#0000ff'];
15 | const color = React.useRef(0)
16 | color.current += 1
17 | var nextColor = colors[color.current % colors.length];
18 |
19 | return
24 | {settingsState.isHighlightUpdateEnabled &&
25 |
32 | }
33 | {tasksState.promised ?
34 | <>> :
35 |
40 |
Total tasks: {tasksState.length}
41 |
Done: {tasksState.filter(i => i.done.value).length}
42 |
Remaining: {tasksState.filter(i => !i.done.value).length}
43 |
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/docs/index/docs/05-async-state.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: asynchronous-state
3 | title: Asynchronous state
4 | sidebar_label: Asynchronous state
5 | ---
6 |
7 | import { PreviewSample } from '../src/PreviewSample'
8 |
9 | The root state can be set to a promise value, either as an initial value for [hookstate](typedoc-hookstate-core#hookstate)/[useHookstate](typedoc-hookstate-core#usehookstate) or as a subsequent value via [State.set](typedoc-hookstate-core#set) method.
10 |
11 | ## Checking if state is loading
12 |
13 | While a promise is not resolved or rejected almost any operation will result in an exception. To check if underlying promise is resolved or rejected, use [State.promised](typedoc-hookstate-core#readonly-promised).
14 | To check if underlying promise is rejected, use [State.error](typedoc-hookstate-core#readonly-error). For example:
15 |
16 |
17 |
18 | ## Executing an action when state is loaded
19 |
20 | It is also possible to access the underlying promise and add a value handling on the promise resolution:
21 |
22 | ```tsx
23 | const state = hookstate(new Promise(...));
24 | state.promise.then(() => {})
25 | ```
26 |
27 | ```tsx
28 | const state = hookstate(none);
29 | state.promise.then(() => {})
30 | setTimeout(() => state.set(...), 1000)
31 | ```
32 |
33 | ## Suspending rendering until asynchronous state is loaded
34 |
35 | Suspend is a React 18 feature. Hookstate provides integration with it in 2 ways:
36 | - the [suspend](typedoc-hookstate-core#suspend) function
37 | - and the `suspend` option of the `StateFragment` component.
38 |
39 | Both methods work with local, global and scoped states.
40 |
41 | Example:
42 |
43 | ```tsx
44 | function MyComponent() {
45 | let state = useHookstate(new Promise(...))
46 | return suspend(state) ?? State is loaded: {state.value}
47 | }
48 | ```
49 |
--------------------------------------------------------------------------------
/docs/demos/strictmode/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/docs/demos/todolist/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
18 |
19 |
28 | Hookstate: Example Application
29 |
30 |
31 | You need to enable JavaScript to run this app.
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/docs/index/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/docs",
3 | "version": "4.0.0-rc3",
4 | "private": true,
5 | "scripts": {
6 | "start": "docusaurus start --no-open --host 0.0.0.0",
7 | "build": "docusaurus build && cp .netlify-headers build/_headers && cp .netlify-redirects build/_redirects",
8 | "swizzle": "docusaurus swizzle",
9 | "deploy": "docusaurus deploy",
10 | "update:deps": "ncu -u"
11 | },
12 | "dependencies": {
13 | "@docusaurus/core": "2.0.0-beta.22",
14 | "@docusaurus/preset-classic": "2.0.0-beta.22",
15 | "@hookstate/core": "workspace:*",
16 | "@hookstate/clonable": "workspace:*",
17 | "@hookstate/comparable": "workspace:*",
18 | "@hookstate/initializable": "workspace:*",
19 | "@hookstate/identifiable": "workspace:*",
20 | "@hookstate/serializable": "workspace:*",
21 | "@hookstate/snapshotable": "workspace:*",
22 | "@hookstate/subscribable": "workspace:*",
23 | "@hookstate/localstored": "workspace:*",
24 | "@hookstate/broadcasted": "workspace:*",
25 | "@hookstate/validation": "workspace:*",
26 | "@hookstate/logged": "workspace:*",
27 | "@hookstate/todolist": "workspace:*",
28 | "@material-ui/core": "4.12.4",
29 | "@material-ui/icons": "4.11.3",
30 | "@mdx-js/react": "1.6.22",
31 | "@types/lodash.clonedeep": "4.5.7",
32 | "@types/lodash.isequal": "4.5.6",
33 | "broadcast-channel": "4.13.0",
34 | "classnames": "2.3.1",
35 | "lodash.clonedeep": "4.5.0",
36 | "lodash.isequal": "4.5.0",
37 | "prism-react-renderer": "1.3.5",
38 | "react": "18.2.0",
39 | "react-dom": "18.2.0",
40 | "react-player": "2.10.1",
41 | "clsx": "^1.2.1"
42 | },
43 | "browserslist": {
44 | "production": [
45 | ">0.2%",
46 | "not dead",
47 | "not op_mini all"
48 | ],
49 | "development": [
50 | "last 1 chrome version",
51 | "last 1 firefox version",
52 | "last 1 safari version"
53 | ]
54 | },
55 | "devDependencies": {
56 | "@docusaurus/module-type-aliases": "2.0.0-beta.22",
57 | "@tsconfig/docusaurus": "^1.0.6",
58 | "npm-check-updates": "^15.2.1",
59 | "typescript": "^4.7.4"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/docs/demos/todolist/src/components/SettingsViewer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSettingsState } from './SettingsState';
3 |
4 | export function SettingsViewer() {
5 | const settingsState = useSettingsState();
6 |
7 | return
15 |
16 |
17 | settingsState.toggleEditableInline()}
22 | />
23 |
24 |
25 | edit inline
26 |
27 |
28 |
29 |
30 | settingsState.toggleScopedUpdate()}
35 | />
36 |
37 |
38 | use scoped state
39 |
40 |
41 |
42 |
43 | settingsState.toggleHighlightUpdate()}
48 | />
49 |
50 |
51 | highlight updates
52 |
53 |
54 |
55 | }
--------------------------------------------------------------------------------
/core/src/__tests__/Error.tsx:
--------------------------------------------------------------------------------
1 | import { useHookstate } from '../';
2 |
3 | import { renderHook } from '@testing-library/react-hooks';
4 |
5 | test('error: should not allow set to another state value', async () => {
6 | const state1 = renderHook(() => {
7 | return useHookstate({ prop1: [0, 0] })
8 | });
9 |
10 | const state2 = renderHook(() => {
11 | return useHookstate({ prop2: [0, 0] })
12 | });
13 |
14 | expect(() => {
15 | state2.result.current.prop2.set(p => state1.result.current.get().prop1);
16 | // tslint:disable-next-line: max-line-length
17 | }).toThrow(`Error: HOOKSTATE-102 [path: /prop2]. See https://hookstate.js.org/docs/exceptions#hookstate-102`);
18 | });
19 |
20 | test('error: should not allow create state from another state value', async () => {
21 | const state1 = renderHook(() => {
22 | return useHookstate({ prop1: [0, 0] })
23 | });
24 |
25 | const state2 = renderHook(() => {
26 | return useHookstate(state1.result.current.get().prop1)
27 | })
28 |
29 | expect(state2.result.error?.message)
30 | // tslint:disable-next-line: max-line-length
31 | .toEqual(`Error: HOOKSTATE-101 [path: /]. See https://hookstate.js.org/docs/exceptions#hookstate-101`)
32 | });
33 |
34 | test('error: should not allow create state from another state value (nested)', async () => {
35 | const state1 = renderHook(() => {
36 | return useHookstate({ prop1: [0, 0] })
37 | });
38 |
39 | const state2 = renderHook(() => {
40 | return useHookstate(state1.result.current)
41 | })
42 |
43 | const state3 = renderHook(() => {
44 | return useHookstate(state2.result.current.prop1.get())
45 | })
46 |
47 | expect(state3.result.error?.message)
48 | // tslint:disable-next-line: max-line-length
49 | .toEqual(`Error: HOOKSTATE-101 [path: /]. See https://hookstate.js.org/docs/exceptions#hookstate-101`)
50 | });
51 |
52 | test('error: should not allow serialization of statelink', async () => {
53 | const state1 = renderHook(() => {
54 | return useHookstate({ prop1: [0, 0] })
55 | });
56 |
57 | expect(() => JSON.stringify(state1))
58 | .toThrow('Error: HOOKSTATE-109 [path: /]. See https://hookstate.js.org/docs/exceptions#hookstate-109')
59 | });
60 |
--------------------------------------------------------------------------------
/docs/index/docs/40-devtools-overview.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: devtools
3 | title: Development Tools Overview
4 | sidebar_label: Overview
5 | ---
6 |
7 | ## Setting up
8 |
9 | * Devtools is an extension. Hookstate-4 requires it to be added explicitly to a state, by providing it as a second argument to `hookstate` or `useHookstate` functions.
10 | * If a state does not have `identifiable` extension attached as well, `devtools` extension should be initialized with the `key` option.
11 |
12 | ```tsx
13 | import { devtools } from '@hookstate/devtools'
14 | let state = hookstate(value, devtools({ key: 'my-state-label' }))
15 | ```
16 |
17 | ```tsx
18 | import { identifiable } from '@hookstate/identifiable'
19 | import { devtools } from '@hookstate/devtools'
20 | let state = hookstate(value, extend(identifiable('my-state-label'), devtools()))
21 | ```
22 |
23 | * Install [Chrome browser's extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en) and reload your app.
24 |
25 | There is no impact on performance in production. Development tools are activated only when the browser's extension is opened.
26 |
27 | ## Demo
28 |
29 | [Demo application](https://github.com/avkonst/hookstate/tree/master/docs/demos/todolist) has got DevTools integrated. Try it out!
30 |
31 | ## Set state value from the development tools
32 |
33 | You can set new value for a state at root or at a specific path using the development tools.
34 | Put content for an action in the 'Dispatch' form and click 'Dispatch' button.
35 |
36 | The easiest way to learn the content of a dispatch action is to inspect an action data for the state update, triggered within an application.
37 |
38 | ## Toggle breakpoint on state update
39 |
40 | Trigger a dispatch action from the Redux development tools with the following content:
41 |
42 | ```tsx
43 | {
44 | type: 'BREAKPOINT',
45 | }
46 | ```
47 |
48 | Now any event, which sets a state, will trigger a breakpoint in the browser.
49 |
50 | To disable the breakpoint, repeat the same dispatch action again.
51 |
52 | ## Pausing/Unpausing monitoring
53 |
54 | Click 'Pause recording' button (bottom row) in the development tools.
55 |
56 | ## Persist state on page reload
57 |
58 | Click 'Persist' button (bottom row) in the development tools.
59 |
--------------------------------------------------------------------------------
/docs/index/src/examples/local-complex-tree-structure.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHookstate, State } from '@hookstate/core';
3 |
4 | interface Node { name: string; children?: Node[] }
5 |
6 | export const ExampleComponent = () => {
7 | const state = useHookstate([
8 | {
9 | name: 'Node 1',
10 | children: [
11 | { name: 'Node 1.1' },
12 | { name: 'Node 1.2' }
13 | ]
14 | },
15 | {
16 | name: 'Node 2'
17 | }
18 | ]);
19 | return <>
20 |
21 |
22 | >
23 | }
24 |
25 | function NodeNameEditor(props: { nameState: State }) {
26 | // scoped state is optional for performance
27 | // could have used props.nameState everywhere instead
28 | const state = useHookstate(props.nameState);
29 | return <>
30 |
31 | state.set(e.target.value)}
34 | /> Last render at: {(new Date()).toISOString()}
35 |
36 | >
37 | }
38 |
39 | function NodeListEditor(props: { nodes: State }) {
40 | // scoped state is optional for performance
41 | // could have used props.nodes everywhere instead
42 | const state = useHookstate(props.nodes);
43 | return
44 | {state.ornull && state.ornull.map((nodeState: State
, i) =>
45 |
46 |
47 |
48 |
49 | )}
50 | state.set(nodes => (nodes || []).concat([{ name: 'Untitled' }]))}>
51 | Add Node
52 |
53 |
54 | }
55 |
56 | function JsonDump(props: { state: State }) {
57 | // scoped state is optional for performance
58 | // could have used props.state everywhere instead
59 | const state = useHookstate(props.state);
60 | return
61 | Current state: {JSON.stringify(state.value)}
62 | Last render at: {(new Date()).toISOString()}
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/docs/index/src/examples/plugin-validation.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { State, useHookstate } from '@hookstate/core';
3 | import { Validation, validation } from '@hookstate/validation';
4 |
5 | interface Task { name: string }
6 |
7 | export const ExampleComponent = () => {
8 | const state: State = useHookstate([{ name: 'First Task' }, { name: 'Second Task' }], validation())
9 |
10 | // configure rules
11 | state.validate(tasks => tasks.length >= 3,
12 | 'There should be at least 3 tasks in the list');
13 | state.validate(tasks => tasks.length < 4,
14 | 'There are too many tasks',
15 | 'warning')
16 |
17 | return <>
18 |
19 | Is this task list valid?: {state.valid().toString()}
20 | Is this task list valid (ignoring nested errors)?:
21 | {state.valid({ depth: 1 }).toString()}
22 | What are the errors and warnings?: {JSON.stringify(state.errors(), null, 4)}
23 | What is the first error or warning?: {JSON.stringify(state.firstError(), null, 4)}
24 | What is the first error (ignoring warnings and nested errors)?: {
25 | JSON.stringify(state.firstError(
26 | i => i.severity === 'error', 1), null, 4)}
27 |
28 | {state.map((taskState, taskIndex) => {
29 | // attaching validation to any element in the array applies it to every
30 | taskState.name.validate(
31 | taskName => taskName.length > 0, 'Task name should not be empty')
32 | return
33 | Is this task valid?: {taskState.valid().toString()}
34 | Is the name of the task valid?: {taskState.name.valid().toString()}
35 | This task validation errors and warnings: {JSON.stringify(taskState.errors())}
36 | taskState.name.set(e.target.value)}
39 | />
40 |
41 | })}
42 | state.merge([{ name: '' }])}>
43 | Add task
44 |
45 | >
46 | }
47 |
--------------------------------------------------------------------------------
/plugins/clonable/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/clonable",
3 | "version": "4.0.0",
4 | "description": "Plugin for @hookstate/core to define state cloning capabilities.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "main": "dist/index.js",
17 | "module": "dist/index.es.js",
18 | "jsnext:main": "dist/index.es.js",
19 | "types": "dist/index.d.ts",
20 | "scripts": {
21 | "build": "rollup -c",
22 | "build:w": "rollup -c -w",
23 | "clean": "rimraf dist",
24 | "test": "jest --env=jsdom",
25 | "test:ci": "jest --env=jsdom",
26 | "test:w": "jest --env=jsdom --watch",
27 | "release": "npm publish --access public",
28 | "update:deps": "ncu -u"
29 | },
30 | "dependencies": {},
31 | "peerDependencies": {
32 | "@hookstate/core": "^4.0.0"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "7.18.6",
36 | "@babel/runtime": "7.18.6",
37 | "@hookstate/core": "workspace:*",
38 | "@rollup/plugin-babel": "5.3.1",
39 | "@rollup/plugin-commonjs": "22.0.1",
40 | "@rollup/plugin-node-resolve": "13.3.0",
41 | "@rollup/plugin-url": "7.0.0",
42 | "@testing-library/react": "13.3.0",
43 | "@testing-library/react-hooks": "8.0.1",
44 | "@types/jest": "28.1.4",
45 | "@types/lodash.clonedeep": "4.5.7",
46 | "@types/lodash.isequal": "4.5.6",
47 | "@types/node": "^18.0.3",
48 | "@types/react": "18.0.15",
49 | "@types/react-dom": "18.0.6",
50 | "codecov": "3.8.3",
51 | "coverage": "0.4.1",
52 | "cross-env": "7.0.3",
53 | "jest": "28.1.2",
54 | "jest-environment-jsdom": "28.1.2",
55 | "npm-check-updates": "15.2.1",
56 | "react": "18.2.0",
57 | "react-dom": "18.2.0",
58 | "react-test-renderer": "18.2.0",
59 | "rimraf": "3.0.2",
60 | "rollup": "2.76.0",
61 | "rollup-plugin-peer-deps-external": "2.2.4",
62 | "rollup-plugin-typescript2": "0.32.1",
63 | "tslib": "^2.4.0",
64 | "typescript": "4.7.4"
65 | },
66 | "files": [
67 | "dist"
68 | ]
69 | }
--------------------------------------------------------------------------------
/plugins/comparable/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/comparable",
3 | "version": "4.0.0",
4 | "description": "Plugin for @hookstate/core to enable state values comparison.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "main": "dist/index.js",
17 | "module": "dist/index.es.js",
18 | "jsnext:main": "dist/index.es.js",
19 | "types": "dist/index.d.ts",
20 | "scripts": {
21 | "build": "rollup -c",
22 | "build:w": "rollup -c -w",
23 | "clean": "rimraf dist",
24 | "test": "jest --env=jsdom",
25 | "test:ci": "jest --env=jsdom",
26 | "test:w": "jest --env=jsdom --watch",
27 | "release": "npm publish --access public",
28 | "update:deps": "ncu -u"
29 | },
30 | "dependencies": {},
31 | "peerDependencies": {
32 | "@hookstate/core": "^4.0.0"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "7.18.6",
36 | "@babel/runtime": "7.18.6",
37 | "@hookstate/core": "workspace:*",
38 | "@rollup/plugin-babel": "5.3.1",
39 | "@rollup/plugin-commonjs": "22.0.1",
40 | "@rollup/plugin-node-resolve": "13.3.0",
41 | "@rollup/plugin-url": "7.0.0",
42 | "@testing-library/react": "13.3.0",
43 | "@testing-library/react-hooks": "8.0.1",
44 | "@types/jest": "28.1.4",
45 | "@types/lodash.clonedeep": "4.5.7",
46 | "@types/lodash.isequal": "4.5.6",
47 | "@types/node": "^18.0.3",
48 | "@types/react": "18.0.15",
49 | "@types/react-dom": "18.0.6",
50 | "codecov": "3.8.3",
51 | "coverage": "0.4.1",
52 | "cross-env": "7.0.3",
53 | "jest": "28.1.2",
54 | "jest-environment-jsdom": "28.1.2",
55 | "npm-check-updates": "15.2.1",
56 | "react": "18.2.0",
57 | "react-dom": "18.2.0",
58 | "react-test-renderer": "18.2.0",
59 | "rimraf": "3.0.2",
60 | "rollup": "2.76.0",
61 | "rollup-plugin-peer-deps-external": "2.2.4",
62 | "rollup-plugin-typescript2": "0.32.1",
63 | "tslib": "^2.4.0",
64 | "typescript": "4.7.4"
65 | },
66 | "files": [
67 | "dist"
68 | ]
69 | }
--------------------------------------------------------------------------------
/plugins/validation/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/validation",
3 | "version": "4.0.1",
4 | "description": "Plugin for @hookstate/core to enable validation of data state.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "main": "dist/index.js",
17 | "module": "dist/index.es.js",
18 | "jsnext:main": "dist/index.es.js",
19 | "types": "dist/index.d.ts",
20 | "scripts": {
21 | "build": "rollup -c",
22 | "build:w": "rollup -c -w",
23 | "clean": "rimraf dist",
24 | "test": "jest --env=jsdom",
25 | "test:ci": "jest --env=jsdom",
26 | "test:w": "jest --env=jsdom --watch",
27 | "release": "npm publish --access public",
28 | "update:deps": "ncu -u"
29 | },
30 | "dependencies": {},
31 | "peerDependencies": {
32 | "@hookstate/core": "^4.0.0"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "7.18.6",
36 | "@babel/runtime": "7.18.6",
37 | "@hookstate/core": "workspace:*",
38 | "@rollup/plugin-babel": "5.3.1",
39 | "@rollup/plugin-commonjs": "22.0.1",
40 | "@rollup/plugin-node-resolve": "13.3.0",
41 | "@rollup/plugin-url": "7.0.0",
42 | "@testing-library/react": "13.3.0",
43 | "@testing-library/react-hooks": "8.0.1",
44 | "@types/jest": "28.1.4",
45 | "@types/lodash.clonedeep": "4.5.7",
46 | "@types/lodash.isequal": "4.5.6",
47 | "@types/node": "^18.0.3",
48 | "@types/react": "18.0.15",
49 | "@types/react-dom": "18.0.6",
50 | "codecov": "3.8.3",
51 | "coverage": "0.4.1",
52 | "cross-env": "7.0.3",
53 | "jest": "28.1.2",
54 | "jest-environment-jsdom": "28.1.2",
55 | "npm-check-updates": "15.2.1",
56 | "react": "18.2.0",
57 | "react-dom": "18.2.0",
58 | "react-test-renderer": "18.2.0",
59 | "rimraf": "3.0.2",
60 | "rollup": "2.76.0",
61 | "rollup-plugin-peer-deps-external": "2.2.4",
62 | "rollup-plugin-typescript2": "0.32.1",
63 | "tslib": "^2.4.0",
64 | "typescript": "4.7.4"
65 | },
66 | "files": [
67 | "dist"
68 | ]
69 | }
70 |
--------------------------------------------------------------------------------
/plugins/initializable/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/initializable",
3 | "version": "4.0.0",
4 | "description": "Plugin for @hookstate/core to enable one off initialization callback.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "main": "dist/index.js",
17 | "module": "dist/index.es.js",
18 | "jsnext:main": "dist/index.es.js",
19 | "types": "dist/index.d.ts",
20 | "scripts": {
21 | "build": "rollup -c",
22 | "build:w": "rollup -c -w",
23 | "clean": "rimraf dist",
24 | "test": "jest --env=jsdom",
25 | "test:ci": "jest --env=jsdom",
26 | "test:w": "jest --env=jsdom --watch",
27 | "release": "npm publish --access public",
28 | "update:deps": "ncu -u"
29 | },
30 | "dependencies": {},
31 | "peerDependencies": {
32 | "@hookstate/core": "^4.0.0"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "7.18.6",
36 | "@babel/runtime": "7.18.6",
37 | "@hookstate/core": "workspace:*",
38 | "@rollup/plugin-babel": "5.3.1",
39 | "@rollup/plugin-commonjs": "22.0.1",
40 | "@rollup/plugin-node-resolve": "13.3.0",
41 | "@rollup/plugin-url": "7.0.0",
42 | "@testing-library/react": "13.3.0",
43 | "@testing-library/react-hooks": "8.0.1",
44 | "@types/jest": "28.1.4",
45 | "@types/lodash.clonedeep": "4.5.7",
46 | "@types/lodash.isequal": "4.5.6",
47 | "@types/node": "^18.0.3",
48 | "@types/react": "18.0.15",
49 | "@types/react-dom": "18.0.6",
50 | "codecov": "3.8.3",
51 | "coverage": "0.4.1",
52 | "cross-env": "7.0.3",
53 | "jest": "28.1.2",
54 | "jest-environment-jsdom": "28.1.2",
55 | "npm-check-updates": "15.2.1",
56 | "react": "18.2.0",
57 | "react-dom": "18.2.0",
58 | "react-test-renderer": "18.2.0",
59 | "rimraf": "3.0.2",
60 | "rollup": "2.76.0",
61 | "rollup-plugin-peer-deps-external": "2.2.4",
62 | "rollup-plugin-typescript2": "0.32.1",
63 | "tslib": "^2.4.0",
64 | "typescript": "4.7.4"
65 | },
66 | "files": [
67 | "dist"
68 | ]
69 | }
--------------------------------------------------------------------------------
/plugins/serializable/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/serializable",
3 | "version": "4.0.0",
4 | "description": "Plugin for @hookstate/core to enable state serialization and deserialization.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "main": "dist/index.js",
17 | "module": "dist/index.es.js",
18 | "jsnext:main": "dist/index.es.js",
19 | "types": "dist/index.d.ts",
20 | "scripts": {
21 | "build": "rollup -c",
22 | "build:w": "rollup -c -w",
23 | "clean": "rimraf dist",
24 | "test": "jest --env=jsdom",
25 | "test:ci": "jest --env=jsdom",
26 | "test:w": "jest --env=jsdom --watch",
27 | "release": "npm publish --access public",
28 | "update:deps": "ncu -u"
29 | },
30 | "dependencies": {},
31 | "peerDependencies": {
32 | "@hookstate/core": "^4.0.0"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "7.18.6",
36 | "@babel/runtime": "7.18.6",
37 | "@hookstate/core": "workspace:*",
38 | "@rollup/plugin-babel": "5.3.1",
39 | "@rollup/plugin-commonjs": "22.0.1",
40 | "@rollup/plugin-node-resolve": "13.3.0",
41 | "@rollup/plugin-url": "7.0.0",
42 | "@testing-library/react": "13.3.0",
43 | "@testing-library/react-hooks": "8.0.1",
44 | "@types/jest": "28.1.4",
45 | "@types/lodash.clonedeep": "4.5.7",
46 | "@types/lodash.isequal": "4.5.6",
47 | "@types/node": "^18.0.3",
48 | "@types/react": "18.0.15",
49 | "@types/react-dom": "18.0.6",
50 | "codecov": "3.8.3",
51 | "coverage": "0.4.1",
52 | "cross-env": "7.0.3",
53 | "jest": "28.1.2",
54 | "jest-environment-jsdom": "28.1.2",
55 | "npm-check-updates": "15.2.1",
56 | "react": "18.2.0",
57 | "react-dom": "18.2.0",
58 | "react-test-renderer": "18.2.0",
59 | "rimraf": "3.0.2",
60 | "rollup": "2.76.0",
61 | "rollup-plugin-peer-deps-external": "2.2.4",
62 | "rollup-plugin-typescript2": "0.32.1",
63 | "tslib": "^2.4.0",
64 | "typescript": "4.7.4"
65 | },
66 | "files": [
67 | "dist"
68 | ]
69 | }
--------------------------------------------------------------------------------
/plugins/identifiable/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/identifiable",
3 | "version": "4.0.0",
4 | "description": "Plugin for @hookstate/core to enable state labeling and identification by name.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "main": "dist/index.js",
17 | "module": "dist/index.es.js",
18 | "jsnext:main": "dist/index.es.js",
19 | "types": "dist/index.d.ts",
20 | "scripts": {
21 | "build": "rollup -c",
22 | "build:w": "rollup -c -w",
23 | "clean": "rimraf dist",
24 | "test": "jest --env=jsdom",
25 | "test:ci": "jest --env=jsdom",
26 | "test:w": "jest --env=jsdom --watch",
27 | "release": "npm publish --access public",
28 | "update:deps": "ncu -u"
29 | },
30 | "dependencies": {},
31 | "peerDependencies": {
32 | "@hookstate/core": "^4.0.0"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "7.18.6",
36 | "@babel/runtime": "7.18.6",
37 | "@hookstate/core": "workspace:*",
38 | "@rollup/plugin-babel": "5.3.1",
39 | "@rollup/plugin-commonjs": "22.0.1",
40 | "@rollup/plugin-node-resolve": "13.3.0",
41 | "@rollup/plugin-url": "7.0.0",
42 | "@testing-library/react": "13.3.0",
43 | "@testing-library/react-hooks": "8.0.1",
44 | "@types/jest": "28.1.4",
45 | "@types/lodash.clonedeep": "4.5.7",
46 | "@types/lodash.isequal": "4.5.6",
47 | "@types/node": "^18.0.3",
48 | "@types/react": "18.0.15",
49 | "@types/react-dom": "18.0.6",
50 | "codecov": "3.8.3",
51 | "coverage": "0.4.1",
52 | "cross-env": "7.0.3",
53 | "jest": "28.1.2",
54 | "jest-environment-jsdom": "28.1.2",
55 | "npm-check-updates": "15.2.1",
56 | "react": "18.2.0",
57 | "react-dom": "18.2.0",
58 | "react-test-renderer": "18.2.0",
59 | "rimraf": "3.0.2",
60 | "rollup": "2.76.0",
61 | "rollup-plugin-peer-deps-external": "2.2.4",
62 | "rollup-plugin-typescript2": "0.32.1",
63 | "tslib": "^2.4.0",
64 | "typescript": "4.7.4"
65 | },
66 | "files": [
67 | "dist"
68 | ]
69 | }
--------------------------------------------------------------------------------
/plugins/logged/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/logged",
3 | "version": "4.0.0",
4 | "description": "Plugin for @hookstate/core to enable logging into console when state is created/updated/destroyed.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "main": "dist/index.js",
17 | "module": "dist/index.es.js",
18 | "jsnext:main": "dist/index.es.js",
19 | "types": "dist/index.d.ts",
20 | "scripts": {
21 | "build": "rollup -c",
22 | "build:w": "rollup -c -w",
23 | "clean": "rimraf dist",
24 | "test": "jest --env=jsdom",
25 | "test:ci": "jest --env=jsdom",
26 | "test:w": "jest --env=jsdom --watch",
27 | "release": "npm publish --access public",
28 | "update:deps": "ncu -u"
29 | },
30 | "dependencies": {},
31 | "peerDependencies": {
32 | "@hookstate/core": "^4.0.0"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "7.18.6",
36 | "@babel/runtime": "7.18.6",
37 | "@hookstate/core": "workspace:*",
38 | "@rollup/plugin-babel": "5.3.1",
39 | "@rollup/plugin-commonjs": "22.0.1",
40 | "@rollup/plugin-node-resolve": "13.3.0",
41 | "@rollup/plugin-url": "7.0.0",
42 | "@testing-library/react": "13.3.0",
43 | "@testing-library/react-hooks": "8.0.1",
44 | "@types/jest": "28.1.4",
45 | "@types/lodash.clonedeep": "4.5.7",
46 | "@types/lodash.isequal": "4.5.6",
47 | "@types/node": "^18.0.3",
48 | "@types/react": "18.0.15",
49 | "@types/react-dom": "18.0.6",
50 | "codecov": "3.8.3",
51 | "coverage": "0.4.1",
52 | "cross-env": "7.0.3",
53 | "jest": "28.1.2",
54 | "jest-environment-jsdom": "28.1.2",
55 | "npm-check-updates": "15.2.1",
56 | "react": "18.2.0",
57 | "react-dom": "18.2.0",
58 | "react-test-renderer": "18.2.0",
59 | "rimraf": "3.0.2",
60 | "rollup": "2.76.0",
61 | "rollup-plugin-peer-deps-external": "2.2.4",
62 | "rollup-plugin-typescript2": "0.32.1",
63 | "tslib": "^2.4.0",
64 | "typescript": "4.7.4"
65 | },
66 | "files": [
67 | "dist"
68 | ]
69 | }
--------------------------------------------------------------------------------
/plugins/subscribable/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/subscribable",
3 | "version": "4.0.0",
4 | "description": "Plugin for @hookstate/core to make it easier to subscribe a custom callback to state changes.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "main": "dist/index.js",
17 | "module": "dist/index.es.js",
18 | "jsnext:main": "dist/index.es.js",
19 | "types": "dist/index.d.ts",
20 | "scripts": {
21 | "build": "rollup -c",
22 | "build:w": "rollup -c -w",
23 | "clean": "rimraf dist",
24 | "test": "jest --env=jsdom",
25 | "test:ci": "jest --env=jsdom",
26 | "test:w": "jest --env=jsdom --watch",
27 | "release": "npm publish --access public",
28 | "update:deps": "ncu -u"
29 | },
30 | "dependencies": {},
31 | "peerDependencies": {
32 | "@hookstate/core": "^4.0.0"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "7.18.6",
36 | "@babel/runtime": "7.18.6",
37 | "@hookstate/core": "workspace:*",
38 | "@rollup/plugin-babel": "5.3.1",
39 | "@rollup/plugin-commonjs": "22.0.1",
40 | "@rollup/plugin-node-resolve": "13.3.0",
41 | "@rollup/plugin-url": "7.0.0",
42 | "@testing-library/react": "13.3.0",
43 | "@testing-library/react-hooks": "8.0.1",
44 | "@types/jest": "28.1.4",
45 | "@types/lodash.clonedeep": "4.5.7",
46 | "@types/lodash.isequal": "4.5.6",
47 | "@types/node": "^18.0.3",
48 | "@types/react": "18.0.15",
49 | "@types/react-dom": "18.0.6",
50 | "codecov": "3.8.3",
51 | "coverage": "0.4.1",
52 | "cross-env": "7.0.3",
53 | "jest": "28.1.2",
54 | "jest-environment-jsdom": "28.1.2",
55 | "npm-check-updates": "15.2.1",
56 | "react": "18.2.0",
57 | "react-dom": "18.2.0",
58 | "react-test-renderer": "18.2.0",
59 | "rimraf": "3.0.2",
60 | "rollup": "2.76.0",
61 | "rollup-plugin-peer-deps-external": "2.2.4",
62 | "rollup-plugin-typescript2": "0.32.1",
63 | "tslib": "^2.4.0",
64 | "typescript": "4.7.4"
65 | },
66 | "files": [
67 | "dist"
68 | ]
69 | }
--------------------------------------------------------------------------------
/plugins/localstored/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/localstored",
3 | "version": "4.0.2",
4 | "description": "Plugin for @hookstate/core to enable persistence of managed data states to browser's local storage.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "main": "dist/index.js",
17 | "module": "dist/index.es.js",
18 | "jsnext:main": "dist/index.es.js",
19 | "types": "dist/index.d.ts",
20 | "scripts": {
21 | "build": "rollup -c",
22 | "build:w": "rollup -c -w",
23 | "clean": "rimraf dist",
24 | "test": "jest --env=jsdom",
25 | "test:ci": "jest --env=jsdom",
26 | "test:w": "jest --env=jsdom --watch",
27 | "release": "npm publish --access public",
28 | "update:deps": "ncu -u"
29 | },
30 | "dependencies": {},
31 | "peerDependencies": {
32 | "@hookstate/core": "^4.0.0"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "7.18.6",
36 | "@babel/runtime": "7.18.6",
37 | "@hookstate/core": "workspace:*",
38 | "@rollup/plugin-babel": "5.3.1",
39 | "@rollup/plugin-commonjs": "22.0.1",
40 | "@rollup/plugin-node-resolve": "13.3.0",
41 | "@rollup/plugin-url": "7.0.0",
42 | "@testing-library/react": "13.3.0",
43 | "@testing-library/react-hooks": "8.0.1",
44 | "@types/jest": "28.1.4",
45 | "@types/lodash.clonedeep": "4.5.7",
46 | "@types/lodash.isequal": "4.5.6",
47 | "@types/node": "^18.0.3",
48 | "@types/react": "18.0.15",
49 | "@types/react-dom": "18.0.6",
50 | "codecov": "3.8.3",
51 | "coverage": "0.4.1",
52 | "cross-env": "7.0.3",
53 | "jest": "28.1.2",
54 | "jest-environment-jsdom": "28.1.2",
55 | "npm-check-updates": "15.2.1",
56 | "react": "18.2.0",
57 | "react-dom": "18.2.0",
58 | "react-test-renderer": "18.2.0",
59 | "rimraf": "3.0.2",
60 | "rollup": "2.76.0",
61 | "rollup-plugin-peer-deps-external": "2.2.4",
62 | "rollup-plugin-typescript2": "0.32.1",
63 | "tslib": "^2.4.0",
64 | "typescript": "4.7.4"
65 | },
66 | "files": [
67 | "dist"
68 | ]
69 | }
--------------------------------------------------------------------------------
/plugins/snapshotable/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/snapshotable",
3 | "version": "4.0.0",
4 | "description": "Extension for @hookstate/core to enable state snapshoting and reverting to the snapshoted / previous values.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "main": "dist/index.js",
17 | "module": "dist/index.es.js",
18 | "jsnext:main": "dist/index.es.js",
19 | "types": "dist/index.d.ts",
20 | "scripts": {
21 | "build": "rollup -c",
22 | "build:w": "rollup -c -w",
23 | "clean": "rimraf dist",
24 | "test": "jest --env=jsdom",
25 | "test:ci": "jest --env=jsdom",
26 | "test:w": "jest --env=jsdom --watch",
27 | "release": "npm publish --access public",
28 | "update:deps": "ncu -u"
29 | },
30 | "peerDependencies": {
31 | "@hookstate/core": "^4.0.0"
32 | },
33 | "devDependencies": {
34 | "@babel/core": "7.18.6",
35 | "@babel/runtime": "7.18.6",
36 | "@hookstate/core": "workspace:*",
37 | "@hookstate/clonable": "workspace:*",
38 | "@hookstate/comparable": "workspace:*",
39 | "@rollup/plugin-babel": "5.3.1",
40 | "@rollup/plugin-commonjs": "22.0.1",
41 | "@rollup/plugin-node-resolve": "13.3.0",
42 | "@rollup/plugin-url": "7.0.0",
43 | "@testing-library/react": "13.3.0",
44 | "@testing-library/react-hooks": "8.0.1",
45 | "@types/jest": "28.1.4",
46 | "@types/node": "^18.0.3",
47 | "@types/react": "18.0.15",
48 | "@types/react-dom": "18.0.6",
49 | "codecov": "3.8.3",
50 | "coverage": "0.4.1",
51 | "cross-env": "7.0.3",
52 | "jest": "28.1.2",
53 | "jest-environment-jsdom": "28.1.2",
54 | "npm-check-updates": "15.2.1",
55 | "react": "18.2.0",
56 | "react-dom": "18.2.0",
57 | "react-test-renderer": "18.2.0",
58 | "rimraf": "3.0.2",
59 | "rollup": "2.76.0",
60 | "rollup-plugin-peer-deps-external": "2.2.4",
61 | "rollup-plugin-typescript2": "0.32.1",
62 | "ts-jest": "28.0.5",
63 | "tslib": "^2.4.0",
64 | "typescript": "4.7.4"
65 | },
66 | "files": [
67 | "dist"
68 | ]
69 | }
--------------------------------------------------------------------------------
/plugins/broadcasted/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/broadcasted",
3 | "version": "4.0.0",
4 | "description": "Plugin for @hookstate/core to enable state synchronization across browser tabs.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "main": "dist/index.js",
17 | "module": "dist/index.es.js",
18 | "jsnext:main": "dist/index.es.js",
19 | "types": "dist/index.d.ts",
20 | "scripts": {
21 | "build": "rollup -c",
22 | "build:w": "rollup -c -w",
23 | "clean": "rimraf dist",
24 | "test": "jest --env=jsdom",
25 | "test:ci": "jest --env=jsdom",
26 | "test:w": "jest --env=jsdom --watch",
27 | "release": "npm publish --access public",
28 | "update:deps": "ncu -u"
29 | },
30 | "dependencies": {},
31 | "peerDependencies": {
32 | "@hookstate/core": "^4.0.0",
33 | "broadcast-channel": "^4.10.0"
34 | },
35 | "devDependencies": {
36 | "@babel/core": "7.18.6",
37 | "@babel/runtime": "7.18.6",
38 | "@hookstate/core": "workspace:*",
39 | "@rollup/plugin-babel": "5.3.1",
40 | "@rollup/plugin-commonjs": "22.0.1",
41 | "@rollup/plugin-node-resolve": "13.3.0",
42 | "@rollup/plugin-url": "7.0.0",
43 | "@testing-library/react": "13.3.0",
44 | "@testing-library/react-hooks": "8.0.1",
45 | "@types/jest": "28.1.4",
46 | "@types/lodash.clonedeep": "4.5.7",
47 | "@types/lodash.isequal": "4.5.6",
48 | "@types/node": "^18.0.3",
49 | "@types/react": "18.0.15",
50 | "@types/react-dom": "18.0.6",
51 | "broadcast-channel": "4.13.0",
52 | "codecov": "3.8.3",
53 | "coverage": "0.4.1",
54 | "cross-env": "7.0.3",
55 | "jest": "28.1.2",
56 | "jest-environment-jsdom": "28.1.2",
57 | "npm-check-updates": "15.2.1",
58 | "react": "18.2.0",
59 | "react-dom": "18.2.0",
60 | "react-test-renderer": "18.2.0",
61 | "rimraf": "3.0.2",
62 | "rollup": "2.76.0",
63 | "rollup-plugin-peer-deps-external": "2.2.4",
64 | "rollup-plugin-typescript2": "0.32.1",
65 | "tslib": "^2.4.0",
66 | "typescript": "4.7.4"
67 | },
68 | "files": [
69 | "dist"
70 | ]
71 | }
--------------------------------------------------------------------------------
/docs/demos/strictmode/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/index/docs/04c-nullable-state.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: nullable-state
3 | title: Nullable state
4 | sidebar_label: Nullable state
5 | ---
6 |
7 | import { PreviewSample } from '../src/PreviewSample'
8 |
9 | ## Dealing with nullable state
10 |
11 | If a state can be missing (e.g. nested property is `undefined`) or `null`, checking for null state value is essential before diving into the nested states.
12 |
13 | Typescript will fail a compilation if you attempt to work with nested states of a state, which might have `null`/`undefined` state value. For example:
14 |
15 | ```tsx
16 | interface Task { name: string, priority?: number }
17 |
18 | const MyComponent = () => {
19 | const state = useHookstate(null)
20 |
21 | // JS - runtime error, TS - compilation error
22 | state.name.value
23 | // JS - runtime error, TS - compilation error
24 | state.value.name
25 | }
26 | ```
27 |
28 | Here is the recommended way to check for `null`/`undefined` before unfolding nested states:
29 |
30 | ```tsx
31 | // type is for clarity, it is inferred by the compiler
32 | const stateOrNull: State | null = state.ornull
33 | if (stateOrNull) {
34 | // neither compilation nor runtime errors
35 | stateOrNull.name.value
36 |
37 | // neither compilation nor runtime errors
38 | stateOrNull.value.name
39 | }
40 | ```
41 |
42 | [State.ornull](typedoc-hookstate-core.md#ornull) property is a very convenient way to deal in those cases. Here is an example of a component, which receives a state whose value might be `null`.
43 |
44 | ```tsx
45 | const MyInputField = (props: { state: State}) => {
46 | const state: State | null = props.state.ornull;
47 | // state is either null or an instance of State:
48 | if (!state) {
49 | // state value was null, do not render form field
50 | return <>>;
51 | }
52 | // state value is an instance of string, can not be null here:
53 | return state.set(v.target.value)} />
54 | }
55 | ```
56 |
57 | [State.ornull](typedoc-hookstate-core.md#ornull) property is just a convenience. Traditional `||` may also work depending on a case. Here is an example of a component, which receives a state whose value might be `null`, but still proceeds with rendering 'state editor':
58 |
59 | ```tsx
60 | const MyInputField = (props: { state: State}) => {
61 | // state value is an instance of string or null here:
62 | return state.set(v.target.value)} />
63 | }
64 | ```
65 |
--------------------------------------------------------------------------------
/plugins/devtools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/devtools",
3 | "version": "4.0.3",
4 | "description": "Development tools plugin for @hookstate/core.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "main": "dist/index.js",
17 | "module": "dist/index.es.js",
18 | "jsnext:main": "dist/index.es.js",
19 | "types": "dist/index.d.ts",
20 | "scripts": {
21 | "build": "rollup -c",
22 | "build:w": "rollup -c -w",
23 | "clean": "rimraf dist",
24 | "test": "jest --env=jsdom",
25 | "test:ci": "jest --env=jsdom",
26 | "test:w": "jest --env=jsdom --watch",
27 | "release": "npm publish --access public",
28 | "update:deps": "ncu -u"
29 | },
30 | "dependencies": {
31 | "redux": "4.2.0",
32 | "redux-devtools-extension": "2.13.9"
33 | },
34 | "peerDependencies": {
35 | "@hookstate/core": "^4.0.0"
36 | },
37 | "devDependencies": {
38 | "@babel/core": "7.18.6",
39 | "@babel/runtime": "7.18.6",
40 | "@hookstate/core": "workspace:*",
41 | "@rollup/plugin-babel": "5.3.1",
42 | "@rollup/plugin-commonjs": "22.0.1",
43 | "@rollup/plugin-node-resolve": "13.3.0",
44 | "@rollup/plugin-url": "7.0.0",
45 | "@testing-library/react": "13.3.0",
46 | "@testing-library/react-hooks": "8.0.1",
47 | "@types/jest": "28.1.4",
48 | "@types/lodash.clonedeep": "4.5.7",
49 | "@types/lodash.isequal": "4.5.6",
50 | "@types/node": "^18.0.3",
51 | "@types/react": "18.0.15",
52 | "@types/react-dom": "18.0.6",
53 | "codecov": "3.8.3",
54 | "coverage": "0.4.1",
55 | "cross-env": "7.0.3",
56 | "jest-mock-console": "^2.0.0",
57 | "jest": "28.1.2",
58 | "jest-environment-jsdom": "28.1.2",
59 | "npm-check-updates": "15.2.1",
60 | "react": "18.2.0",
61 | "react-dom": "18.2.0",
62 | "react-test-renderer": "18.2.0",
63 | "rimraf": "3.0.2",
64 | "rollup": "2.76.0",
65 | "rollup-plugin-peer-deps-external": "2.2.4",
66 | "rollup-plugin-typescript2": "0.32.1",
67 | "ts-jest": "28.0.5",
68 | "tslib": "^2.4.0",
69 | "typescript": "4.7.4"
70 | },
71 | "files": [
72 | "dist"
73 | ]
74 | }
75 |
--------------------------------------------------------------------------------
/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hookstate/core",
3 | "version": "4.0.2",
4 | "description": "The flexible, fast and extendable state management for React that is based on hooks and state usage tracking.",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Andrey Konstantinov"
8 | },
9 | "repository": {
10 | "url": "https://github.com/avkonst/hookstate"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/avkonst/hookstate/issues"
14 | },
15 | "homepage": "https://github.com/avkonst/hookstate",
16 | "main": "dist/index.js",
17 | "module": "dist/index.es.js",
18 | "jsnext:main": "dist/index.es.js",
19 | "types": "dist/index.d.ts",
20 | "scripts": {
21 | "build": "rollup -c",
22 | "build:w": "rollup -c -w",
23 | "build:docs": "typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --tsconfig ./tsconfig.json --exclude \"dist/**.js\" --gitRevision master --excludeExternals --categorizeByGroup false --readme none --hideGenerator --out dist/docs ./src/index.ts && concat-md --decrease-title-levels --dir-name-as-title dist/docs > dist/typedoc.md && rimraf dist/docs && node ./replace-in-typedoc.js && mv dist/typedoc.md ../docs/index/docs/typedoc-hookstate-core.md",
24 | "clean": "rimraf dist",
25 | "test": "jest --env=jsdom",
26 | "test:ci": "jest --env=jsdom --coverage && codecov -e TRAVIS_NODE_VERSION",
27 | "test:w": "jest --env=jsdom --watch",
28 | "release": "npm publish --access public",
29 | "update:deps": "ncu -u"
30 | },
31 | "peerDependencies": {
32 | "react": "^16.8.6 || ^17.0.0 || ^18.0.0"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "7.18.6",
36 | "@babel/runtime": "7.18.6",
37 | "@rollup/plugin-babel": "5.3.1",
38 | "@rollup/plugin-commonjs": "22.0.1",
39 | "@rollup/plugin-node-resolve": "13.3.0",
40 | "@rollup/plugin-url": "7.0.0",
41 | "@testing-library/react": "13.3.0",
42 | "@testing-library/react-hooks": "8.0.1",
43 | "@types/jest": "28.1.4",
44 | "@types/node": "^18.0.3",
45 | "@types/react": "18.0.15",
46 | "@types/react-dom": "18.0.6",
47 | "codecov": "3.8.3",
48 | "concat-md": "0.4.0",
49 | "coverage": "0.4.1",
50 | "cross-env": "7.0.3",
51 | "jest": "28.1.2",
52 | "jest-environment-jsdom": "28.1.2",
53 | "npm-check-updates": "15.2.1",
54 | "react": "18.2.0",
55 | "react-dom": "18.2.0",
56 | "react-test-renderer": "18.2.0",
57 | "replace": "1.2.1",
58 | "rimraf": "3.0.2",
59 | "rollup": "2.76.0",
60 | "rollup-plugin-peer-deps-external": "2.2.4",
61 | "rollup-plugin-typescript2": "0.32.1",
62 | "ts-jest": "28.0.5",
63 | "tslib": "^2.4.0",
64 | "typedoc": "0.23.6",
65 | "typedoc-plugin-markdown": "3.13.3",
66 | "typescript": "4.7.4"
67 | },
68 | "files": [
69 | "dist"
70 | ]
71 | }
72 |
--------------------------------------------------------------------------------
/docs/index/src/examples/Index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { ExampleComponent as ExampleGlobalPrimitive } from './global-getting-started';
4 | import { ExampleComponent as ExampleGlobalPrimitiveInterface } from './global-getting-started-interface';
5 | import { ExampleComponent as ExampleLocalPrimitive } from './local-getting-started';
6 | import { ExampleComponent as ExampleAsyncState } from './local-async-state';
7 | import { ExampleComponent as ExampleLocalComplexFromDocumentation } from './local-complex-from-documentation';
8 | import { ExampleComponent as ExampleLocalComplexTreeStructure } from './local-complex-tree-structure';
9 | import { ExampleComponent as ExamplePerformanceLargeForm } from './performance-demo-large-form';
10 | import { ExampleComponent as ExampleGlobalMultipleConsumersStateFragment } from './global-multiple-consumers-statefragment';
11 |
12 | import { ExampleComponent as ExamplePluginIdentifiable } from './plugin-identifiable';
13 | import { ExampleComponent as ExamplePluginSubscribable } from './plugin-subscribable';
14 | import { ExampleComponent as ExamplePluginSnapshotable } from './plugin-snapshotable';
15 | import { ExampleComponent as ExamplePluginLocalstored } from './plugin-localstored';
16 | import { ExampleComponent as ExamplePluginBroadcasted } from './plugin-broadcasted';
17 | import { ExampleComponent as ExamplePluginValidation } from './plugin-validation';
18 | import { ExampleComponent as ExamplePluginCustom } from './plugin-custom';
19 |
20 | import { ExampleComponent as ExampleWithUseEffect } from './with-use-effect';
21 |
22 | const baseUrl = 'https://raw.githubusercontent.com/avkonst/hookstate/master/docs/index/src/examples/'
23 |
24 | export const ExampleCodeUrl = (id: string) => `${baseUrl}${id}.tsx`;
25 |
26 | export const ExamplesRepo: Map = new Map();
27 | ExamplesRepo.set('global-getting-started', );
28 | ExamplesRepo.set('global-getting-started-interface', );
29 | ExamplesRepo.set('local-getting-started', );
30 | ExamplesRepo.set('local-complex-from-documentation', );
31 | ExamplesRepo.set('local-async-state', );
32 | ExamplesRepo.set('local-complex-tree-structure', );
33 | ExamplesRepo.set('performance-demo-large-form', );
34 | ExamplesRepo.set('global-multiple-consumers-statefragment', );
35 |
36 | ExamplesRepo.set('plugin-identifiable', );
37 | ExamplesRepo.set('plugin-subscribable', );
38 | ExamplesRepo.set('plugin-snapshotable', );
39 | ExamplesRepo.set('plugin-validation', );
40 | ExamplesRepo.set('plugin-localstored', );
41 | ExamplesRepo.set('plugin-broadcasted', );
42 |
43 | ExamplesRepo.set('plugin-custom', );
44 |
45 | ExamplesRepo.set('with-use-effect', );
46 |
--------------------------------------------------------------------------------
/plugins/logged/src/logged.ts:
--------------------------------------------------------------------------------
1 | import { ExtensionFactory, State, StateValueAtPath } from '@hookstate/core';
2 |
3 | export interface Logged {
4 | log(): void
5 | }
6 |
7 | export function logged(options?: {
8 | identifier?: string,
9 | logger?: (...args: any[]) => void
10 | }): ExtensionFactory {
11 | return () => {
12 | let identifier: string;
13 | let serializer: (s: State) => (opts?: { stealth?: boolean }) => string;
14 | let logger = options?.logger ?? console.log
15 | return {
16 | onCreate: () => ({
17 | log: (s) => () => {
18 | if (s.promise) {
19 | logger(`[hookstate][${identifier}] logged: promised`)
20 | } else {
21 | logger(`[hookstate][${identifier}] logged: ${serializer(s)({ stealth: true })}`)
22 | }
23 | }
24 | }),
25 | onInit: (state, extensionMethods) => {
26 | if (options?.identifier === undefined) {
27 | if (extensionMethods['identifier'] === undefined) {
28 | identifier = 'untitled'
29 | } else {
30 | identifier = extensionMethods['identifier'](state)
31 | }
32 | } else {
33 | identifier = options.identifier
34 | }
35 | if (extensionMethods['serialize'] !== undefined) {
36 | serializer = extensionMethods['serialize']
37 | } else {
38 | serializer = (s) => (opts) => JSON.stringify(s.get({ noproxy: true, stealth: opts?.stealth }))
39 | }
40 | if (state.promise) {
41 | logger(`[hookstate][${identifier}] initialized: promised`)
42 | } else {
43 | logger(`[hookstate][${identifier}] initialized: ${serializer(state)({ stealth: true })}`)
44 | }
45 | },
46 | onSet: (s, d) => {
47 | if (s.promised) {
48 | logger(`[hookstate][${identifier}] set to: promised`)
49 | } else if (s.error !== undefined) {
50 | logger(`[hookstate][${identifier}] set to: error: ${s.error}`)
51 | } else {
52 | if (d.actions) {
53 | logger(`[hookstate][${identifier}] set at path ${s.path} to: ${serializer(s)({ stealth: true })} with update actions: ${JSON.stringify(d)}`)
54 | } else {
55 | logger(`[hookstate][${identifier}] set at path ${s.path} to: ${serializer(s)({ stealth: true })}`)
56 | }
57 | }
58 | },
59 | onDestroy: (s) => {
60 | if (s.promise) {
61 | logger(`[hookstate][${identifier}] destroyed: promised`)
62 | } else {
63 | logger(`[hookstate][${identifier}] destroyed: ${serializer(s)({ stealth: true })}`)
64 | }
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------