Skip to content

Commit

Permalink
fix: correct lifespan of stores
Browse files Browse the repository at this point in the history
Fix #255

BREAKING CHANGE: `setActiveReq()` has been renamed to
`setActivePinia()`. And now receives the application's pinia as the
first parameter instead of an arbitrary object (like a Node http
    request). **This affects particularily users doing SSR** but also
enables them to write universal code.
  • Loading branch information
posva committed Dec 31, 2020
1 parent e69bfb8 commit 483335c
Show file tree
Hide file tree
Showing 16 changed files with 405 additions and 216 deletions.
13 changes: 8 additions & 5 deletions README.md
Expand Up @@ -110,7 +110,7 @@ export const useMainStore = defineStore({
// use getters in other getters
doubleCountPlusOne() {
return this.doubleCount * 2
}
},
},
// optional actions
actions: {
Expand Down Expand Up @@ -143,9 +143,9 @@ export default defineComponent({
})
```

Note: the SSR implementation is yet to be decided on Pinia, but if you intend having SSR on your application, you should avoid using `useStore` functions at the root level of a file to make sure the correct store is retrieved for your request. Here is an example:
Note: the SSR implementation on Pinia might change, but if you intend having SSR on your application, you should avoid using `useStore` functions at the root level of a file to make sure the correct store is retrieved for your currently running application instance. Here is an example:

**Avoid doing this\***:
**Avoid doing this**:

```ts
import { createRouter } from 'vue-router'
Expand Down Expand Up @@ -175,10 +175,13 @@ export default defineComponent({
})

// In a different file...
const pinia = createPinia()
app.use(pinia)

router.beforeEach((to) => {
// ✅ This will work (requires an extra param for SSR, see below)
const main = useMainStore()
// ✅ This will work (requires pinia param when outside of setup on both
// Client and Server. See the SSR section below for more information)
const main = useMainStore(pinia)

if (to.meta.requiresAuth && !main.isLoggedIn) return '/login'
})
Expand Down
28 changes: 14 additions & 14 deletions __tests__/actions.spec.ts
@@ -1,9 +1,9 @@
import { defineStore, setActiveReq } from '../src'
import { createPinia, defineStore, setActivePinia } from '../src'

describe('Actions', () => {
const useStore = () => {
// create a new store
setActiveReq({})
setActivePinia(createPinia())
return defineStore({
id: 'main',
state: () => ({
Expand Down Expand Up @@ -82,14 +82,14 @@ describe('Actions', () => {
expect(store.$state.nested.foo).toBe('bar')
})

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

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

Expand All @@ -99,19 +99,19 @@ describe('Actions', () => {
expect(bStore.$state.b).toBe('c')
})

it('can force the req', () => {
const req1 = {}
const req2 = {}
const aStore = useA(req1)
it('can force the pinia', () => {
const pinia1 = createPinia()
const pinia2 = createPinia()
const aStore = useA(pinia1)

let bStore = useB(req2)
let bStore = useB(pinia2)
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')
bStore = useB(req1)
bStore = useB(pinia1)
expect(bStore.$state.b).toBe('a')
})
})
16 changes: 8 additions & 8 deletions __tests__/getters.spec.ts
@@ -1,9 +1,9 @@
import { defineStore, setActiveReq } from '../src'
import { createPinia, defineStore, setActivePinia } from '../src'

describe('Getters', () => {
const useStore = () => {
// create a new store
setActiveReq({})
setActivePinia(createPinia())
return defineStore({
id: 'main',
state: () => ({
Expand Down Expand Up @@ -52,14 +52,14 @@ describe('Getters', () => {
expect(store.upperCaseName).toBe('ED')
})

it('supports changing between requests', () => {
const req1 = {}
const req2 = {}
setActiveReq(req1)
it('supports changing between piniauests', () => {
const pinia1 = createPinia()
const pinia2 = createPinia()
setActivePinia(pinia1)
const aStore = useA()

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

Expand Down
105 changes: 105 additions & 0 deletions __tests__/lifespan.spec.ts
@@ -0,0 +1,105 @@
import { createPinia, defineStore, setActivePinia } from '../src'
import { mount } from '@vue/test-utils'
import { watch, nextTick, ref } from 'vue'

describe('Store Lifespan', () => {
function defineMyStore() {
return defineStore({
id: 'main',
state: () => ({
a: true,
n: 0,
nested: {
foo: 'foo',
a: { b: 'string' },
},
}),
getters: {
double() {
return this.n * 2
},
notA() {
return !this.a
},
},
})
}

const pinia = createPinia()
// let pinia: object

// const useStore = () => {
// // create a new store
// pinia = {}
// setActivePinia(pinia)
// return defineMyStore()()
// }

it('bug report', async () => {
const inComponentWatch = jest.fn()

const n = ref(0)

const wrapper = mount(
{
render: () => null,
setup() {
watch(() => n.value, inComponentWatch)
n.value++
},
},
{
global: {
plugins: [pinia],
},
}
)

await wrapper.unmount()

expect(inComponentWatch).toHaveBeenCalledTimes(1)

// store!.n++
n.value++
await nextTick()
expect(inComponentWatch).toHaveBeenCalledTimes(1)
})

it('state reactivity outlives component life', async () => {
const useStore = defineMyStore()
setActivePinia(createPinia())

const inComponentWatch = jest.fn()

let store: ReturnType<typeof useStore>

const n = ref(0)

const wrapper = mount(
{
render: () => null,
setup() {
store = useStore()
// watch(() => store.n, inComponentWatch)
watch(() => n.value, inComponentWatch)
store.n++
n.value++
},
},
{
global: {
plugins: [pinia],
},
}
)

await wrapper.unmount()

expect(inComponentWatch).toHaveBeenCalledTimes(1)

// store!.n++
n.value++
await nextTick()
expect(inComponentWatch).toHaveBeenCalledTimes(1)
})
})
32 changes: 16 additions & 16 deletions __tests__/rootState.spec.ts
@@ -1,4 +1,4 @@
import { defineStore, getRootState } from '../src'
import { createPinia, defineStore, getRootState } from '../src'

describe('Root State', () => {
const useA = defineStore({
Expand All @@ -12,35 +12,35 @@ describe('Root State', () => {
})

it('works with no stores', () => {
expect(getRootState({})).toEqual({})
expect(getRootState(createPinia())).toEqual({})
})

it('retrieves the root state of one store', () => {
const req = {}
useA(req)
expect(getRootState(req)).toEqual({
const pinia = createPinia()
useA(pinia)
expect(getRootState(pinia)).toEqual({
a: { a: 'a' },
})
})

it('does not mix up different requests', () => {
const req1 = {}
const req2 = {}
useA(req1)
useB(req2)
expect(getRootState(req1)).toEqual({
it('does not mix up different piniauests', () => {
const pinia1 = createPinia()
const pinia2 = createPinia()
useA(pinia1)
useB(pinia2)
expect(getRootState(pinia1)).toEqual({
a: { a: 'a' },
})
expect(getRootState(req2)).toEqual({
expect(getRootState(pinia2)).toEqual({
b: { b: 'b' },
})
})

it('can hold multiple stores', () => {
const req1 = {}
useA(req1)
useB(req1)
expect(getRootState(req1)).toEqual({
const pinia1 = createPinia()
useA(pinia1)
useB(pinia1)
expect(getRootState(pinia1)).toEqual({
a: { a: 'a' },
b: { b: 'b' },
})
Expand Down
27 changes: 24 additions & 3 deletions __tests__/state.spec.ts
@@ -1,10 +1,10 @@
import { defineStore, setActiveReq } from '../src'
import { computed } from 'vue'
import { createPinia, defineStore, setActivePinia } from '../src'
import { computed, nextTick, watch } from 'vue'

describe('State', () => {
const useStore = () => {
// create a new store
setActiveReq({})
setActivePinia(createPinia())
return defineStore({
id: 'main',
state: () => ({
Expand All @@ -28,4 +28,25 @@ describe('State', () => {
store.name = 'Ed'
expect(upperCased.value).toBe('ED')
})

// it('watch', () => {
// setActivePinia(createPinia())
// defineStore({
// id: 'main',
// state: () => ({
// name: 'Eduardo',
// counter: 0,
// }),
// })()
// })

it('state can be watched', async () => {
const store = useStore()
const spy = jest.fn()
watch(() => store.name, spy)
expect(spy).not.toHaveBeenCalled()
store.name = 'Ed'
await nextTick()
expect(spy).toHaveBeenCalledTimes(1)
})
})
4 changes: 2 additions & 2 deletions __tests__/store.patch.spec.ts
@@ -1,9 +1,9 @@
import { defineStore, setActiveReq } from '../src'
import { createPinia, defineStore, setActivePinia } from '../src'

describe('store.$patch', () => {
const useStore = () => {
// create a new store
setActiveReq({})
setActivePinia(createPinia())
return defineStore({
id: 'main',
state: () => ({
Expand Down

0 comments on commit 483335c

Please sign in to comment.