How to Add Words to Favorites Using the TypeWords Pinia Store

To add words to favorites in TypeWords, import the usePracticeStore composable from @/app/core/stores/practice and call the addFavorite(word) or toggleFavorite(word) action, which automatically persists changes to IndexedDB.

The TypeWords application manages user favorites through a dedicated Pinia store located at app/core/stores/practice.ts. This store provides a reactive favorites array and a set of actions that handle adding, removing, and toggling words—all with automatic persistence across browser sessions.

Understanding the Practice Store Structure

The practice store is defined using Pinia's defineStore('practice', …) and exposes a favorites field in its state. According to the TypeWords source code, this field typically stores full word objects or their IDs depending on the implementation.

The store implements three primary actions for favorite management:

  • addFavorite(word) — Adds a word to the favorites array if it isn't already present
  • removeFavorite(word) — Removes a word from the favorites array
  • toggleFavorite(word) — Switches a word's favorite status (adds if absent, removes if present)

Accessing the Store in Components

Before calling any favorite action, you must import and instantiate the store. The standard pattern across the TypeWords codebase uses a composable-style import:

import { usePracticeStore } from '@/app/core/stores/practice'

// Inside a Vue component or another composable
const practiceStore = usePracticeStore()

Once instantiated, the store provides full access to the favorites reactive state and all associated actions.

Adding a Word to Favorites: Basic Implementation

The simplest approach calls addFavorite() directly with a word object. This pattern appears throughout TypeWords components that implement favorite buttons:

<script setup lang="ts">
import { ref } from 'vue'
import { usePracticeStore } from '@/app/core/stores/practice'

const practice = usePracticeStore()
const currentWord = ref<Word | null>(null)

function onAddToFavorites() {
  if (currentWord.value) {
    practice.addFavorite(currentWord.value)
  }
}
</script>

<template>
  <button @click="onAddToFavorites">★ Add to Favorites</button>
</template>

When addFavorite() executes, it performs three operations:

  1. Checks if the word already exists in this.favorites (prevents duplicates)
  2. Pushes the word onto the array
  3. Calls saveFavorites() to persist to IndexedDB

Toggle Pattern for Favorite Buttons

Most TypeWords UI elements use toggle behavior rather than separate add/remove buttons. The toggleFavorite() action handles this logic internally:

import { usePracticeStore } from '@/app/core/stores/practice'
import type { Word } from '@/app/core/types'

export function useFavoriteWord(word: Word) {
  const practice = usePracticeStore()

  const isFavorited = computed(() =>
    practice.favorites.some(f => f.id === word.id)
  )

  function toggle() {
    practice.toggleFavorite(word)
  }

  return { isFavorited, toggle }
}

This composable pattern, common in app/core/composables/practice-words/, returns:

  • A reactive boolean indicating favorite status
  • A toggle function that delegates to the store action

How Persistence Works: IndexedDB Integration

The TypeWords store persists favorites automatically through idb-keyval helpers defined in app/core/utils.ts. The persistence flow works as follows:

// Simplified from app/core/stores/practice.ts
actions: {
  async init() {
    const stored = await get(FAVORITES_KEY)
    if (stored) this.favorites = JSON.parse(stored)
  },
  
  async saveFavorites() {
    await set(FAVORITES_KEY, JSON.stringify(this.favorites))
  },
  
  addFavorite(word) {
    if (!this.favorites.some(f => f.id === word.id)) {
      this.favorites.push(word)
      this.saveFavorites()  // Immediate persistence
    }
  },
  
  removeFavorite(word) {
    const index = this.favorites.findIndex(f => f.id === word.id)
    if (index > -1) {
      this.favorites.splice(index, 1)
      this.saveFavorites()  // Immediate persistence
    }
  },
  
  toggleFavorite(word) {
    const isFav = this.favorites.some(f => f.id === word.id)
    isFav ? this.removeFavorite(word) : this.addFavorite(word)
  }
}

The FAVORITES_KEY constant identifies the IndexedDB entry, ensuring favorites survive page reloads, browser restarts, and cache clears.

Integration with Practice Flow Targets

The TypeWords practice system supports targeting specific word sets, including favorites. The target field in the practice flow configuration accepts 'favorite' as a valid value, as defined in:

When target: 'favorite' is specified, the practice engine filters available words against the store's favorites array.

Summary

  • Import usePracticeStore from @/app/core/stores/practice to access favorite functionality
  • Call addFavorite(word) to add, removeFavorite(word) to remove, or toggleFavorite(word) to switch status
  • All changes auto-persist to IndexedDB via saveFavorites()—no manual storage handling required
  • The favorites array is fully reactive—components update automatically when favorite status changes
  • Practice flows can target favorites exclusively using target: 'favorite'

Frequently Asked Questions

How do I check if a word is already favorited?

Access the favorites array from the store and check for the word's ID. The common pattern uses computed() for reactivity: computed(() => practice.favorites.some(f => f.id === word.id)).

Where are favorites stored between sessions?

Favorites persist to IndexedDB using the idb-keyval library. The saveFavorites() action in app/core/stores/practice.ts handles serialization and storage automatically whenever favorites change.

Can I use toggleFavorite for a star icon button?

Yes—toggleFavorite() is the recommended approach for star toggles. It internally checks the current state and calls either addFavorite() or removeFavorite(), simplifying UI component logic.

What happens if I add the same word twice?

The addFavorite() action prevents duplicates by checking this.favorites.some(f => f.id === word.id) before pushing. Duplicate calls have no effect and don't trigger unnecessary persistence operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →