Skip to content

Commit

Permalink
feat: allow useStore to be called within a store
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Jan 20, 2020
1 parent 1ae9162 commit fdf6b45
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 19 deletions.
35 changes: 35 additions & 0 deletions __tests__/actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@ describe('Store', () => {
})()
}

const useB = createStore({
id: 'B',
state: () => ({ b: 'b' }),
})

const useA = createStore({
id: 'A',
state: () => ({ a: 'a' }),
actions: {
swap() {
const bStore = useB()
const b = bStore.state.b
bStore.state.b = this.state.a
this.state.a = b
},
},
})

it('can use the store as this', () => {
const store = useStore()
expect(store.state.a).toBe(true)
Expand All @@ -45,4 +63,21 @@ describe('Store', () => {
expect(store.state.a).toBe(false)
expect(store.state.nested.foo).toBe('bar')
})

it('supports being called between requests', () => {
const req1 = {}
const req2 = {}
setActiveReq(req1)
const aStore = useA()

// simulate a different request
setActiveReq(req2)
const bStore = useB()
bStore.state.b = 'c'

aStore.swap()
expect(aStore.state.a).toBe('b')
// a different instance of b store was used
expect(bStore.state.b).toBe('c')
})
})
31 changes: 31 additions & 0 deletions __tests__/getters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@ describe('Store', () => {
})()
}

const useB = createStore({
id: 'B',
state: () => ({ b: 'b' }),
})

const useA = createStore({
id: 'A',
state: () => ({ a: 'a' }),
getters: {
fromB(state) {
const bStore = useB()
return state.a + ' ' + bStore.state.b
},
},
})

it('adds getters to the store', () => {
const store = useStore()
expect(store.upperCaseName.value).toBe('EDUARDO')
Expand All @@ -27,4 +43,19 @@ describe('Store', () => {
store.state.name = 'Ed'
expect(store.upperCaseName.value).toBe('ED')
})

it('supports changing between requests', () => {
const req1 = {}
const req2 = {}
setActiveReq(req1)
const aStore = useA()

// simulate a different request
setActiveReq(req2)
const bStore = useB()
bStore.state.b = 'c'

aStore.state.a = 'b'
expect(aStore.fromB.value).toBe('b b')
})
})
46 changes: 27 additions & 19 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
isPlainObject,
StoreWithGetters,
StoreGetter,
NonNullObject,
} from './types'
import { useStoreDevtools } from './devtools'

Expand All @@ -32,6 +33,15 @@ function innerPatch<T extends StateTree>(
return target
}

/**
* setActiveReq must be called to handle SSR at the top of functions like `fetch`, `setup`, `serverPrefetch` and others
*/
export let activeReq: NonNullObject = {}
export const setActiveReq = (req: NonNullObject | undefined) =>
req && (activeReq = req)

export const getActiveReq = () => activeReq

export interface StoreAction {
(...args: any[]): any
}
Expand Down Expand Up @@ -103,6 +113,7 @@ export function buildStore<
initialState?: S | undefined
): Store<Id, S, G, A> {
const state: Ref<S> = ref(initialState || buildState())
const _r = getActiveReq()

let isListening = true
let subscriptions: SubscriptionCallback<S>[] = []
Expand Down Expand Up @@ -151,6 +162,7 @@ export function buildStore<

const storeWithState: StoreWithState<Id, S> = {
id,
_r,
// it is replaced below by a getter
state: state.value,

Expand All @@ -159,20 +171,27 @@ export function buildStore<
reset,
}

// @ts-ignore we have to build it
const computedGetters: StoreWithGetters<S, G> = {}
const computedGetters: StoreWithGetters<S, G> = {} as StoreWithGetters<S, G>
for (const getterName in getters) {
const method = getters[getterName]
// @ts-ignore
computedGetters[getterName] = computed<ReturnType<typeof method>>(() =>
getters[getterName](state.value)
)
computedGetters[getterName] = computed(() => {
setActiveReq(_r)
return getters[getterName](state.value)
}) as StoreWithGetters<S, G>[typeof getterName]
}

const wrappedActions: StoreWithActions<A> = {} as StoreWithActions<A>
for (const actionName in actions) {
wrappedActions[actionName] = function() {
setActiveReq(_r)
// eslint-disable-next-line
return actions[actionName].apply(this, arguments as unknown as any[])
} as StoreWithActions<A>[typeof actionName]
}

const store: Store<Id, S, G, A> = {
...storeWithState,
...computedGetters,
...((actions as unknown) as StoreWithActions<A>),
...wrappedActions,
}

// make state access invisible
Expand All @@ -188,17 +207,6 @@ export function buildStore<
return store
}

type NonNullObject = Record<any, any>

/**
* setActiveReq must be called to handle SSR at the top of functions like `fetch`, `setup`, `serverPrefetch` and others
*/
export let activeReq: NonNullObject = {}
export const setActiveReq = (req: NonNullObject | undefined) =>
req && (activeReq = req)

export const getActiveReq = () => activeReq

/**
* The api needs more work we must be able to use the store easily in any
* function by calling `useStore` to get the store Instance and we also need to
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export function isPlainObject(
)
}

export type NonNullObject = Record<any, any>

export interface StoreGetter<S extends StateTree, T = any> {
(state: S): T
}
Expand Down Expand Up @@ -46,6 +48,11 @@ export interface StoreWithState<Id extends string, S extends StateTree> {
*/
state: S

/**
* Private property defining the _req for this store
*/
_r: NonNullObject

/**
* Applies a state patch to current state. Allows passing nested values
* @param partialState patch to apply to the state
Expand Down

0 comments on commit fdf6b45

Please sign in to comment.