Skip to content

Commit

Permalink
Refactoring some things, adding server rendering abilities
Browse files Browse the repository at this point in the history
  • Loading branch information
lostpebble committed Feb 17, 2019
1 parent 6c7f9a0 commit 8a8ab07
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 20 deletions.
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"printWidth": 120,
"trailingComma": "es5"
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"devDependencies": {
"typescript": "^3.3.3",
"in-publish": "^2.0.0",
"react": "^16.8.2"
"react": "^16.8.2",
"prettier": "^1.16.4"
},
"peerDependencies": {
"react": "^16.8.2"
Expand Down
54 changes: 35 additions & 19 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,95 @@
import shallowEqual from "fbjs/lib/shallowEqual";
import immer from "immer";
import { useEffect, useState } from "react";
import React, { useEffect, useState } from "react";

export type TUpdateListener = () => void;
export type TPullstateUpdateListener = () => void;

// S = State
class Store<S = any> {
private updateListeners: TUpdateListener[] = [];
private updateListeners: TPullstateUpdateListener[] = [];
private currentState: S;
private readonly initialState: S;

constructor(initialState: S) {
console.log(`Constructing ${this.constructor.name}`);
this.currentState = initialState;
this.initialState = initialState;
}

getState(): S {
_getState(): S {
return this.currentState;
}

updateState(nextState: S) {
_updateState(nextState: S) {
this.currentState = nextState;
this.updateListeners.forEach(listener => listener());
}

addUpdateListener(listener: TUpdateListener) {
_addUpdateListener(listener: TPullstateUpdateListener) {
this.updateListeners.push(listener);
}

removeUpdateListener(listener: TUpdateListener) {
_removeUpdateListener(listener: TPullstateUpdateListener) {
this.updateListeners = this.updateListeners.filter(f => f !== listener);
}

resetStore() {
this.currentState = this.initialState;
update(updater: (state: S) => void) {
update(this, updater);
}
}

function update<S = any>(store: Store<S>, updater: (state: S) => void) {
const currentState: S = store.getState();
const currentState: S = store._getState();
const nextState: S = immer(currentState as any, updater);
if (nextState !== currentState) {
store.updateState(nextState);
store._updateState(nextState);
}
}

// S = State
// SS = Sub-state
function useStore<S = any>(store: Store<S>): S;
function useStore<S = any, SS = any>(store: Store<S>, getSubState: (state: S) => SS): SS;
function useStore(store: Store, getSubState?: (state) => any): any {
function useStoreState<S = any>(store: Store<S>): S;
function useStoreState<S = any, SS = any>(store: Store<S>, getSubState: (state: S) => SS): SS;
function useStoreState(store: Store, getSubState?: (state) => any): any {
const [subState, setSubState] = useState<any | null>(null);
let shouldUpdate = true;

function onStoreUpdate() {
const nextSubState = getSubState ? getSubState(store.getState()) : store.getState();
const nextSubState = getSubState ? getSubState(store._getState()) : store._getState();
if (shouldUpdate && !shallowEqual(subState, nextSubState)) {
setSubState(nextSubState);
}
}

useEffect(() => {
store.addUpdateListener(onStoreUpdate);
store._addUpdateListener(onStoreUpdate);

return () => {
shouldUpdate = false;
store.removeUpdateListener(onStoreUpdate);
store._removeUpdateListener(onStoreUpdate);
};
});

if (subState === null) {
return getSubState ? getSubState(store.getState()) : store.getState();
return getSubState ? getSubState(store._getState()) : store._getState();
}

return subState;
}

export { useStore, update, Store };
export interface IPropsInjectStoreState<S extends any = any, SS extends any = any> {
store: Store<S>;
getSubState?: (state: S) => SS;
children: (output: SS) => React.ReactElement;
}

function InjectStoreState<S = any, SS = any>({
store,
children,
getSubState = s => s as any,
}: IPropsInjectStoreState<S, SS>) {
const state: SS = useStoreState(store, getSubState);
return children(state);
}

export { useStoreState, update, Store, InjectStoreState };
111 changes: 111 additions & 0 deletions src/serverRenderingUtils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React, { useContext } from "react";
import { Store } from "./index";

export interface IPullstateAllStores {
[storeName: string]: Store<any>;
}

const PullstateContext = React.createContext<IPullstateAllStores>({});

export const PullstateProvider = <T extends IPullstateAllStores = IPullstateAllStores>({
stores,
children,
}: {
stores: T;
children?: any;
}) => {
return <PullstateContext.Provider value={stores}>{children}</PullstateContext.Provider>;
};

let singleton: PullstateSingleton | null = null;

export class PullstateSingleton<T extends IPullstateAllStores = IPullstateAllStores> {
private readonly allInitialStores: T = {} as T;

constructor(allStores: T) {
if (singleton !== null) {
console.error(
`Pullstate: createPullstate() - Should not be creating the core Pullstate class more than once! In order to re-use pull state, you need to call instantiate() on your already created object.`
);
}

singleton = this;
this.allInitialStores = allStores;
}

instantiate({
hydrateState = null,
reuseStores = false,
}: { hydrateState?: any; reuseStores?: boolean } = {}): PullstateInstance<T> {
if (reuseStores) {
const instantiated = new PullstateInstance(this.allInitialStores);

if (hydrateState != null) {
instantiated.hydrateFromAllState(hydrateState);
}

return instantiated;
}

const newStores = {} as T;

for (const storeName of Object.keys(this.allInitialStores)) {
if (hydrateState == null) {
newStores[storeName] = new Store(this.allInitialStores[storeName]._getState());
} else if (hydrateState.hasOwnProperty(storeName)) {
newStores[storeName] = new Store(hydrateState[storeName]);
} else {
newStores[storeName] = new Store(this.allInitialStores[storeName]._getState());
console.warn(
`Pullstate (instantiate): store [${storeName}] didn't hydrate any state (data was non-existent on hydration object)`
);
}
}

return new PullstateInstance(newStores);
}

useStores() {
return useContext(PullstateContext) as T;
}
}

class PullstateInstance<T extends IPullstateAllStores = IPullstateAllStores> {
private readonly _stores: T = {} as T;

constructor(allStores: T) {
this._stores = allStores;
}

getAllState(): { [storeName: string]: any } {
const allState = {};

for (const storeName of Object.keys(this._stores)) {
allState[storeName] = this._stores[storeName]._getState();
}

return allState;
}

get stores(): T {
return this._stores;
}

hydrateFromAllState(allState: any) {
for (const storeName of Object.keys(this._stores)) {
if (allState.hasOwnProperty(storeName)) {
this._stores[storeName]._updateState(allState[storeName]);
} else {
console.warn(`${storeName} didn't hydrate any state (data was non-existent on hydration object)`);
}
}
}
}

export function createPullstate<T extends IPullstateAllStores = IPullstateAllStores>(allStores: T) {
return new PullstateSingleton(allStores);
}

export function useStores<T extends IPullstateAllStores = {}>() {
return useContext(PullstateContext) as T;
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"jsx": "react",
"module": "commonjs",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
Expand Down
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"

prettier@^1.16.4:
version "1.16.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717"

promise@^7.1.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
Expand Down

0 comments on commit 8a8ab07

Please sign in to comment.