GithubHelp home page GithubHelp logo

vuejs / pinia Goto Github PK

View Code? Open in Web Editor NEW
12.3K 65.0 965.0 7.65 MB

🍍 Intuitive, type safe, light and flexible Store for Vue using the composition api with DevTools support

Home Page: https://pinia.vuejs.org

License: MIT License

JavaScript 6.51% TypeScript 75.97% Shell 0.19% HTML 0.54% Vue 14.84% CSS 1.95%
vue composition-api ssr store vuex

pinia's Introduction

Pinia logo


npm package build status code coverage


Pinia

Intuitive, type safe and flexible Store for Vue

  • πŸ’‘ Intuitive
  • πŸ”‘ Type Safe
  • βš™οΈ Devtools support
  • πŸ”Œ Extensible
  • πŸ— Modular by design
  • πŸ“¦ Extremely light
  • ⛰️ Nuxt Module

Pinia works with both Vue 2 and Vue 3.

Pinia 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.

Help me keep working on this project πŸ’š

Silver Sponsors

VueMastery Prefect

Bronze Sponsors

Stanislas Ormières Antony Konstantinidis Storyblok Nuxt UI Pro Templates


FAQ

A few notes about the project and possible questions:

Q: Is Pinia the successor of Vuex?

A: Yes

Q: What about dynamic modules?

A: Dynamic modules are not type safe, so instead we allow creating different stores that can be imported anywhere

Installation

# or pnpm or yarn
npm install pinia

If you are using Vue <2.7, make sure to install latest @vue/composition-api:

npm install pinia @vue/composition-api

Usage

Install the plugin

Create a pinia (the root store) and pass it to app:

// Vue 3
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')
// Vue 2
import { createPinia, PiniaVuePlugin } from 'pinia'

Vue.use(PiniaVuePlugin)
const pinia = createPinia()

new Vue({
  el: '#app',
  // other options...
  // ...
  // note the same `pinia` instance can be used across multiple Vue apps on
  // the same page
  pinia,
})

For more detailed instructions, including Nuxt configuration, check the Documentation.

Create a Store

You can create as many stores as you want, and they should each exist in different files:

import { defineStore } from 'pinia'

// main is the name of the store. It is unique across your application
// and will appear in devtools
export const useMainStore = defineStore('main', {
  // a function that returns a fresh state
  state: () => ({
    counter: 0,
    name: 'Eduardo',
  }),
  // optional getters
  getters: {
    // getters receive the state as first parameter
    doubleCounter: (state) => state.counter * 2,
    // use getters in other getters
    doubleCounterPlusOne(): number {
      return this.doubleCounter + 1
    },
  },
  // optional actions
  actions: {
    reset() {
      // `this` is the store instance
      this.counter = 0
    },
  },
})

defineStore returns a function that has to be called to get access to the store:

import { useMainStore } from '@/stores/main'
import { storeToRefs } from 'pinia'

export default defineComponent({
  setup() {
    const main = useMainStore()

    // extract specific store properties
    const { counter, doubleCounter } = storeToRefs(main)

    return {
      // gives access to the whole store in the template
      main,
      // gives access only to specific state or getter
      counter,
      doubleCounter,
    }
  },
})

Documentation

To learn more about Pinia, check its documentation.

License

MIT

pinia's People

Contributors

akhigbe-e avatar awxiaoxian2020 avatar blackcrowxyz avatar bodograumann avatar danielkellyio avatar danielroe avatar dependabot-preview[bot] avatar dependabot[bot] avatar i5dr0id avatar idorenyinudoh avatar jeraldvin avatar jeremygoccc avatar kimyangofcat avatar lazzzis avatar niceplugin avatar nicodevs avatar pi0 avatar pierresaid avatar posva avatar renovate-bot avatar renovate[bot] avatar skirtles-code avatar taist24 avatar tannazma avatar theiaz avatar tkint avatar tomatoguy0502 avatar tslocke avatar walkalone0325 avatar wangenze267 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pinia's Issues

Stores cannot be used before lifecycle hooks

Reproduction

Maybe it is my misunderstanding of how stores work and it's by design but currently Vue throws a warning when I use lifecycle hooks and declare them after declaring a store. I would like to declare stores at the beginning of setup() method and then use them within my hooks.

I modified the sandbox to include the warning, if you move the lifecycle hook at the top of setup method it goes away.
https://codesandbox.io/s/pinia-vue-3-demo-forked-85iy9?file=/src/App.vue

Warning thrown:
onBeforeMount is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup(). If you are using async setup(), make sure to register lifecycle hooks before the first await statement.

Is this behavior ok? If so, am I supposed to instantiate store every time inside hooks?

Expected behavior

I would expect using stores with hooks to be without any restrictions.

Actual behavior

I have to declare lifecycle hooks before declaring stores as if they were await calls.

Pinia and Vue 3 + Vue-dev-tool-next

