Vue

Lesson 08

Pinia store actions

Keep shared Vue state behind Pinia stores with actions that name writes and async transitions.

Good Code

stores/reviews.ts
import { defineStore } from "pinia";

export const useReviewsStore = defineStore("reviews", {
  state: () => ({
    reviews: [] as Review[],
    isLoading: false,
  }),
  getters: {
    pendingReviews: (state) =>
      state.reviews.filter((review) => review.status === "pending"),
  },
  actions: {
    // Actions name the write path and own the async transition.
    async loadReviews() {
      this.isLoading = true;
      try {
        this.reviews = await fetch("/api/reviews").then((response) =>
          response.json(),
        );
      } finally {
        this.isLoading = false;
      }
    },
  },
});

Bad Code

reviewState.ts
import { reactive } from "vue";

// Any importer can rewrite shared state without a named action.
export const reviewState = reactive({
  reviews: [] as Review[],
  isLoading: false,
});

export async function loadReviews() {
  reviewState.reviews = await fetch("/api/reviews").then((response) =>
    response.json(),
  );
}

Review Notes

What to review

Good Code

The good version gives shared state a store name, derived getter, and action. Components can call loadReviews without knowing every field that changes.

Bad Code

The bad version exports a mutable object and a free function. Any importer can write to state directly, bypassing one reviewable update path.

Takeaways

  • A Pinia store should expose state, derived getters, and named actions instead of letting components mutate shared state from any file.