Comparing version 0.1.0 to 0.2.0
import { StateTree, StoreWithState } from './types'; | ||
export declare function useStoreDevtools(store: StoreWithState<string, StateTree>): void; | ||
export declare function useStoreDevtools(store: StoreWithState<string, StateTree>, stateDescriptor: { | ||
get: () => StateTree; | ||
set: (newValue: StateTree) => void; | ||
}): void; | ||
//# sourceMappingURL=devtools.d.ts.map |
@@ -1,4 +0,5 @@ | ||
export { createStore } from './store'; | ||
export { setActiveReq, setStateProvider, getRootState } from './rootStore'; | ||
export { StateTree, Store } from './types'; | ||
export { PiniaSsr } from './ssrPlugin'; | ||
export { setActivePinia, createPinia, Pinia, PiniaPlugin, PiniaStorePlugin, PiniaCustomProperties, } from './rootStore'; | ||
export { defineStore } from './store'; | ||
export { StateTree, Store, StoreWithGetters, StoreWithActions, StoreWithState, } from './types'; | ||
export { createStore } from './deprecated'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -1,9 +0,6 @@ | ||
import { NonNullObject, StateTree } from './types'; | ||
import { InjectionKey, Ref } from '@vue/composition-api'; | ||
import { StateTree, StoreWithState, StateDescriptor } from './types'; | ||
import { PluginFunction, VueConstructor } from 'vue'; | ||
import type Vue from 'vue'; | ||
/** | ||
* setActiveReq must be called to handle SSR at the top of functions like `fetch`, `setup`, `serverPrefetch` and others | ||
*/ | ||
export declare let activeReq: NonNullObject; | ||
export declare const setActiveReq: (req: NonNullObject | undefined) => Record<any, any> | undefined; | ||
export declare const getActiveReq: () => Record<any, any>; | ||
/** | ||
* The api needs more work we must be able to use the store easily in any | ||
@@ -13,21 +10,74 @@ * function by calling `useStore` to get the store Instance and we also need to | ||
*/ | ||
export declare const storesMap: WeakMap<Record<any, any>, Map<string, import("./types").Store<string, Record<string | number | symbol, any>, Record<string, import("./types").Method>, Record<string, import("./types").Method>>>>; | ||
export declare const storesMap: WeakMap<Pinia, Map<string, [StoreWithState<string, StateTree>, StateDescriptor<StateTree>]>>; | ||
/** | ||
* A state provider allows to set how states are stored for hydration. e.g. setting a property on a context, getting a property from window | ||
* Properties that are added to every store by `pinia.use()` | ||
*/ | ||
interface StateProvider { | ||
(): Record<string, StateTree>; | ||
export interface PiniaCustomProperties { | ||
} | ||
export declare const piniaSymbol: InjectionKey<Pinia>; | ||
/** | ||
* Map of initial states used for hydration | ||
* Plugin to extend every store | ||
*/ | ||
export declare const stateProviders: WeakMap<Record<any, any>, StateProvider>; | ||
export declare function setStateProvider(stateProvider: StateProvider): void; | ||
export declare function getInitialState(id: string): StateTree | undefined; | ||
export interface PiniaStorePlugin { | ||
(pinia: Pinia): Partial<PiniaCustomProperties>; | ||
} | ||
/** | ||
* Gets the root state of all active stores. This is useful when reporting an application crash by | ||
* retrieving the problematic state and send it to your error tracking service. | ||
* @param req request key | ||
* Every application must own its own pinia to be able to create stores | ||
*/ | ||
export declare function getRootState(req: NonNullObject): Record<string, StateTree>; | ||
export {}; | ||
export interface Pinia { | ||
/** | ||
* root state | ||
*/ | ||
state: Ref<Record<string, StateTree>>; | ||
/** | ||
* Adds a store plugin to extend every store | ||
* | ||
* @alpha DO NOT USE, The plugin architecture will change to provide more | ||
* customization options. | ||
* | ||
* @param plugin - store plugin to add | ||
*/ | ||
use(plugin: PiniaStorePlugin): void; | ||
/** | ||
* Installed store plugins | ||
* | ||
* @internal | ||
*/ | ||
_p: Array<() => Partial<PiniaCustomProperties>>; | ||
/** | ||
* Vue constructor retrieved when installing the pinia. | ||
*/ | ||
Vue: VueConstructor<Vue>; | ||
} | ||
declare module 'vue/types/vue' { | ||
interface Vue { | ||
$pinia: Pinia; | ||
} | ||
} | ||
declare module 'vue/types/options' { | ||
interface ComponentOptions<V extends Vue> { | ||
pinia?: Pinia; | ||
} | ||
} | ||
export declare const PiniaPlugin: PluginFunction<void>; | ||
/** | ||
* Creates a Pinia instance to be used by the application | ||
*/ | ||
export declare function createPinia(): Pinia; | ||
/** | ||
* setActivePinia must be called to handle SSR at the top of functions like | ||
* `fetch`, `setup`, `serverPrefetch` and others | ||
*/ | ||
export declare let activePinia: Pinia | undefined; | ||
/** | ||
* Sets or unsets the active pinia. Used in SSR and internally when calling | ||
* actions and getters | ||
* | ||
* @param pinia - Pinia instance | ||
*/ | ||
export declare const setActivePinia: (pinia: Pinia | undefined) => Pinia | undefined; | ||
/** | ||
* Get the currently active pinia | ||
*/ | ||
export declare const getActivePinia: () => Pinia; | ||
//# sourceMappingURL=rootStore.d.ts.map |
@@ -1,13 +0,10 @@ | ||
import { StateTree, StoreWithState, StoreWithGetters, Store, Method } from './types'; | ||
import { StateTree, StoreWithState, StoreWithGetters, Store } from './types'; | ||
import { Pinia } from './rootStore'; | ||
/** | ||
* Creates a store instance | ||
* @param id unique identifier of the store, like a name. eg: main, cart, user | ||
* @param initialState initial state applied to the store, Must be correctly typed to infer typings | ||
*/ | ||
export declare function buildStore<Id extends string, S extends StateTree, G extends Record<string, Method>, A extends Record<string, Method>>(id: Id, buildState?: () => S, getters?: G, actions?: A, initialState?: S | undefined): Store<Id, S, G, A>; | ||
/** | ||
* Creates a `useStore` function that retrieves the store instance | ||
* Defines a `useStore()` function that creates or retrieves the store instance | ||
* when called. | ||
* | ||
* @param options | ||
*/ | ||
export declare function createStore<Id extends string, S extends StateTree, G, A>(options: { | ||
export declare function defineStore<Id extends string, S extends StateTree, G, A>(options: { | ||
id: Id; | ||
@@ -17,2 +14,3 @@ state?: () => S; | ||
actions?: A & ThisType<A & S & StoreWithState<Id, S> & StoreWithGetters<G>>; | ||
}): (reqKey?: object | undefined) => Store<Id, S, G, A>; | ||
}): (pinia?: Pinia | null | undefined) => Store<Id, S, G, A>; | ||
//# sourceMappingURL=store.d.ts.map |
@@ -0,2 +1,10 @@ | ||
import { Pinia } from './rootStore'; | ||
export declare type StateTree = Record<string | number | symbol, any>; | ||
/** | ||
* Object descriptor for Object.defineProperty | ||
*/ | ||
export interface StateDescriptor<S extends StateTree> { | ||
get(): S; | ||
set(newValue: S): void; | ||
} | ||
export declare function isPlainObject(o: any): o is StateTree; | ||
@@ -16,16 +24,18 @@ export declare type NonNullObject = Record<any, any>; | ||
*/ | ||
id: Id; | ||
$id: Id; | ||
/** | ||
* State of the Store. Setting it will replace the whole state. | ||
*/ | ||
state: S; | ||
$state: S; | ||
/** | ||
* Private property defining the request key for this store | ||
* Private property defining the pinia the store is attached to. | ||
* | ||
* @internal | ||
*/ | ||
_r: NonNullObject; | ||
_p: Pinia; | ||
/** | ||
* Applies a state patch to current state. Allows passing nested values | ||
* @param partialState patch to apply to the state | ||
* @param partialState - patch to apply to the state | ||
*/ | ||
patch(partialState: DeepPartial<S>): void; | ||
$patch(partialState: DeepPartial<S>): void; | ||
/** | ||
@@ -35,9 +45,9 @@ * Resets the store to its initial state by removing all subscriptions and | ||
*/ | ||
reset(): void; | ||
$reset(): void; | ||
/** | ||
* Setups a callback to be called whenever the state changes. | ||
* @param callback callback that is called whenever the state | ||
* @param callback - callback that is called whenever the state | ||
* @returns function that removes callback from subscriptions | ||
*/ | ||
subscribe(callback: SubscriptionCallback<S>): () => void; | ||
$subscribe(callback: SubscriptionCallback<S>): () => void; | ||
} | ||
@@ -67,1 +77,2 @@ export declare type Method = (...args: any[]) => any; | ||
} | ||
//# sourceMappingURL=types.d.ts.map |
// @ts-check | ||
/// <reference types="./types" /> | ||
import Vue from 'vue' | ||
// @ts-ignore: this must be pinia to load the local module | ||
import { setActiveReq, PiniaSsr, setStateProvider, getRootState } from 'pinia' | ||
import { setActivePinia, PiniaPlugin, createPinia } from 'pinia' | ||
if (process.server) { | ||
Vue.use(PiniaSsr) | ||
} | ||
Vue.use(PiniaPlugin) | ||
/** @type {import('@nuxt/types').Plugin} */ | ||
const myPlugin = context => { | ||
// console.log('🍍 Pinia Nuxt plugin installed') | ||
const myPlugin = (context, inject) => { | ||
// console.log(context) | ||
/** @type {import('../src').Pinia} */ | ||
const pinia = createPinia() | ||
context.app.pinia = pinia | ||
context.pinia = pinia | ||
setActivePinia(pinia) | ||
// we bypass warnings | ||
// @ts-ignore | ||
pinia._p.push(() => ({ $nuxt: context })) | ||
if (process.server) { | ||
setActiveReq(context.req) | ||
context.beforeNuxtRender(({ nuxtState }) => { | ||
nuxtState.pinia = getRootState(context.req) | ||
nuxtState.pinia = pinia.state.value | ||
}) | ||
} else if (context.nuxtState) { | ||
setStateProvider(() => context.nuxtState.pinia) | ||
} else if (context.nuxtState && context.nuxtState.pinia) { | ||
pinia.state.value = context.nuxtState.pinia | ||
} | ||
@@ -22,0 +30,0 @@ } |
{ | ||
"name": "pinia", | ||
"version": "0.1.0", | ||
"description": "Some awesome description", | ||
"main": "dist/pinia.common.js", | ||
"module": "dist/pinia.esm.js", | ||
"unpkg": "dist/pinia.js", | ||
"browser": "dist/pinia.esm.js", | ||
"version": "0.2.0", | ||
"description": "Intuitive, type safe and flexible Store for Vue", | ||
"main": "dist/pinia.cjs.js", | ||
"module": "dist/pinia.esm-bundler.js", | ||
"unpkg": "dist/pinia.global.js", | ||
"jsdelivr": "dist/pinia.global.js", | ||
"types": "dist/src", | ||
@@ -22,8 +22,11 @@ "sideEffects": false, | ||
"test": "yarn run unit && yarn run build", | ||
"build": "rollup -c rollup.config.js", | ||
"prepublishOnly": "yarn run build" | ||
"build": "rimraf dist && rollup -c rollup.config.js", | ||
"size": "rollup -c size-checks/rollup.config.js && node scripts/check-size.js", | ||
"release": "bash scripts/release.sh", | ||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1" | ||
}, | ||
"files": [ | ||
"nuxt", | ||
"dist", | ||
"nuxt/*.js", | ||
"nuxt/*.d.ts", | ||
"dist/*.js", | ||
"dist/src", | ||
@@ -36,31 +39,50 @@ "LICENSE", | ||
}, | ||
"keywords": [], | ||
"keywords": [ | ||
"vue", | ||
"vuex", | ||
"store", | ||
"pinia", | ||
"piña", | ||
"pigna", | ||
"composition", | ||
"api", | ||
"setup", | ||
"typed", | ||
"typescript", | ||
"ts", | ||
"type", | ||
"safe" | ||
], | ||
"license": "MIT", | ||
"devDependencies": { | ||
"@nuxt/types": "^2.13.0", | ||
"@rollup/plugin-alias": "^3.0.0", | ||
"@rollup/plugin-replace": "^2.2.1", | ||
"@types/jest": "^26.0.0", | ||
"@typescript-eslint/eslint-plugin": "^2.20.0", | ||
"@typescript-eslint/parser": "^2.20.0", | ||
"@vue/composition-api": "^0.6.3", | ||
"codecov": "^3.6.1", | ||
"eslint": "^6.4.0", | ||
"eslint-config-prettier": "^6.10.0", | ||
"eslint-plugin-prettier": "^3.1.1", | ||
"jest": "^25.1.0", | ||
"@nuxt/types": "^2.15.2", | ||
"@rollup/plugin-alias": "^3.1.1", | ||
"@rollup/plugin-commonjs": "^17.0.0", | ||
"@rollup/plugin-node-resolve": "^11.0.0", | ||
"@rollup/plugin-replace": "^2.3.3", | ||
"@types/jest": "^26.0.20", | ||
"@typescript-eslint/eslint-plugin": "^4.16.1", | ||
"@typescript-eslint/parser": "^4.16.1", | ||
"@vue/composition-api": "^1.0.0-rc.3", | ||
"@vue/test-utils": "^1.1.3", | ||
"brotli": "^1.3.2", | ||
"codecov": "^3.8.1", | ||
"conventional-changelog-cli": "^2.1.0", | ||
"eslint": "^7.21.0", | ||
"eslint-config-prettier": "^8.1.0", | ||
"eslint-plugin-prettier": "^3.3.1", | ||
"jest": "^26.6.3", | ||
"jest-mock-warn": "^1.1.0", | ||
"pascalcase": "^1.0.0", | ||
"prettier": "^1.18.2", | ||
"prettier": "^2.2.1", | ||
"rimraf": "^3.0.1", | ||
"rollup": "^2.7.2", | ||
"rollup-plugin-commonjs": "^10.1.0", | ||
"rollup-plugin-node-resolve": "^5.2.0", | ||
"rollup": "^2.40.0", | ||
"rollup-plugin-terser": "^7.0.0", | ||
"rollup-plugin-typescript2": "^0.27.0", | ||
"ts-jest": "^25.2.1", | ||
"tsd": "^0.13.1", | ||
"typescript": "^3.8.2", | ||
"rollup-plugin-typescript2": "^0.30.0", | ||
"ts-jest": "^26.5.2", | ||
"tsd": "^0.14.0", | ||
"typescript": "^4.2.2", | ||
"vue": "^2.6.11", | ||
"vue-server-renderer": "^2.6.11" | ||
"vue-server-renderer": "^2.6.11", | ||
"vue-template-compiler": "^2.6.12" | ||
}, | ||
@@ -67,0 +89,0 @@ "repository": { |
309
README.md
@@ -1,51 +0,67 @@ | ||
# Pinia [![Build Status](https://badgen.net/circleci/github/posva/pinia/master)](https://circleci.com/gh/posva/pinia) [![npm package](https://badgen.net/npm/v/pinia)](https://www.npmjs.com/package/pinia) [![coverage](https://badgen.net/codecov/c/github/posva/pinia/master)](https://codecov.io/github/posva/pinia) [![thanks](https://badgen.net/badge/thanks/♥/pink)](https://github.com/posva/thanks) | ||
<p align="center"> | ||
<a href="https://pinia.esm.dev" target="_blank" rel="noopener noreferrer"> | ||
<img width="180" src="https://pinia.esm.dev/logo.svg" alt="Pinia logo"> | ||
</a> | ||
</p> | ||
<br/> | ||
<p align="center"> | ||
<a href="https://npmjs.com/package/pinia"><img src="https://badgen.net/npm/v/pinia" alt="npm package"></a> | ||
<a href="https://app.circleci.com/pipelines/github/posva/pinia?branch=v1"><img src="https://badgen.net/circleci/github/posva/pinia/v1" alt="build status"></a> | ||
<a href="https://codecov.io/github/posva/pinia"><img src="https://badgen.net/codecov/c/github/posva/pinia/v1" alt="code coverage"></a> | ||
</p> | ||
<br/> | ||
> Pronounced like the fruit in Spanish, _Piña_ | ||
> | ||
> _Piña_ is also an invalid package name... that's why it has to be _pinia_ which sounds very similar | ||
# Pinia | ||
🍍Automatically Typed, Modular and lightweight (but **Experimental**) Store for Vue based on the composition api with devtools support | ||
> Intuitive, type safe and flexible Store for Vue | ||
## 👉 [Demo](https://vcuiu.csb.app/) | ||
- 💡 Intuitive | ||
- 🔑 Type Safe | ||
- ⚙️ Devtools support | ||
- 🔌 Extensible | ||
- 🏗 Modular by design | ||
- 📦 Extremely light | ||
⚠️⚠️⚠️ This project is experimental, it's an exploration of what a _Store_ could be like using [the composition api](https://vue-composition-api-rfc.netlify.com). It works for Vue 2 by using the [official library](https://github.com/vuejs/composition-api). | ||
Pinia works both for Vue 2.x and Vue 3.x and you are currently on the branch that supports Vue 2.x. **If you are looking for the version compatible with Vue 3.x, check the [`v2` branch](https://github.com/posva/pinia/tree/v2)**. | ||
**If you are looking for the version compatible with Vue 3.x, check the [`next` branch](https://github.com/posva/pinia/tree/next)** | ||
Pinia is is the most similar English pronunciation of the word _pineapple_ in Spanish: _piña_. A pineapple is in reality a group of individual flowers that join together to create a multiple fruit. Similar to stores, each one is born individually, but they are all connected at the end. It's also a delicious tropical fruit indigenous to South America. | ||
What I want is to inspire others to think about ways to improve Vuex and come up with something that works very well with the composition api. Ideally it could also be used without it. **@vue/composition-api is necessary**. | ||
There are the core principles that I try to achieve with this experiment: | ||
- Flat modular structure 🍍 No nesting, only stores, compose them as needed | ||
- Light layer on top of Vue 💨 keep it very lightweight | ||
- Only `state`, `getters` | ||
- No more verbose mutations, 👐 `patch` is _the mutation_ | ||
- Actions are like _methods_ ⚗️ Group your business there | ||
- Import what you need, let webpack code split 📦 No need for dynamically registered modules | ||
- SSR support ⚙️ | ||
- DevTools support 💻 Which is crucial to make this enjoyable | ||
**Help me keep working on Open Source in a sustainable way 🚀**. Help me with as little as \$1 a month, [sponsor me on Github](https://github.com/sponsors/posva). | ||
<h3 align="center">Silver Sponsors</h3> | ||
<h4 align="center">Gold Sponsors</h4> | ||
<p align="center"> | ||
<a href="https://www.vuemastery.com" title="Vue Mastery" target="_blank"> | ||
<img src="https://www.vuemastery.com/images/lgo-vuemastery.svg" alt="Vue Mastery logo" height="48px"> | ||
<a href="https://passionatepeople.io/" target="_blank" rel="noopener noreferrer"> | ||
<img src="https://img2.storyblok.com/0x200/filters::format(webp)/f/86387/x/4cf6a70a8c/logo-white-text.svg" height="72px" alt="Passionate People"> | ||
</a> | ||
</p> | ||
<h4 align="center">Silver Sponsors</h4> | ||
<p align="center"> | ||
<a href="https://vuetifyjs.com" target="_blank" title="Vuetify"> | ||
<img src="https://vuejs.org/images/vuetify.png" alt="Vuetify logo" height="48px"> | ||
<a href="https://www.vuemastery.com" target="_blank" rel="noopener noreferrer"> | ||
<img src="https://www.vuemastery.com/images/vuemastery.svg" height="42px" alt="Vue Mastery"> | ||
</a> | ||
<a href="https://vuetifyjs.com" target="_blank" rel="noopener noreferrer"> | ||
<img src="https://cdn.vuetifyjs.com/docs/images/logos/vuetify-logo-light-text.svg" alt="Vuetify" height="42px"> | ||
</a> | ||
<a href="https://www.codestream.com/?utm_source=github&utm_campaign=vuerouter&utm_medium=banner" target="_blank" rel="noopener noreferrer"> | ||
<img src="https://alt-images.codestream.com/codestream_logo_vuerouter.png" alt="CodeStream" height="42px"> | ||
</a> | ||
</p> | ||
<h3 align="center">Bronze Sponsors</h3> | ||
<h4 align="center">Bronze Sponsors</h4> | ||
<p align="center"> | ||
<a href="https://www.storyblok.com" target="_blank" title="Storyblok"> | ||
<img src="https://a.storyblok.com/f/51376/3856x824/fea44d52a9/colored-full.png" alt="Storyblok logo" height="32px"> | ||
<a href="https://storyblok.com" target="_blank" rel="noopener noreferrer"> | ||
<img src="https://a.storyblok.com/f/51376/3856x824/fea44d52a9/colored-full.png" alt="Storyblok" height="32px"> | ||
</a> | ||
<a href="https://nuxtjs.org" target="_blank" rel="noopener noreferrer"> | ||
<img src="https://nuxtjs.org/logos/nuxtjs-typo-white.svg" alt="Storyblok" height="32px"> | ||
</a> | ||
</p> | ||
--- | ||
@@ -63,3 +79,3 @@ | ||
**A**: Dynamic modules are not type safe, so instead [we allow creating different stores](#composing-stores) that can be imported anywhere | ||
**A**: Dynamic modules are not type safe, so instead [we allow creating different stores](#composing-stores) that can be imported anywhere **making them dynamic by design**! | ||
@@ -71,10 +87,15 @@ ## Roadmap / Ideas | ||
- [x] Should the state be merged at the same level as actions and getters? | ||
- [ ] Flexible plugin API | ||
- [ ] Flag to remove devtools support (for very light production apps) | ||
- [ ] Allow grouping stores together into a similar structure and allow defining new getters (`pinia`) | ||
- ~~Getter with params that act like computed properties (@ktsn)~~ | ||
- [ ] Passing all getters to a getter (need Typing support) | ||
- [ ] Strict mode to disable state changes outside of actions | ||
## Installation | ||
## Usage | ||
```sh | ||
### Installation | ||
### Vue | ||
Pinia requires the Vue (or Nuxt) Composition API library. | ||
```bash | ||
yarn add pinia @vue/composition-api | ||
@@ -85,14 +106,56 @@ # or with npm | ||
Note: **The Vue Composition API plugin must be installed for Pinia to work** | ||
```js | ||
import Vue from 'vue' | ||
import VueCompositionAPI from '@vue/composition-api' | ||
import { PiniaPlugin, createPinia } from 'pinia' | ||
## Usage | ||
Vue.use(VueCompositionAPI) | ||
Vue.use(PiniaPlugin) | ||
### Creating a Store | ||
const pinia = createPinia() | ||
You can create as many stores as you want, and they should each exist in different files: | ||
new Vue({ | ||
el: '#app', | ||
pinia, | ||
// ... | ||
}) | ||
``` | ||
#### Nuxt | ||
```sh | ||
yarn add pinia @nuxtjs/composition-api | ||
# or with npm | ||
npm install pinia @nuxtjs/composition-api | ||
``` | ||
Add the pinia module after the composition api to `buildModules` in `nuxt.config.js`: | ||
```js | ||
// nuxt.config.js | ||
export default { | ||
// ... other options | ||
buildModules: ['@nuxtjs/composition-api', 'pinia/nuxt'], | ||
} | ||
``` | ||
If you are using TypeScript, you should also add the types for `context.pinia`: | ||
```json | ||
{ | ||
"include": [ | ||
// ... | ||
"pinia/nuxt/types" | ||
] | ||
} | ||
``` | ||
### Defining a Store | ||
You can define as many stores as you want, and they should each exist in different files: | ||
```ts | ||
import { createStore } from 'pinia' | ||
import { defineStore } from 'pinia' | ||
export const useMainStore = createStore({ | ||
export const useMainStore = defineStore({ | ||
// name of the store | ||
@@ -126,3 +189,3 @@ // it is used in devtools and allows restoring state | ||
`createStore` returns a function that has to be called to get access to the store: | ||
`defineStore` returns a function that has to be called to get access to the store: | ||
@@ -148,3 +211,3 @@ ```ts | ||
**There is one important rule for this to work**: the `useMainStore` (or any other _useStore_ function) must be called inside of deferred functions. This is to allow the **Vue Composition API plugin to be installed**. **Never, ever call `useStore`** like this: | ||
These functions should be called inside of `setup()` functions. If they are called outside, make sure the `createPinia()` has been called before: | ||
@@ -156,8 +219,3 @@ ```ts | ||
const main = useMainStore() | ||
export default defineComponent({ | ||
setup() { | ||
return {} | ||
}, | ||
}) | ||
const pinia = createPinia() | ||
``` | ||
@@ -177,3 +235,3 @@ | ||
router.beforeEach((to, from, next) => { | ||
if (main.state.isLoggedIn) next() | ||
if (main.$state.isLoggedIn) next() | ||
else next('/login') | ||
@@ -186,13 +244,6 @@ }) | ||
```ts | ||
export default defineComponent({ | ||
setup() { | ||
// ✅ This will work | ||
const main = useMainStore() | ||
const pinia = createPinia() | ||
const main = useMainStore() | ||
return {} | ||
}, | ||
}) | ||
// In a different file... | ||
router.beforeEach((to, from, next) => { | ||
@@ -202,3 +253,3 @@ // ✅ This will work (requires an extra param for SSR, see below) | ||
if (main.state.isLoggedIn) next() | ||
if (main.$state.isLoggedIn) next() | ||
else next('/login') | ||
@@ -216,4 +267,4 @@ }) | ||
const main = useMainStore() | ||
const text = main.name | ||
const doubleCount = main.doubleCount | ||
main.name // 'Eduardo' | ||
main.doubleCount // 2 | ||
return {} | ||
@@ -224,3 +275,3 @@ }, | ||
The `main` store in an object wrapped with `reactive`, meaning there is no need to write `.value` after getters but, like `props` in `setup`, we cannot destructure it: | ||
The `main` store in an object wrapped with `reactive`, meaning there is no need to write `.value` after getters but, like `props` in `setup`, we cannot destructure them: | ||
@@ -260,6 +311,6 @@ ```ts | ||
or call the method `patch` that allows you apply multiple changes at the same time with a partial `state` object: | ||
or call the method `$patch` that allows you apply multiple changes at the same time with a partial `state` object: | ||
```ts | ||
main.patch({ | ||
main.$patch({ | ||
counter: -1, | ||
@@ -270,4 +321,6 @@ name: 'Abalam', | ||
The main difference here is that `patch` allows you to group multiple changes into one single entry in the devtools. | ||
The main difference here is that `$patch` allows you to group multiple changes into one single entry in the devtools. | ||
This is convenient to map a state property directly to a `v-model` without creating a computed with a setter. | ||
### Replacing the `state` | ||
@@ -278,8 +331,18 @@ | ||
```ts | ||
main.state = { counter: 666, name: 'Paimon' } | ||
main.$state = { counter: 666, name: 'Paimon' } | ||
``` | ||
You can also replace all stores state by setting the state on the `pinia` object returned by `createPinia()`: | ||
```js | ||
pinia.state.value = { | ||
main: { counter: 666, name: 'Paimon' }, | ||
} | ||
``` | ||
Note: _This is useful for SSR or plugins that replace the whole state_ | ||
### SSR | ||
When writing a Single Page Application, there always only one instance of the store, but on the server, each request will create new store instances. For Pinia to track which one should be used, we rely on the _Request_ object (usually named `req`). Pinia makes this automatic in a few places: | ||
When writing a Single Page Application, there always only one instance of the store, but on the server, each request will create new store instances. For Pinia to track which one should be used, we rely on the `pinia` object Pinia makes this automatic in a few places: | ||
@@ -289,19 +352,15 @@ - actions | ||
- `setup` | ||
- `serverPrefetch` | ||
Meaning that you can call `useMainStore` at the top of these functions and it will retrieve the correct store. **Calling it anywhere else requires you to pass the current `req` to `useMainStore`**. | ||
Meaning that you can call `useMainStore()` at the top of these functions and it will retrieve the correct store. **Calling it anywhere else requires you to pass the `pinia` to `useMainStore(pinia)`**. Pinia injects itself as `$pinia` in all components, giving you access to it in `serverPrefetch()`: | ||
#### Nuxt Plugin | ||
SSR is much easier with Nuxt, and so is for Pinia: include the Pinia module in your `buildModules` in your `nuxt.config.js`: | ||
```js | ||
export default { | ||
// ... | ||
// rest of the nuxt config | ||
// ... | ||
buildModules: ['pinia/nuxt'], | ||
function serverPrefetch() { | ||
const main = useMainStore(this.$pinia) | ||
} | ||
``` | ||
#### Nuxt | ||
Make sure you added the module in [installation](#installation). | ||
By default, it will also disable Vuex so you can directly use the `store` folder for pinia. If you want to keep using Vuex, you need to provide an option in `nuxt.config.js`: | ||
@@ -311,2 +370,6 @@ | ||
export default { | ||
// ... | ||
buildModules: [ | ||
// ... | ||
], | ||
disableVuex: false, | ||
@@ -316,8 +379,8 @@ } | ||
If you are dealing with SSR, in order to make sure the correct store is retrieved by `useStore` functions, pass the current `req` to `useStore`. **This is necessary anywhere not in the list above**: | ||
If you are dealing with SSR, in order to make sure the correct store is retrieved by `useStore` functions, pass the active `pinia` to `useStore`. **This is necessary anywhere not in the list above** but only during server side rendering. This is because on the client side there is always only one pinia instance but on the server there is one pinia instance per active application on your running server: | ||
```js | ||
export default { | ||
async fetch({ req }) { | ||
const store = useStore(req) | ||
async fetch({ pinia }) { | ||
const store = useStore(pinia) | ||
}, | ||
@@ -329,3 +392,3 @@ } | ||
It may look like things are working even if you don't pass `req` to `useStore` **but multiple concurrent requests to the server could end up sharing state between different users**. | ||
It may look like things are working even if you don't pass `pinia` to `useStore` **but multiple concurrent requests to the server could end up sharing state between different users**. Fortunately you have access to `pinia` in the Nuxt Context. | ||
@@ -338,12 +401,8 @@ #### Raw Vue SSR | ||
// entry-server.js | ||
import { getRootState, PiniaSsr } from 'pinia' | ||
// install plugin to automatically use correct context in setup and onServerPrefetch | ||
Vue.use(PiniaSsr) | ||
export default context => { | ||
export default (context) => { | ||
const pinia = createPinia() | ||
/* ... */ | ||
context.rendered = () => { | ||
// pass state to context | ||
context.piniaState = getRootState(context.req) | ||
context.piniaState = pinia.state.value | ||
} | ||
@@ -365,6 +424,8 @@ /* ... */ | ||
// entry-client.js | ||
import { setStateProvider } from 'pinia' | ||
const pinia = createPinia() | ||
// install and inject pinia... | ||
// ... | ||
// inject ssr-state | ||
setStateProvider(() => window.__PINIA_STATE__) | ||
pinia.state.value = window.__PINIA_STATE__ | ||
``` | ||
@@ -374,8 +435,8 @@ | ||
You can `useOtherStore` inside a store `actions` and `getters`: | ||
You can `useOtherStore()` inside a store `actions` and `getters`: | ||
Actions are simply function that contain business logic. As with components, they **must call `useStore`** to retrieve the store: | ||
Since actions are functions that contain business logic, as in components, they **must call `useStore`** to retrieve the store: | ||
```ts | ||
createStore({ | ||
defineStore({ | ||
id: 'cart', | ||
@@ -403,19 +464,17 @@ state: () => ({ items: [] }), | ||
Composing stores may look hard at first glance but there is only one rule to follow really: | ||
If **multiple stores use each other** or you need to use **multiple stores** at the same time, you should create a **separate file** where you import all of them. | ||
If **multiple stores use each other** or you need to use **multiple stores** at the same time, you must create a **separate file** where you import all of them. | ||
If one store uses an other store, there is no need to create a new file, you can directly import it like in [the example above](#accessing-other-stores). Think of it as nesting. | ||
If one store uses an other store, there is no need to create a new file, you can directly import it. Think of it as nesting. | ||
#### Shared Getters | ||
If you need to compute a value based on the `state` and/or `getters` of multiple stores, you may be able to import all the stores but one into the remaining store, but depending on how your stores are used across your application, **this would hurt your code splitting** because importing the store that imports all others stores, would result in **one single big chunk** with all of your stores. | ||
To prevent this, **we follow the rule above** and we create a new file with a new store: | ||
To prevent this, **we create a new file with a new store** that imports the other stores: | ||
```ts | ||
import { createStore } from 'pinia' | ||
import { defineStore } from 'pinia' | ||
import { useUserStore } from './user' | ||
import { useCartStore } from './cart' | ||
export const useSharedStore = createStore({ | ||
export const useSharedStore = defineStore({ | ||
id: 'shared', | ||
@@ -433,12 +492,14 @@ getters: { | ||
<!-- TODO: redo this example with more stores to make sense and move to advanced --> | ||
#### Shared Actions | ||
When an actions needs to use multiple stores, we do the same, we create a new file with a new store: | ||
The same applies to actions: | ||
```ts | ||
import { createStore } from 'pinia' | ||
import { defineStore } from 'pinia' | ||
import { useUserStore } from './user' | ||
import { useCartStore } from './cart' | ||
export const useSharedStore = createStore({ | ||
export const useSharedStore = defineStore({ | ||
id: 'shared', | ||
@@ -462,38 +523,2 @@ state: () => ({}), | ||
#### Creating _Pinias_ | ||
_Not implemented_. Still under discussion, needs more feedback as this doesn't seem necessary because it can be replaced by shared stores as shown above. | ||
Combine multiple _stores_ (gajos) into a new one: | ||
```ts | ||
import { pinia } from 'pinia' | ||
import { useUserStore } from './user' | ||
import { useCartStore, emptyCart } from './cart' | ||
export const useCartUserStore = pinia( | ||
{ | ||
user: useUserStore, | ||
cart: useCartStore, | ||
}, | ||
{ | ||
getters: { | ||
combinedGetter () { | ||
return `Hi ${this.user.name}, you have ${this.cart.list.length} items in your cart. It costs ${this.cart.price}.`, | ||
} | ||
}, | ||
actions: { | ||
async orderCart() { | ||
try { | ||
await apiOrderCart(this.user.token, this.cart.items) | ||
this.cart.emptyCart() | ||
} catch (err) { | ||
displayError(err) | ||
} | ||
}, | ||
}, | ||
} | ||
) | ||
``` | ||
## TypeScript | ||
@@ -513,3 +538,3 @@ | ||
export const useMainStore = createStore({ | ||
export const useMainStore = defineStore({ | ||
id: 'main', | ||
@@ -516,0 +541,0 @@ state: (): MainState => ({ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
103581
27
1899
530
30
8
1