Noticed that Pinia's next branch has not been updated in a while and is a few commits behind the master branch (for Vue 2). As Vue 3 should be released soon-ish, will Pinia's next branch be updated in particular to support the new Vue-dev-tool-next which has timeline and plugin support?

Vue dev tools does not show the state with pinia 0.2.0

Hello, I have tried today a new version of Pinia 0.2.0 for Vue 2.

I have noticed an issue with Vue Devtools, they stopped showing state of pinia store, mutations are being displayed however state is not.

(Vue Devtools recently changed default settings for loading state to manual "Load State" button, but that should not be related to this issue)

I have setup reproduction in my repository.

Firstly after pulling a repo try to serve a master branch which has old pinia 0.1.0 which works correctly and displaying the state. (there is a clicking button in UI which increments counter)

git clone https://github.com/dyamon-cz/pinia-bug-store-state.git
cd pinia-bug-store-state
npm ci #must use ci to get data from package-lock.json and do not update packages
npm run serve

After clicking on the "click" button in the UI the state is being properly displayed.

image

Then switch to bug-devtools branch reinstall node modules and serve. This branch has pinia 0.2.0 set up in main.js based on documentation.

git checkout --track origin/bug-devtools
npm ci #must use ci to get data from package-lock.json and do not update packages
npm run serve

Now there is no state displayed after clicking on the button.

image

Thank you very much for help!

Cannot read property 'req' of undefined

Seems to happen at setActiveReq(context.ssrContext.req);

"nuxt": "^2.11.0",
"nuxt-composition-api": "^0.9.0",
"@vue/composition-api": "^0.6.1",
"pinia": "0.0.6",

universal mode

Cannot stringify POJOs with symbolic keys

When using Pinia from a fetch function in page with Nuxt, we get the following error for each Pinia store initiated.

WARN: Cannot stringify POJOs with symbolic keys Symbol(vfa.key.reactiveIdentifier)

It doesn't seem to affect the functionality, but this seems to be something that shouldn't happen...

Bug can be reproduced with a clean Nuxt installation:

npx create-nuxt-app piniabug
cd piniabug
yarn add pinia @vue/composition-api
# create a store according to example in Pinia README to stores/main.js
# setup the Nuxt plugin according to Pinia README
# setup composition-api as Nuxt plugin (see below)
# create a fetch function in the pages/index.vue file (see below)

plugins/composition-api.js

import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';

Vue.use(VueCompositionApi);

export * from '@vue/composition-api';

pages/index.vue (relevant parts)

import { useMainStore } from '@/stores/main'
...
 fetch({ req }) {
    const main = useMainStore(req);
    console.log(main.state);
  },
...

The error goes away if I use for example lodash cloneDeep on the req before passing it in to useMainStore, but that seems to break Pinia somehow, so that store modifications done during the fetch method are not available in the client-side.

interested in redux devtools support?

Screen Recording 2021-02-17 at 02 37 54 PM

I wrote a plugin that allows you to manage the Pinia store/state using the redux devtools. Is there any interest in this? Is it worth making a plugin for?

Is it possible to access Nuxt context from a pinia sotre?

First of all thanks for this awesome library.

When I'm using nuxt to build my apps, I normally tend to use Nuxt axios and auth modules for instance and have my API methods wrapped around vuex accessing this.$axios to perform HTTP requests without being worried about authentication and other configuration stuff.

I want to know if it is possible to have nuxt context injected inside my pinia store so i can keep using this pattern? If so how can I achieve this?

Thanks.

Mocking stores in component unit tests

Hi there! I had the need to mock an imported useStore function in my unit tests, and was having some trouble with setting up the mocks in the test properly. I came up with the following solution:

component.vue

<template>
    <button @click="addOne">Add 1</button>
</template>

<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import { useCounterStore } from '../stores/counter';

export default defineComponent({
    setup() {
        const counterStore = useCounterStore();

        return { addOne };

        function addOne() {
            counterStore.add(1);
        }
    }
});
</script>

component.test.js

import { shallowMount } from '@vue/test-utils';
import CounterComponent from './component.vue';
import { useCounterStore } from '../stores/counter';

describe('Component', () => {
    it('Calls the add method on the counter store', () => {
        const counterStore = useCounterStore({});
        jest.spyOn(counterStore, 'add');

        const component = shallowMount(CounterComponent);
        component.find('button').trigger('click');

        expect(counterStore.add).toHaveBeenCalledWith(1);
    });
});

Browser ESM build is broken

I'm trying to use ESM file directly from unpkg.com. The problem is that pinia.esm.js and pinia.esm.min.js both include import { ref, watch, computed } from '@vue/composition-api';.

Is this intentional? I'm actually trying it with freshly published vue 3 build.

I see that @vue/composition-api is defined as global in rollup config. Maybe for vue 3 we'd need another build that doesn't rely on @vue/composition-api.

