Created
February 28, 2026 18:13
-
-
Save kelsos/5f5c2ca2748e41ea5c50020cccc7d732 to your computer and use it in GitHub Desktop.
Pinia Store Cleanup Plan — rotki frontend
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Pinia Store Cleanup Plan | |
| Analysis of 47 Pinia stores (~7,500 LOC) in the rotki frontend identified several performance and design issues. This plan organizes fixes by effort and risk. | |
| --- | |
| ## Quick Wins (~30 min, low risk) | |
| ### 1. Fix `toRefs()` → `storeToRefs()` (7 files) | |
| Using `toRefs()` on a Pinia store wraps **all** properties (including actions) as refs. `storeToRefs()` only extracts reactive state and getters. | |
| | File | Line | | |
| |------|------| | |
| | `frontend/app/src/components/helper/OnboardingSettingsButton.vue` | 7 | | |
| | `frontend/app/src/composables/defi/metadata.ts` | 14 | | |
| | `frontend/app/src/composables/defi/airdrops/metadata.ts` | 14 | | |
| | `frontend/app/src/composables/info/chains.ts` | 41 | | |
| | `frontend/app/src/components/staking/kraken/KrakenStaking.vue` | 22 | | |
| | `frontend/app/src/modules/history/events/use-history-events-status.ts` | 25–26 | | |
| ### 2. Fix notification store `nextId` — expensive sort just to find max | |
| **File:** `store/notifications/index.ts:51-57` | |
| ```typescript | |
| // Current — sorts entire array to find max | |
| const nextId = computed<number>(() => { | |
| const ids = get(data).map(value => value.id).sort((a, b) => b - a); | |
| return ids.length > 0 ? ids[0] + 1 : 1; | |
| }); | |
| // Fix — use Math.max or a running counter | |
| const nextId = computed<number>(() => { | |
| const ids = get(data).map(value => value.id); | |
| return ids.length > 0 ? Math.max(...ids) + 1 : 1; | |
| }); | |
| ``` | |
| ### 3. Replace unnecessary watcher in statistics store | |
| **File:** `store/statistics/index.ts:47-53` | |
| ```typescript | |
| // Current — watcher syncing one ref to another | |
| const scrambleMultiplier = ref<number>(get(scrambleMultiplierRef) ?? 1); | |
| watchEffect(() => { | |
| const newValue = get(scrambleMultiplierRef); | |
| if (newValue !== undefined) | |
| set(scrambleMultiplier, newValue); | |
| }); | |
| // Fix — just use a computed | |
| const scrambleMultiplier = computed<number>(() => get(scrambleMultiplierRef) ?? 1); | |
| ``` | |
| --- | |
| ## Medium Effort | |
| ### 4. Fix monitor store reset leak + typing | |
| **File:** `store/monitor.ts` | |
| - Intervals stored in `Record<string, any>` are never cleared on `$reset()` — timers keep running after logout | |
| - Add a `reset()` method that calls `stop()` so the reset plugin triggers cleanup | |
| - Type `monitors` properly: `Record<string, ReturnType<typeof setInterval>>` instead of `any` | |
| ### 5. Fix notification store double-sort | |
| **File:** `store/notifications/index.ts:44-46` | |
| ```typescript | |
| // Current — sorts twice | |
| const prioritized = computed<NotificationData[]>(() => { | |
| const byDate = orderBy(get(data), ['date'], ['desc']); | |
| return orderBy(byDate, [(n) => n.priority ?? Priority.NORMAL], ['desc']); | |
| }); | |
| // Fix — single multi-key sort | |
| const prioritized = computed<NotificationData[]>(() => | |
| orderBy(get(data), [(n) => n.priority ?? Priority.NORMAL, 'date'], ['desc', 'desc']) | |
| ); | |
| ``` | |
| ### 6. Add reset cleanup to websocket + tasks stores | |
| - **`store/websocket.ts`** — ensure WebSocket disconnect on `$reset()` | |
| - **`store/tasks/index.ts`** — clear the non-reactive `handlers` registry and reset the plain `isRunning` boolean on `$reset()` | |
| --- | |
| ## Larger Refactors (discuss before starting) | |
| ### 7. Statistics store — composables created inside computed | |
| **File:** `store/statistics/index.ts:82-141` | |
| The `overall` computed creates new `useNumberScrambler` instances and new `computed()` refs **every time it re-evaluates**. Composables should be instantiated once during setup. | |
| ```typescript | |
| // Problem — inside computed body | |
| const overall = computed<Overall>(() => { | |
| // ... | |
| const scrambledNetWorth = get(useNumberScrambler({ // new instance each run! | |
| value: computed(() => totalNW), // new computed each run! | |
| })); | |
| }); | |
| ``` | |
| Fix: hoist `useNumberScrambler` calls to store setup scope; pass reactive inputs. | |
| Similarly, `getNetValue()` (line 145) returns a new `computed` per call. When called from `overall` (line 89) it creates a new computed on every re-evaluation. | |
| ### 8. Addresses-names store — nested computed factories | |
| **File:** `store/blockchain/accounts/addresses-names.ts:59-72` | |
| `ensNameSelector()` and `getEnsAvatarUrl()` are factory functions that create new computed refs per call. `getEnsAvatarUrl` even nests them — it calls `ensNameSelector(address)` which creates a computed, then wraps that in another computed. | |
| These are fine when called once per component setup, but audit callers to ensure they aren't invoked inside other computed properties or render loops. | |
| ### 9. Frontend settings store — 49 `useComputedRef` wrappers | |
| **File:** `store/settings/frontend.ts:20-68` | |
| Creates 49 individual `computed` refs from a single `settings` ref via `useComputedRef`. Each is just `computed(() => get(inp)[prop])`. Consider whether a single `reactive`/`toRefs` pattern would be cleaner. | |
| **Note:** Benchmark before changing — the current approach may be fine in practice since each computed is lightweight. The `markRaw` on the settings object is intentional for performance. | |
| --- | |
| ## Suggested PR Grouping | |
| | PR | Items | Risk | Description | | |
| |----|-------|------|-------------| | |
| | **PR 1** | 1, 2, 3 | Low | Quick wins: toRefs fix, nextId, watcher → computed | | |
| | **PR 2** | 4, 5, 6 | Low–Medium | Store reset/cleanup + notification sort | | |
| | **PR 3** | 7, 8 | Medium | Computed factory refactors (needs careful testing) | | |
| | **PR 4** | 9 | Low | Settings store (optional, benchmark first) | | |
| --- | |
| ## Additional Notes | |
| - All 47 stores use **setup syntax** (composition API) — no options API stores | |
| - Error handling is generally consistent (try/catch → `notifyError` + logger) | |
| - No circular store dependencies found | |
| - API call patterns are inconsistent across stores (direct calls, item cache, task-based) but this is a larger architectural concern, not a quick fix |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment