Hone logo
Hone
Problems

Vue Store Composition with Pinia

This challenge focuses on building a robust and scalable state management solution for a Vue 3 application using Pinia's composition API. You will learn how to define, organize, and access stores effectively, making your application's state management more maintainable and testable.

Problem Description

Your task is to create a modular state management system for a hypothetical e-commerce application using Pinia. You need to define multiple stores that represent different aspects of the application's state, such as user authentication, product catalog, and shopping cart. These stores should be composable, meaning they can be easily imported and used within different Vue components.

Key Requirements:

  • Define Multiple Stores: Create at least three distinct Pinia stores:
    • useAuthStore: Manages user authentication state (e.g., logged-in status, user profile).
    • useProductStore: Manages the list of available products, including fetching and filtering.
    • useCartStore: Manages the items in the shopping cart, including adding, removing, and updating quantities.
  • State Definition: Each store should have its own state properties relevant to its domain.
  • Getters: Implement computed properties (getters) within stores to derive values from the state (e.g., total cart items, formatted product names).
  • Actions: Define methods (actions) to modify the state and perform asynchronous operations (e.g., logging in a user, fetching products from an API, adding an item to the cart).
  • Composition: Demonstrate how to compose these stores within a Vue component. A single component should be able to access and interact with multiple stores simultaneously.
  • Type Safety: Ensure all store definitions and usage are strongly typed using TypeScript.

Expected Behavior:

  • Components should be able to access and display state from any of the defined stores.
  • Actions should correctly update the relevant store's state.
  • Getters should return correct derived values based on the current state.
  • The application should be able to handle adding multiple items to the cart, updating quantities, and calculating totals.

Edge Cases:

  • Handling an empty product catalog.
  • Handling an empty shopping cart.
  • Ensuring state is correctly reset or managed when a user logs out.

Examples

Example 1: useAuthStore Usage in a Component

Input:

Imagine a UserProfile.vue component.

// UserProfile.vue
<template>
  <div>
    <div v-if="authStore.isLoggedIn">
      Welcome, {{ authStore.user?.name }}!
      <button @click="authStore.logout()">Logout</button>
    </div>
    <div v-else>
      <button @click="authStore.login({ name: 'Alice', id: '123' })">Login</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { useAuthStore } from '@/stores/useAuthStore';

const authStore = useAuthStore();
</script>

Output:

Initially, if no user is logged in, the "Login" button is displayed. Clicking "Login" updates the state, and the "Welcome, Alice! Logout" message appears. Clicking "Logout" reverts to the login state.

Explanation: The component directly uses useAuthStore() to access its state (isLoggedIn, user) and actions (login, logout).

Example 2: useProductStore and useCartStore Composition

Input:

Imagine a ProductList.vue component that displays products and allows adding them to the cart.

// ProductList.vue
<template>
  <div>
    <h2>Products</h2>
    <div v-if="productStore.isLoading">Loading products...</div>
    <ul v-else>
      <li v-for="product in productStore.products" :key="product.id">
        {{ product.name }} - ${{ product.price }}
        <button @click="cartStore.addItem(product)">Add to Cart</button>
      </li>
    </ul>

    <h2>Cart</h2>
    <div v-if="cartStore.cartItems.length === 0">Your cart is empty.</div>
    <ul v-else>
      <li v-for="item in cartStore.cartItems" :key="item.product.id">
        {{ item.product.name }} x {{ item.quantity }} - ${{ item.totalPrice.toFixed(2) }}
      </li>
      <li>Total: ${{ cartStore.totalCartPrice.toFixed(2) }}</li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { onMounted } from 'vue';
import { useProductStore } from '@/stores/useProductStore';
import { useCartStore } from '@/stores/useCartStore';

const productStore = useProductStore();
const cartStore = useCartStore();

onMounted(() => {
  productStore.fetchProducts();
});
</script>

Output:

The component displays a list of products fetched by productStore. Each product has an "Add to Cart" button. Below, it displays the current items in the cart, managed by cartStore, along with the total price. Clicking "Add to Cart" for a product adds it to the cart and updates the cart display.

Explanation: This component demonstrates composing useProductStore (for fetching and displaying products) and useCartStore (for managing and displaying cart items) within a single component.

Constraints

  • Vue Version: Vue 3.x
  • State Management Library: Pinia
  • Language: TypeScript
  • Asynchronous Operations: Simulate API calls for product fetching using setTimeout.
  • No External Libraries: Beyond Vue and Pinia.

Notes

  • Consider how to structure your stores directory for optimal organization.
  • Think about how to handle potential errors during asynchronous operations (e.g., product fetching failure).
  • Ensure your TypeScript types accurately reflect the shape of your state, getters, and actions.
  • The fetchProducts action in useProductStore should simulate a network request, returning a predefined list of products after a short delay.
  • The addItem action in useCartStore should handle cases where the item is already in the cart (increment quantity) versus adding a new item.
Loading editor...
typescript