Persist store throughout page reloads

What problem is this solving

To completely eliminate the need for Vuex, since Vuex has session persistence support via a plugin.

Proposed solution

Accept an extra object in the createStore initialization:

export const usePiniaStore = createStore({
	id: "simpleStore",
	state: () => ({
		simpleVariable: "",
	}),
	persistence: {
		enable: true,
		mode: "localSession"
	}
});

// type definition
interface IPiniaPersistence {
	enable: boolean,
	mode: "localSession" | "localStorage";		
}

Describe alternatives you've considered

There does not seem to be any Vuex-less alternatives out there that supports the vue-composition-api.

Add an option to disallow direct state modification from component

In the example, I kind of dislike the fact that the component can directly call cart.state.rawItems = [];. Just because I think that can encourage people to modify state in a disorganized manner. Can we get a plugin setting that disallows state being modified from the component (rather than an explicit action)?

Export StoreWithState interface

What problem is this solving

I am trying to create generic function for creating base stores. Something like this

interface BaseState {
  baseData: ""
};

function defineEnumerationStore<TEnumeration, TAdditionalState, TAdditionalActions>(id: string, additionalState?: TAdditionalState, additionalActions?: TAdditionalActions) {
  return defineStore({
    id: "...",
    state: () => ({
      baseData: "",
      ...additionalState
    } as BaseState & TAdditionalState),
    actions: {
      baseMethod() {
        return this.baseData;
      },
      ...additionalActions
    } //here I can't type actions object like StoreWithState<> & TAdditionalActions
  })
}

I want to be able to provide additional actions and state data to this generic function. My problem is that I can't type my extended actions object like shown in the code above. Maybe I am doing it wrong way, but I think it would help if StoreWithState interface was exported so I can use it to type my actions object.

Proposed solution

Export type:
interface StoreWithState<Id extends string, S extends StateTree>

Feature request: subscribeAction()

VueX has an option to subscribeAction. I think it would be even more relevant here. Pinia currently has subscribe() but it's just a simple wrapper over watch() and it often might not be precise enough.

Subscribing to actions would be more explicit.

https://vuex.vuejs.org/api/#subscribeaction

store.subscribeAction((action, state) => {
  console.log(action.type)
  console.log(action.payload)
})

Usecase:

In many places in the code you might want to react to userStore.state.isLoggedIn. Current store.subscribe() may fire multiple times, you'd need to check for truthy value of loggedIn, check if it's different from previous value and so on.

PiΓ±a = Peenya en InglΓ©s

Micro issue, you say:
"Pinia is is the most similar English pronunciation of the word pineapple in Spanish: piΓ±a."

"Pinia" would sound phonetically like 'peen-ee-aah', or Pin-ee-ya'.

May I suggest 'peenya' as a replacement.

It might read like this:
"PiΓ±a means pineapple in Spanish, in English it would sound like 'peenya'.

Thank you for your work.

"New Vuex Backend" VueDevTools option doesn't allow time travel

Versions

pinia: 0.0.2
Vue DevTools : 5.3.3

Description

In Vue DevTools there is an option named "New Vuex Backend" that is bringing performance boost.
But it seems there's an issue when using pinia and that it's enabled.

image

New Vuex Backend OFF
image

New Vuex Backend ON
image

It seems there's an issue about keeping the old state, which make "Commit", "Revert" & "Travel Time" not working.

Toggling off the option fix the issues.

I've inspected Vue DevTools code and it seems that the "old" one turned legacy, at least around variable names in else statements here :
https://github.com/vuejs/vue-devtools/blob/dev/packages/app-backend/src/vuex.js#L128
https://github.com/vuejs/vue-devtools/blob/dev/packages/app-backend/src/vuex.js#L207

So I don't know if it's planned to be removed, but I guess that for now :

  • pinia isn't fully compatible when the the option is enabled
    OR
  • vue-devtools may have an issue around states when the option is enabled
    OR
  • The option is not supposed to be used with pinia (but dunno, as things turn to become legacy around vue-devtools)

Still, awesome work @posva !

separating getters and actions to dedicated files break typing of `this`

I want to separate my getters and actions to dedicated files instead of just put them all together in one store file, like this:

import { defineStore } from 'pinia'
import { state } from './state'
import { getters } from './getters'
import { actions } from './actions'

export const useMainStore = defineStore({
  id: 'main',
  state: () => state,
  getters,
  actions,
})

But in the getters and actions file, I can not use this to reference the store object now.
What should I do for such a situation?
Thanks!

State not reactive when updating from external function

Maybe I'm using it wrong, but I thought something like this should work:

// component.vue

import { defineComponent } from '@vue/composition-api';

export default defineComponent({
  setup() {
    const app = useAppStore();

    const loading = app.state.loading;

    return { loading };
  })
});

// external function

function startLoading() {
    const app = useAppStore();
    app.state.loading = true;
}

Whenever this external function is called, my component doesn't update based on the state change. Only if I wrap the state in toRefs does it update correctly when being changed "from the outside":

import { defineComponent, toRefs } from '@vue/composition-api';

export default defineComponent({
  setup() {
    const app = useAppStore();

    // now it updates correctly when startLoading() is called somewhere else
    const loading = toRefs(app.state).loading;
    return { loading };
  })
});

Am I misunderstanding something here, or is this a bug?

I made a CodeSandbox with an example:
https://codesandbox.io/s/pinia-example-rbb44

Pinia makes the whole app very slow

So we have a very big and complicated order management app with 7 different stores and price recalculations on IE11 take up to 30s compared 200ms on iOS. When we removed pinia as a store and used Vue.observable() instead it went down to 30ms on IE11 and very fast on iOS

Feature: A way to define plugins

A way to create plugin to pinia. I used in my last project the vuex cache plugin and i would want for a way to create a plugin. Maybe something like:

// random-project.js
import  { usePlugin } from 'pinia'
import piniaCache from 'pinia-cache'
usePlugin(piniaCache)

// will track both stores
const a = createStore(...)
const b = createStore(...)

// pinia-cache-example.js
export function piniaCache(pinia){
   pinia.subscribe((mutation, state)=> ...)
}

This is like a listen store but global to track all state. I dont know if this is the best way but is important have ways to extend the main behavior πŸ˜„

Better documentation of Shared Getters

Hello,

I noticed in the doc one very important point:

There is one important rule for this to work: the useMainStore (or any other useStore function) must be called inside of deferred functions

Further in the doc, when speaking about shared getters, we have this example:

import { computed } from '@vue/composition-api'
import { useUserStore } from './user'
import { useCartStore } from './cart'

export const summary = computed(() => {
  const user = useUserStore()
  const cart = useCartStore()

  return `Hi ${user.state.name}, you have ${cart.state.list.length} items in your cart. It costs ${cart.price}.`
})

