diff --git a/example/index.tsx b/example/index.tsx
index 8c01a0c..c94a498 100644
--- a/example/index.tsx
+++ b/example/index.tsx
@@ -1,8 +1,10 @@
// @ts-ignore
import React, { ChangeEvent, useContext, useEffect } from "react"
import {
+ createReactive,
createSubscription,
ISubscription,
+ useReactive,
useReducerSubscription,
useSubscription,
} from "../src/index"
@@ -150,6 +152,34 @@ function Counter2() {
)
}
+
+const sourceOfTruth = createReactive({
+ text1: 'Text 1 sync together',
+ text2: 'Text 2 walk alone.'
+})
+
+const Text1 = () => {
+ const state = useReactive(sourceOfTruth, ['text1'])
+ return state.text1 = e.target.value} />
+}
+const Text2 = () => {
+ const state = useReactive(sourceOfTruth, ['text2'])
+ return state.text2 = e.target.value} />
+}
+
+const ReactiveApp = () => {
+
+ return
+
Reactive pattern:
+
+ <>
+
+
+
+ >
+
+}
+
function App() {
return (
<>
@@ -159,6 +189,7 @@ function App() {
+
>
)
}
diff --git a/package.json b/package.json
index f4a788c..b3e49d0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "global-state-hook",
- "version": "1.6.0",
+ "version": "2.0.0",
"description": "Super tiny state sharing library with hooks",
"source": "src/index.ts",
"main": "dist/index.js",
@@ -25,27 +25,14 @@
},
"license": "MIT",
"devDependencies": {
- "@types/react": "^16.9.48",
- "@types/react-dom": "^16.9.8",
- "husky": "^4.2.5",
- "lint-staged": "^10.2.13",
- "microbundle": "^0.12.3",
+ "@types/react": "^16.14.8",
+ "@types/react-dom": "^16.9.13",
+ "microbundle": "^0.13.1",
"parcel": "^1.12.4",
- "prettier": "^2.1.1",
- "react": "^16.13.1",
- "react-dom": "^16.13.1",
- "ts-node": "^9.0.0",
- "typescript": "^4.0.2"
- },
- "husky": {
- "hooks": {
- "pre-commit": "lint-staged"
- }
- },
- "lint-staged": {
- "*.{js,jsx,ts,tsx}": [
- "prettier --write",
- "git add"
- ]
+ "prettier": "^2.3.0",
+ "react": "^16.14.0",
+ "react-dom": "^16.14.0",
+ "ts-node": "^9.1.1",
+ "typescript": "^4.3.2"
}
}
diff --git a/src/index.ts b/src/index.ts
index 143149d..47de31e 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -3,29 +3,49 @@ import React from "react"
type Listener = (newState: S) => void
export interface ISubscription {
+ __updateState?: (nextState: any, forceUpdate?: boolean) => void
subscribe: (fn: Listener) => void
unsubscribe: (fn: Listener) => void
listener: Set>
state: S
- updateState: (nextState: S, forceUpdate?: boolean) => void
+ updateState: (nextState: S | any, forceUpdate?: boolean) => void
+ [key: string]: any
+}
+export interface IReactive {
+ subscribe: (fn: Listener) => void
+ unsubscribe: (fn: Listener) => void
+ listener: Set>
+ store: S
}
-export function createSubscription(
- initialState?: S,
+export function createSubscription(
+ initialState?: S
): ISubscription {
- const state: S = initialState || ({} as any)
+ let state: S = initialState || ({} as any)
let listener: Set> = new Set()
const subscribe = (fn: Listener) => {
listener.add(fn)
}
- const unsubscribe = (fn: Listener) => listener.delete(fn)
+ const unsubscribe = (fn: Listener) =>
+ listener.delete(fn)
const updateState = (nextState: S, forceUpdate = true) => {
- Object.assign(state, nextState)
+ state = { ...state, ...nextState }
if (forceUpdate) {
- listener.forEach((fn) => fn(nextState))
+ listener.forEach(fn => fn(nextState))
}
}
- return { subscribe, unsubscribe, listener, state, updateState }
+ return {
+ subscribe,
+ unsubscribe,
+ listener,
+ get state() {
+ return state
+ },
+ set state(nextState) {
+ state = nextState
+ },
+ updateState
+ }
}
export interface IStateUpdater {
@@ -41,29 +61,27 @@ export interface IStateReduceUpdater {
function useSubscriber(
subscriber: ISubscription,
- pick?: string[],
+ pick?: string[]
) {
+ if (!subscriber) {
+ console.trace('Missing subscriber!!')
+ }
const [changed, setUpdate] = React.useState({})
- // const mounted = React.useRef(true)
- const updater = React.useCallback(
- (nextState: S) => {
- if (
- !pick ||
+ const updater = React.useCallback((nextState: S) => {
+ if (subscriber &&
+ (!pick ||
!pick.length ||
- typeof nextState !== "object" ||
- nextState.constructor !== Object ||
- Object.keys(nextState).find((k) => pick.includes(k))
- ) {
- setUpdate({})
- }
- },
- [pick],
- )
+ Object.keys(nextState).find((k) => pick.includes(k)))
+ ) {
+ setUpdate({})
+ }
+ }, [pick])
React.useEffect(() => {
- subscriber.subscribe(updater)
- return () => {
- subscriber.unsubscribe(updater)
+ if (subscriber) {
+ subscriber.subscribe(updater)
+ return () => subscriber.unsubscribe(updater)
}
+ return
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return changed
@@ -71,43 +89,83 @@ function useSubscriber(
export function useReducerSubscription(
subscriber: ISubscription,
- reducer: any = () => {},
+ reducer: any = () => {
+ }
): IStateReduceUpdater {
useSubscriber(subscriber)
const dispatch = (...args: any) => {
const newState = reducer(subscriber.state, ...args)
subscriber.state = Object.assign({}, subscriber.state, newState)
- subscriber.listener.forEach((fn) => fn(newState))
+ subscriber.listener.forEach(fn => fn(newState))
}
React.useDebugValue(subscriber.state)
- return { state: subscriber.state, dispatch }
+ return { state: subscriber?.state, dispatch }
}
export function useSubscription(
subscriber: ISubscription,
- pick?: string[],
+ pick?: string[]
): IStateUpdater {
const changed = useSubscriber(subscriber, pick)
- React.useDebugValue(subscriber.state)
+ React.useDebugValue(subscriber?.state)
+
return {
changed,
- state: subscriber.state,
+ state: subscriber?.state,
setState: React.useCallback(
(newState: any, callback?: Function) => {
- if (typeof newState === "object" && newState.constructor === Object) {
+ if (!subscriber) {
+ return
+ }
+ if (typeof newState === 'object' && newState.constructor === Object) {
subscriber.state = Object.assign({}, subscriber.state, newState)
- } else if (typeof newState === "function") {
+ } else if (typeof newState === 'function') {
const nextState = newState(subscriber.state)
subscriber.state = Object.assign({}, subscriber.state, nextState)
} else {
subscriber.state = newState
}
- subscriber.listener.forEach((fn) => fn(newState))
+ subscriber.listener.forEach(fn => fn(newState))
callback && callback()
},
// eslint-disable-next-line react-hooks/exhaustive-deps
- [subscriber.state, pick],
- ),
+ [subscriber.state, pick]
+ )
}
}
+
+
+export const createReactive = (initialState: S): IReactive => {
+ let listener: Set> = new Set()
+ const subscribe = (fn: Listener) => listener.add(fn)
+ const unsubscribe = (fn: Listener) => listener.delete(fn)
+ const store: S = new Proxy(initialState, {
+ set(target, p: string, value, receiver) {
+ listener.forEach((fn) => fn(p))
+ return Reflect.set(target, p, value, receiver)
+ }
+ })
+ return {
+ listener, store, subscribe, unsubscribe
+ }
+}
+
+export const useReactive = (reactiveStore: IReactive, pick?: Array) => {
+ const [, setUpdate] = React.useState({})
+ const updater = React.useCallback((prop) => {
+ if (!pick || (Array.isArray(pick) && pick.includes(prop))) {
+ setUpdate({})
+ }
+ }, [pick])
+ React.useEffect(() => {
+ if (reactiveStore) {
+ reactiveStore.subscribe(updater)
+ return () => reactiveStore.unsubscribe(updater)
+ }
+ return
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
+ return reactiveStore.store
+};
+