Skip to content

Commit

Permalink
implement support for async local storage + update deps
Browse files Browse the repository at this point in the history
  • Loading branch information
PabloSzx committed Jul 31, 2020
1 parent 1140d2a commit 3139c41
Show file tree
Hide file tree
Showing 13 changed files with 1,224 additions and 187 deletions.
43 changes: 24 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
},
"license": "MIT",
"author": "PabloSzx",
"sideEffects": false,
"main": "dist/index.js",
"module": "dist/react-state-selector.esm.js",
"typings": "dist/index.d.ts",
Expand All @@ -42,56 +43,60 @@
"coverageDirectory": "./coverage/"
},
"resolutions": {
"@types/react": "^16.9.25",
"@types/react": "^16.9.43",
"prettier": "^2.0.5"
},
"dependencies": {
"immer": "^7.0.0",
"immer": "^7.0.7",
"reselect": "^4.0.0"
},
"devDependencies": {
"@babel/core": "^7.10.2",
"@babel/core": "^7.11.0",
"@storybook/addon-actions": "^5.3.19",
"@storybook/addon-docs": "^5.3.19",
"@storybook/addon-info": "^5.3.19",
"@storybook/addon-links": "^5.3.19",
"@storybook/addon-storysource": "^5.3.19",
"@storybook/addons": "^5.3.19",
"@storybook/core": "^5.3.19",
"@storybook/preset-create-react-app": "^3.0.0",
"@storybook/preset-create-react-app": "^3.1.4",
"@storybook/react": "^5.3.19",
"@testing-library/jest-dom": "^5.9.0",
"@testing-library/react": "^10.2.1",
"@testing-library/react-hooks": "^3.3.0",
"@types/babel__core": "^7.1.8",
"@types/jest": "^26.0.0",
"@types/react": "^16.9.25",
"@testing-library/jest-dom": "^5.11.2",
"@testing-library/react": "^10.4.7",
"@testing-library/react-hooks": "^3.4.1",
"@types/babel__core": "^7.1.9",
"@types/jest": "^26.0.8",
"@types/lodash": "^4.14.158",
"@types/react": "^16.9.43",
"@types/react-dom": "^16.9.8",
"@types/react-is": "^16.7.1",
"@types/react-test-renderer": "^16.9.2",
"@types/storybook__addon-info": "^5.2.1",
"@types/testing-library__jest-dom": "^5.9.1",
"@types/testing-library__react": "^10.0.1",
"@types/testing-library__react-hooks": "^3.2.0",
"@types/webpack": "^4.41.17",
"@types/testing-library__jest-dom": "^5.9.2",
"@types/testing-library__react": "^10.2.0",
"@types/testing-library__react-hooks": "^3.4.0",
"@types/webpack": "^4.41.21",
"babel-jest": "^26.2.2",
"babel-loader": "^8.1.0",
"husky": "^4.2.5",
"lodash": "^4.17.19",
"prettier": "^2.0.5",
"pretty-quick": "^2.0.1",
"react": "^16.13.1",
"react-docgen-typescript-loader": "^3.7.2",
"react-dom": "^16.13.1",
"react-is": "^16.13.1",
"react-scripts": "^3.4.1",
"react-state-selector": "^1.0.7",
"react-state-selector": "^1.0.9",
"react-test-renderer": "^16.13.1",
"regenerator-runtime": "^0.13.5",
"ts-loader": "^7.0.5",
"regenerator-runtime": "^0.13.7",
"ts-loader": "^8.0.1",
"tsdx": "^0.13.2",
"tslib": "^2.0.0",
"typescript": "^3.9.5",
"typescript": "^3.9.7",
"waait": "^1.0.5",
"wait-for-expect": "^3.0.2",
"webpack": "^4.43.0"
"webpack": "^4.44.1"
},
"peerDependencies": {
"react": ">=16.8"
Expand Down
24 changes: 22 additions & 2 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ const incrementParam = (num: number) => ++num;

export const emptyArray: [] = [];

export const Constants = {
IS_NOT_PRODUCTION: process.env.NODE_ENV !== "production",
};

export const useUpdate = (
persistencePlugin?: PersistenceStoragePlugin | null
) => {
Expand All @@ -41,6 +45,22 @@ export const useUpdate = (
return useCallback(() => setState(incrementParam), emptyArray);
};

export const getPromiseWithCallbacks = () => {
let resolve: () => void = undefined as any;
let reject: (reason?: any) => void = undefined as any;

const promise = new Promise<void>((resolvePromise, rejectPromise) => {
resolve = resolvePromise;
reject = rejectPromise;
});

return {
promise,
resolve,
reject,
};
};

export const isClientSide = typeof window !== "undefined";

type NN<T> = NonNullable<T>;
Expand Down Expand Up @@ -105,6 +125,6 @@ export type IUseProduce<TStore> = () => {
};

export type IPersistenceMethod = {
setItem(key: string, data: any): any;
getItem(key: string): string | null;
setItem(key: string, value: string): void | Promise<void>;
getItem(key: string): (string | null) | Promise<string | null>;
};
19 changes: 16 additions & 3 deletions src/createStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {
toAnonFunction,
useIsomorphicLayoutEffect,
useUpdate,
Constants,
getPromiseWithCallbacks,
} from "./common";
import { connectDevTools, ReduxDevTools } from "./plugins/devTools";
import {
Expand Down Expand Up @@ -147,8 +149,13 @@ export function createStore<
*/
actions: IActionsObj<TStore, typeof options> &
IAsyncActionsObj<TStore, typeof options>;
/**
* Promise that resolves when the store is ready to be used.
* Only useful when using an Async Storage Persistance, like AsyncStorage from React Native
*/
isReady: Promise<void>;
} {
if (process.env.NODE_ENV !== "production") {
if (Constants.IS_NOT_PRODUCTION) {
for (const name in options?.hooks) {
if (
name.length < 4 ||
Expand All @@ -162,7 +169,7 @@ export function createStore<
}
}

if (process.env.NODE_ENV !== "production") {
if (Constants.IS_NOT_PRODUCTION) {
if (options?.actions && options.asyncActions) {
const asyncActionsKeys = Object.keys(options.asyncActions);

Expand All @@ -178,10 +185,11 @@ export function createStore<

let devTools: ReduxDevTools | undefined;
let localStoragePlugin: PersistenceStoragePlugin<TStore> | undefined | null;
let isReady = Promise.resolve();

if (
options?.devName &&
(options.devToolsInProduction || process.env.NODE_ENV !== "production")
(options.devToolsInProduction || Constants.IS_NOT_PRODUCTION)
) {
devTools = connectDevTools(options.devName);
}
Expand Down Expand Up @@ -287,6 +295,8 @@ export function createStore<
};

if (options?.storagePersistence?.isActive) {
const { promise, resolve, reject } = getPromiseWithCallbacks();
isReady = promise;
if (typeof initialStore !== "object" || Array.isArray(initialStore))
throw new Error(
"For local storage persistence your store has to be an object"
Expand All @@ -304,6 +314,8 @@ export function createStore<
debounceWait: options.storagePersistence.debounceWait,
persistenceMethod: options.storagePersistence.persistenceMethod,
isSSR: options.storagePersistence.isSSR,
resolve,
reject,
});
}

Expand Down Expand Up @@ -432,6 +444,7 @@ export function createStore<
> &
IAsyncActionsObj<TStore, typeof options>,
hooks: (hooksObj as unknown) as IHooksObj<TStore, typeof options>,
isReady,
...produceObj,
};
}
73 changes: 60 additions & 13 deletions src/createStoreContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { createSelector, ParametricSelector } from "reselect";

Expand All @@ -40,6 +41,8 @@ import {
toAnonFunction,
useIsomorphicLayoutEffect,
useUpdate,
Constants,
getPromiseWithCallbacks,
} from "./common";
import { connectDevTools, ReduxDevTools } from "./plugins/devTools";
import {
Expand All @@ -48,6 +51,8 @@ import {
PersistenceStoragePlugin,
} from "./plugins/persistenceStorage";

const defaultIsReady = Promise.resolve();

/**
* Create React Context version of a **react-state-selector** global store
*
Expand Down Expand Up @@ -164,8 +169,13 @@ export function createStoreContext<
* @type {Record<string, (...props:any[]) => unknown>}
*/
hooks: IHooksObj<TStore, typeof options>;
/**
* Returns promise that resolves when the store is ready to be used.
* Only useful when using an Async Storage Persistance, like AsyncStorage from React Native
*/
useIsReady: () => boolean;
} {
if (process.env.NODE_ENV !== "production") {
if (Constants.IS_NOT_PRODUCTION) {
for (const name in options?.hooks) {
if (
name.length < 4 ||
Expand All @@ -177,9 +187,7 @@ export function createStoreContext<
);
}
}
}

if (process.env.NODE_ENV !== "production") {
if (options?.actions && options.asyncActions) {
const asyncActionsKeys = Object.keys(options.asyncActions);

Expand All @@ -203,6 +211,8 @@ export function createStoreContext<
"For local storage persistence your store has to be an object"
);

const { promise: isReady, resolve, reject } = getPromiseWithCallbacks();

let persistenceKey: string | undefined;
if (options.storagePersistence.persistenceKey) {
persistenceKey = options.storagePersistence.persistenceKey;
Expand All @@ -219,21 +229,28 @@ export function createStoreContext<
persistenceKey += "-noProvider";
}

return connectPersistenceStorage({
persistenceKey,
produce: produceStore,
debounceWait: options.storagePersistence.debounceWait,
persistenceMethod: options.storagePersistence.persistenceMethod,
isSSR: options.storagePersistence.isSSR,
});
return Object.assign(
connectPersistenceStorage({
persistenceKey,
produce: produceStore,
debounceWait: options.storagePersistence.debounceWait,
persistenceMethod: options.storagePersistence.persistenceMethod,
isSSR: options.storagePersistence.isSSR,
resolve,
reject,
}),
{
isReady,
}
);
}
return null;
};

const createDevToolsInstance = (debugName?: string | null) => {
if (
options?.devName &&
(options.devToolsInProduction || process.env.NODE_ENV !== "production")
(options.devToolsInProduction || Constants.IS_NOT_PRODUCTION)
) {
let devToolsName: string;
if (debugName) {
Expand Down Expand Up @@ -266,6 +283,7 @@ export function createStoreContext<
asyncProduce: IAsyncProduce<TStore>;
};
storagePersistence?: PersistenceStoragePlugin | null;
isReady: Promise<void>;
};

const createProduceObj = (
Expand Down Expand Up @@ -358,16 +376,24 @@ export function createStoreContext<
devTools: createDevToolsInstance(debugName),
};

let isReady = defaultIsReady;

const produceObj = createProduceObj(storeRef);

storeRef.produce = produceObj;

storeRef.storagePersistence = createStoragePersistenceInstance(
const persistance = createStoragePersistenceInstance(
produceObj.produce,
debugName
);

return Object.assign(storeRef, { produce: produceObj });
if (persistance) {
const { current, getState, setState } = persistance;
storeRef.storagePersistence = { current, getState, setState };
isReady = persistance.isReady;
}

return Object.assign(storeRef, { produce: produceObj, isReady });
};

const StoreContext = createContext<MutableRefObject<IStore>>({
Expand Down Expand Up @@ -546,11 +572,32 @@ export function createStoreContext<
};
}

const useIsReady = () => {
const {
current: { isReady },
} = useContext(StoreContext);

const [ready, setReady] = useState(isReady === defaultIsReady);

useEffect(() => {
if (isReady !== defaultIsReady) {
isReady.then(() => {
setTimeout(() => {
setReady(true);
}, 0);
});
}
}, [setReady]);

return ready;
};

return {
Provider,
useStore,
useProduce,
useActions,
hooks: (hooksObj as unknown) as IHooksObj<TStore, typeof options>,
useIsReady,
};
}
Loading

0 comments on commit 3139c41

Please sign in to comment.