I tried it, by importing this shared getter from a component (note: i'm using TS):

import { createComponent } from '@vue/composition-api';
import { summary } from '@/store/shared-getters';

export default createComponent({
  name: 'Home',
  setup() {
    ...
    return { summary };
  }
});

and get an error : Error: [vue-composition-api] must call Vue.use(plugin) before using any function.

I guess it's because as soon as we import summary it gets evaluated and we fall right into the rule above : we must call it inside a deferred function

So what i had to do (but i'm not sure if this is the right way to do it) is to export summary as a function:

export default function summary() {
  return computed(() => {
    ...
  });
}

After that it worked properly. If this is intended behavior, i would recommend to document it because it may not be obvious how to implement shared getters based on the current documentation. If this is not intended behavior, i would appreciate any guidance on how to make it work 😎

Cheers
Yannick

Should the state be merged at the same level as actions and getters?

In the readme, you mention

Should the state be merged at the same level as actions and getters?

I figured we should open a discussion around this πŸ™‚


My 2 cents:

I think we should either scope all things into relevant properties, eg:

store.state.counter
store.getters.doubleCount
store.actions.reset()

or scope none

store.counter     // state
store.doubleCount // getter
store.reset       // action

Having them all merged in the "root" store might cause naming conflicts between getters and state. However, you could make the argument that having a getter with the exact same name as a property in the state is a fairly bad design decision to begin with. In the case of a naming conflict, the state one should probably overwrite the getter, as getters will often rely on the state value to produce their own value.

A big pro for having them all in the root is that you would be able to do things like

import { useStore } from '@/stores/my-store';

export default createComponent({
  setup() {
    const { counter, doubleCount, reset } = useStore();
  }
});

where you can destructure any of the 'items' in the store.

Is there any specific reason why you decided on putting actions / getters in the root currently @posva?

Problem: How to handle dynamic store on runtime?

suppose I have multiple travelers with the same state fields Name and Age.

of course, we will not create multiple store files like this:

stores/traveler-1.js
stores/traveler-2.js
stores/traveler-3.js

how can we create a traveler store dynamically with different id?


My Current Solution

// stores/traveler.js

import { createStore } from 'pinia'

export const useTravelerStore = function (name) {
    return createStore({
        id: 'traveler-' + name,

        state: () => ({
            name: '',
            age: ''
        })
    })
}

and using in Traveler.vue like this

import { useTravelerStore } from './stores/traveler'

export default {
    props: {
        name: String
    },

    setup({name}) {
        let { state } = useTravelerStore(name)()

        return { state }
    }
}

and use traveler form as:

<traveler-form name="one"></traveler-form>
<traveler-form name="two"></traveler-form>
<traveler-form name="three"></traveler-form>

is there any better solution or that way is ok?

Store state losing reactivity when used with router Vue 2

Reproduction

Hello, first of all I would like to say that I love pinia and this direction and I'm actually trying to use it in production with Vue2 and composition-api library.

I have an issue which I would like to help with. Issue can be easily reproduced with new vue-cli project, router with history mode, Typescript and Babel without class components.

Screenshot 2021-02-25 at 3 40 45 PM

Install composition-api and pinia for Vue2 and register composition-api in main.ts by Vue.use().

Next just need two component, store from documentation here on github and router.

stores/test-store.ts

import { createStore } from "pinia"; // btw can't find defineComponent method

export const useMainStore = createStore({
  // name of the store
  // it is used in devtools and allows restoring state
  id: "main",
  // a function that returns a fresh state
  state: () => ({
    counter: 0,
    name: "Eduardo",
  }),
  // optional getters
  getters: {
    doubleCount() {
      return this.counter * 2;
    },
    doubleCountPlusOne() {
      return this.doubleCount * 2;
    },
  },
  // optional actions
  actions: {
    reset() {
      // `this` is the store instance
      this.counter = 0;
    },
  },
});

views/MainComponent.vue

<template>
  <div>
    <h1>{{ main.counter }}</h1>
    <router-link to="/second" tag="button">go</router-link>
    <button @click="main.counter++">click</button>
  </div>
</template>

<script>
import { useMainStore } from "@/stores/test-store";
import { computed, defineComponent } from "@vue/composition-api";

export default defineComponent({
  setup() {
    const main = useMainStore();
    console.log(main.counter);
    main.counter = main.counter + 1;
    console.log("count");
    console.log(main.counter);

    return {
      // gives access to the whole store
      main,
      // gives access only to specific state
      state: computed(() => main.counter),
      // gives access to specific getter; like `computed` properties
      doubleCount: computed(() => main.doubleCount),
    };
  },
});
</script>

views/SecondComponent.vue

<template>
  <div>
    <h1>Test</h1>
  </div>
</template>

<script>
import { computed, defineComponent } from "@vue/composition-api";

export default defineComponent({
  setup() {
    return {};
  },
});
</script>

router/index.ts

import Vue from "vue";
import VueRouter, { RouteConfig } from "vue-router";
import MainComponent from "../views/MainComponent.vue";
import SecondComponent from "../views/SecondComponent.vue";

Vue.use(VueRouter);

const routes: Array<RouteConfig> = [
  {
    path: "/",
    name: "Main",
    component: MainComponent,
  },
  {
    path: "/second",
    name: "Second",
    component: SecondComponent,
  },
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes,
});

export default router;

After setting up the app there are to buttons, go to next route button and click button, there is also logic in MainComponent.vue setup() function to increment counter every time and to console.log() to make sure setup function is being called.

When you serve the app everything works fine, counter gets automatically incremented to 1 and when you click on button it keeps incrementing. However when you get to the second route using go button and back using history back button, setup function is triggered but counter is not incremented and click button also not incrementing anymore. Event of the click button is being fired just reactivity of store state seems broken. (even if you not use browser back history btn but setup router-link in the next component and redirect back it's gonna be the same).

Any ideas what could be the issue and potential workaround?

Thank you very much for help!

Screen.Recording.2021-02-25.at.4.13.52.PM.mov

(v1) Deploy changes to NPM

Currently the latest NPM version is 0.1.0, which still uses the old API for creating and registering stores. Following the README tutorial gives errors since these changes have not yet been published to NPM.

If these changes are something you are still working on, sorry for this issue, but I wanted to create this in case it was missed.

Details

  • Version: 0.1.0
  • Vue Version: 2.6.12

Document testing configuration

I was looking for a way to reset all stores completely in order to write tests for my store, and found that you use setActiveReq({}) in the tests for Pinia itself. Is this the recommended way of testing stores created with Pinia?

The following works exactly as needed, but I'm not sure what setActiveReq is doing in this case or if I should be using it to begin with.

import Vue from 'vue';
import VueCompositionAPI from '@vue/composition-api';
import { useMainStore } from '@/stores/main';
import { setActiveReq } from 'pinia';

describe('Stores', () => {
	beforeAll(() => {
		Vue.use(VueCompositionAPI);
	});

	beforeEach(() => {
		setActiveReq({});
	});

	describe('Actions / reset', () => {
		it('Sets counter to 0', () => {
			const store = useMainStore();
			store.state.counter = 25;
			store.reset();
			expect(store.state.counter).toBe(0);
		});
	});
});

Patch does not update "deep" state

In my application, I return a user object when authenticated:

{
    "id": 1,
    "username": "googlemac",
    "hasAccess": true,
    "roles": {
        "isAdmin": true,
    },
}

My issue is that updating the user state with store.patch() does not have the desired effect for "deep" data. All of the data is correctly updated (shown in devtools) and the username (regular state) in the UI is updated, but trying to reference state.roles.isAdmin is not reactive. The getter is also not reactive. If I instead update the state directly, accessing the state directly or through the getter works properly.

I have defined my store:

function state() {
    return {
        employeeId: 0,
        username: '',
        hasAccess: false,
        roles: {},
    } as User;
}

export type RootState = ReturnType<typeof state>;

const getters = {
    isRestricted: (state: RootState) => !state.hasAccess,
    isAdmin: (state: RootState) => state.roles?.isAdmin ?? false,
};

export const fetchUser = async () => {
    const store = useUserStore();

    const { data } = await axios.get('/api/user');
    const { user } = data;

    // NOTE: this does not work!
    // store.patch(user);

    // NOTE: this works as desired
    store.state.employeeId = user.employeeId;
    store.state.username = user.username;
    store.state.hasAccess = user.hasAccess;
    store.state.roles = user.roles;
};

Also, is there a way to make the getter state already typed with the root state? It's only one line and not that big of a deal, but it may help. If I don't put this, I get a TS error that it is any type.

v1 can't see pinia store in devtools

Reproduction

i trying to examine pinia in existing project with vue 2.6.12, vuex 3.6.0 , @vue/composition-api: 1.0.0-beta.25 . i can see state in console, but not see in dev tools. Should i some register in main.ts (App.use) or dev tools support not work with existing vuex?

What is needed to support getters with parameters?

Hi there!

What would need to be done in order to support getters with parameters, and how should they work ideally?

How do they differ from actions? Is there a way where you could return a computed from an action in order to mimic the same functionality?

[v2] actions seem to be missing on the store object

I just gave Pinia a quick try in a Vue 3 app and noticed I couldn't use actions:

import { defineStore } from "pinia";

export const useSessionStore = defineStore({
  id: 'session',
  state: () => ({
    isLoggedIn: false,
  }),
  actions: {
    setLoggedIn() {
      this.isLoggedIn = true;
    }
  }
});
console.log(Object.keys(sessionStore));
//Β ["$id", "_r", "$state", "$patch", "$subscribe", "$reset", "isLoggedIn"]

[Question] Usage outside of Vue component

Heya! I've been playing around with Pinia lately, and am really in love with it so far. I do have one question though, the docs state very clearly that you can only call the store compositions from inside the deferred setup function of Vue components:

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:

import { useMainStore } from '@/stores/main'
// ❌ Depending on where you do this it will fail
// so just don't do it
const main = useMainStore()

export default defineComponent({
  setup() {
    return {}
  },
})

However, does that mean that it's completely impossible to read the store from other files? For example, my use case would be to read a value from the store in a navigation guard in my Vue Router router.js file.

In the tests, you seem to use the store without mocking the setup function, but I might be mistaken:

https://github.com/posva/pinia/blob/master/__tests__/actions.spec.ts#L52

First parameter of getters should be the state

The object in the first param in a getter used to be a state. Now (in 0.1.0) it seems to be the whole store, yet the TS type for it still is DataState. Readme suggests using this in getters but I still like the shorthand arrow functions:

getters: {
  admins: state => state.users.filter(user => user.role === 'admin'),
}

Screenshot 2020-10-03 at 18 15 26

Permit to specify the State structure when using createStore

Feature request

According to this twitter discussion

Need

In order to getting more solid store typing, it could be nice to specify the type which will reflect the store. Currently, the state is inferred by the state initialization.
To be honest, it's already possible by this way :

type MainStare = {
  counter: number;
  name: string;
}

export const useMainStore = createStore<string, MainState>({
  id: 'main',
  state: () => ({
    counter: 0,
    name: 'Edimitchel',
  }),
  // ...
})

As you can see, the current API forced us to specify the first generic type (see here).

My request

My simple request is following : Could it be possible to swap these two lines ?
If accepted, I would create the PR

The result would be like this (with a better usage if we have nested or optional state) :

export type MainStare = {
  counter: number;
  name: string;
  team?: {
    name: string;
    createdAt?: Date;
  }
}

export const useMainStore = createStore<MainState>({
  id: 'main',
  state: () => ({
    counter: 0,
    name: 'Edimitchel',
    team: {
      name: 'Vue Community'
    }
  }),
  // ...
})

I guess, following could be subject to another issue by amending the createStore signature :

export type MainStare = {
  counter: number;
  name: string;
  team?: {
    name: string;
    createdAt?: Date;
  }
}

export const useMainStore = createStore<MainState>('main', {
  state: () => ({
    counter: 0,
    name: 'Edimitchel',
    team: {
      name: 'Vue Community'
    }
  }),
  // ...
})

problem with linked dependency

We have a package A with pinia dependency and we have a package B with a package A dependency.

If you install normally via npm it works. But if you link package A with npm link only the stores in package A still work. Packages in B are missing the "register module – ModuleName" console logs and do not show up in devtools. The mutations show up in devtools but no actual data is found as the store are not found. No error message is displayed.

Typescript : Unable to use complex types in state

Hi,

I'm struggling to make the state type-aware of my TS interfaces

types.d.ts

export interface Rule {
  name: string;
  category: string;
}

rule-store.ts

export const useRuleStore = createStore(
  'rules',
  () => ({
    rules: [] as Rule[],
  }),
  {},
);

I get a TS error :

Type 'Rule[]' is not assignable to type 'StateTreeValue'.
  Type 'Rule[]' is not assignable to type 'StateTreeArray'.
    The types returned by 'pop()' are incompatible between these types.
      Type 'Rule | undefined' is not assignable to type 'string | number | boolean | symbol | void | Function | StateTree | StateTreeArray | JSONSerializable | null | undefined'.
        Type 'Rule' is not assignable to type 'string | number | boolean | symbol | void | Function | StateTree | StateTreeArray | JSONSerializable | null | undefined'.
          Type 'Rule' is not assignable to type 'StateTree'.
            Index signature is missing in type 'Rule'.ts(2322)
types.d.ts(6, 18): The expected type comes from property 'rules' which is declared here on type 'StateTree'

If i replace rule-store.ts by the following:

export const useRuleStore = createStore(
  'rules',
  () => ({
    rules: [] as any[],
  }),
  {},
);

It works, but then i lose the main purpose of using TS as state.rules will not be typed ...

What am I doing wrong ?

Thanks for the help !
Best regards

Pinia stopped working after updating Nuxt to 2.13 (and beyond)

Hello,

First: thanks for the work put in this library, it really is a fresh breeze after using vuex for some time.

To the point:

I have just recently updated our project to new version of nuxt (v.2.13) and I have encountered a problem with pinia.
Here is the transcript of the error:

TypeError: Cannot read property 'control' of undefined
    at getInitialState (pinia.esm.js?d071:119)
    at useStore (pinia.esm.js?d071:257)
    at setup (default.vue?2e42:40)
    at mergedSetupFn (vue-composition-api.module.js?750b:418)
    at eval (vue-composition-api.module.js?750b:637)
    at activateCurrentInstance (vue-composition-api.module.js?750b:569)
    at initSetup (vue-composition-api.module.js?750b:636)
    at VueComponent.wrappedData (vue-composition-api.module.js?750b:623)
    at getData (vue.runtime.esm.js?2b0e:4748)
    at initData (vue.runtime.esm.js?2b0e:4705)

It seems like an issue in this function:

function getInitialState(id) {
    var provider = stateProviders.get(getActiveReq());
    return provider && provider()[id];
}

I am not sure, but it seems to me it could be somehow related to work done on integrating the composition-api guys just recently did in the nuxt project.

Any insights on how to make this work again?

Thanks!

What's the advantages of using this library ?

Not trying to offend, trying to understand do we really need a state management library for Vue 3?
I mean, the example code in the first page:

import { createStore } from 'pinia'

export const useMainStore = createStore({
  // name of the store
  // it is used in devtools and allows restoring state
  id: 'main',
  // a function that returns a fresh state
  state: () => ({
    counter: 0,
    name: 'Eduardo',
  }),
  // optional getters
  getters: {
    doubleCount: (state, getters) => state.counter * 2,
    // use getters in other getters
    doubleCountPlusOne: (state, { doubleCount }) => doubleCount.value * 2,
  },
  // optional actions
  actions: {
    reset() {
      // `this` is the store instance
      this.state.counter = 0
    },
  },
})

Can be replaced with:

import { computed, reactive } from '@vue/composition-api';

const state = reactive({
    counter: 0,
    name: 'Eduardo',
});

// Getters / Computed
const doubleCount = computed(() => state.counter * 2);
const doubleCountPlusOne = computed(() => doubleCount * 2 + 1);

// Actions
const reset = () => state.counter = 0;

export { state, doubleCount, doubleCountPlusOne, reset };

and it will work mostly the same.
Are there any advantages for pinia over the example I attached ?

Persistent Support

Hi,

i'm newest on Vue and Pinia bit this Code is great.

My question is, Exists support for permanent storage or and example how implement with pinia and vue3?

Access global properties in Store

There is currently no documented way to access Vue root plugins, such as this.$axios or this.$router like I might do in Nuxt. I feel that this is a very common use case since the Vue plugin ecosystem is large.

Since binding this is not the way forward, can we have a way to get at the root? I could see it exposed similar to how context is in setup(). I can't think of a way to achieve backwards compatibility with the current API. I also don't see how to access it since the function is used directly (imported and called) and there's no API on the outside. This makes me think that the API would have to exist on the inside of the function much like accessing a store is done:

export async function appendFile(file: File): Promise<void> {
    const store = useFilesStore();
    store.state.all.push(file);
}

Perhaps plugins have to be defined differently in order to be accessed here. All existing plugins would either define a new composition API or a dev could define their own composition wrapper on top of any that haven't converted.

Serializing the state

Should pinia serialize the state with JSON.stringify?

  • For v2 Vue 3 might expose serialization and hydration helpers, so it depends on what is exposed
  • For v1, it's probably worth doing and adapting the plugins

Typing breaks when using non-string 'id' state property

Reproduction

export const useMainStore = defineStore({
  id: "main",
  state: () => ({
    id: 1,
    counter: 0,
  }),
  getters: {
    doubleCount() {
      //this works
      return this.counter * 2;
    },
  },
  actions: {
    reset() {
      //this no longer works, 'this' is not typed to store instance
      this.counter = 0;
    }
  }
});

Steps to reproduce the behavior

  1. Define store with state property 'id' and some type other than string
  2. Define action and try to access the store
  3. 'this' is not typed to instance of store, so cannot access state or patch it

Expected behavior

I should be able to define any properties with any types in the state.

Additional information

Most likely it's a collision with 'id' property on store itself which is typed as string. It works as expected in getters just actions are broken.

TS Error 4023 - Exported variable 'useMainStore' has or is using name 'StoreWithState'

Reproduction

Error: Exported variable 'useMainStore' has or is using name 'StoreWithState' from external module "/Users/steven/Documents/New Documents Dec 2020/boilerplate/versiontwo/node_modules/pinia/dist/pinia" but cannot be named.ts(4023)

image

Steps to reproduce the behavior

Installed pinia@next and added it to main.ts and created a new file with the example code.

main.ts

import { createApp } from "vue";
import "./assets/main.css";
import App from "./App.vue";
import { routes } from "./routes";
import { createRouter, createWebHistory } from "vue-router";
import { store } from "./store";
import { createPinia } from "pinia";

const app = createApp(App);
app.use(createPinia());
app.use(store);

export const router = createRouter({
  history: createWebHistory(),
  routes,
});

app.use(router);
app.mount("#app");

piniaStore.ts

import { defineStore } from "pinia";

export const useMainStore = defineStore({
  // name of the store
  // it is used in devtools and allows restoring state
  id: "main",
  // a function that returns a fresh state
  state: () => ({
    counter: 0,
    name: "Eduardo",
  }),
  // optional getters
  getters: {
    doubleCount() {
      return this.counter * 2;
    },
    // use getters in other getters
    doubleCountPlusOne() {
      return this.doubleCount * 2;
    },
  },
  // optional actions
  actions: {
    reset() {
      // `this` is the store instance
      this.counter = 0;
    },
  },
});

Nuxt crashes in SPA mode

webpack-internal:///./.nuxt/client.js:136 TypeError: Cannot read property 'pinia' of undefined
    at eval (webpack-internal:///./.nuxt/pinia.js:35)
    at getInitialState (webpack-internal:///./node_modules/pinia/dist/pinia.esm.js:125)
    at useStore (webpack-internal:///./node_modules/pinia/dist/pinia.esm.js:263)
    at useApi (webpack-internal:///./client/utils/api.ts:241)
    at setup (webpack-internal:///./node_modules/babel-loader/lib/index.js?!./node_modules/ts-loader/index.js?!./node_modules/vue-loader/lib/index.js?!./client/components/layout/topbar/Topbar.vue?vue&type=script&lang=ts&:176)
    at mergedSetupFn (webpack-internal:///./node_modules/@vue/composition-api/dist/vue-composition-api.module.js:443)
    at $options.setup (webpack-internal:///./.nuxt/pinia.js:22)
    at eval (webpack-internal:///./node_modules/@vue/composition-api/dist/vue-composition-api.module.js:662)
    at activateCurrentInstance (webpack-internal:///./node_modules/@vue/composition-api/dist/vue-composition-api.module.js:594)
    at initSetup 

It crashes on this line because nuxtState seems to be undefined in SPA mode.
https://github.com/posva/pinia/blob/master/nuxt/plugin.js#L20

In universal mode it's all good:)

Function called in different places

Well i have 2 pages that use useLoginStore

//layout.vue
useLoginStore({ onSuccessLogin: () => console.log('test layout.vue') });

//login.vue
const loginStore = useLoginStore({
      onSuccessLogin: () => {
         console.log("teste login.vue");
         vm.root.$router.push({ name: "home" });
      },
});

In the first time everything is ok, but if i reload when i go to login.vue he executes the onSucessLogin from the layout.vue.

My useLoginStore is:

export const useLoginStore = (params: { onSuccessLogin?: Function }) =>
  createStore({
    id: "login",
    state: () => ({
      loading: false,
      loadingUser: false,
      user: {
        id: null as number | null,
        email: "",
      },
    }),
    getters: {
      userEmail: (state) => state.user.email,
      userFirstLetter: (state) => state.user.email.slice(0, 1),
    },
    actions: {
      async submit(form: Login) {
        if (params.onSuccessLogin) params.onSuccessLogin();",
      },
    },
  })();

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.