-
Read the closest existing store to match its style. Run Read src/stores/useSettingsStore.ts (closest to a simple persisted store) or src/stores/useLibraryStore.ts (closest to an API-backed store). Confirm: import order, action naming (setX, loadX, clearX, reset), and whether get() is used. Verify before proceeding to Step 2.
-
Create the file in src/stores/. Use this skeleton, replacing {Domain} and fields. Keep imports in this exact order:
import { create } from 'zustand';
interface DownloadsState {
items: DownloadItem[];
isLoading: boolean;
error: string | null;
loadDownloads: () => Promise<void>;
setItems: (value: DownloadItem[]) => void;
reset: () => void;
}
const initialState = {
items: [],
isLoading: false,
error: null,
};
export const useDownloadsStore = create<DownloadsState>()((set, get) => ({
...initialState,
loadDownloads: async () => {
set({ isLoading: true, error: null });
try {
const data = await downloadsManager.fetch();
set({ items: data, isLoading: false });
} catch (err) {
set({ error: err instanceof Error ? err.message : 'Unknown error', isLoading: false });
}
},
setItems: (value) => set({ items: value }),
reset: () => set(initialState),
}));
Verify with npm run typecheck (zero errors) before proceeding to Step 3.
-
If state must persist across launches, hydrate from AsyncStorage on first import and write through on every mutation. Match the pattern at the top of src/stores/useSettingsStore.ts:
import AsyncStorage from '@react-native-async-storage/async-storage';
const STORAGE_KEY = 'phlix_downloads';
const persist = (state: Partial<DownloadsState>) => {
AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(state)).catch(() => {});
};
export const useDownloadsStore = create<DownloadsState>()((set, get) => ({
...initialState,
hydrate: async () => {
const raw = await AsyncStorage.getItem(STORAGE_KEY);
if (raw) set(JSON.parse(raw));
},
setItems: (value) => {
set({ items: value });
persist({ items: value, ...get() });
},
}));
Call useDownloadsStore.getState().hydrate() once from src/App.tsx startup. Verify the key starts with phlix_ before proceeding to Step 4.
-
Wire the hook into src/stores/index.ts. Open the file and add a named re-export beside the existing ones — keep alphabetical order:
export { useDownloadsStore } from './useDownloadsStore';
Do NOT add a default export. Verify with Grep "useDownloadsStore" src/stores/index.ts.
-
Consume in screens/components via the barrel only. Never deep-import:
import { useDownloadsStore } from '../stores';
import { useDownloadsStore } from '../stores/useDownloadsStore';
Use selector syntax to avoid re-renders on unrelated changes:
const items = useDownloadsStore((s) => s.items);
const load = useDownloadsStore((s) => s.loadDownloads);
-
Add a Jest test at src/stores/__tests__/useDownloadsStore.test.ts mirroring existing store tests. At minimum cover: initial state, one action mutation, and reset() restoring initialState. Use useXStore.setState(initialState) in beforeEach to isolate tests.
import { useDownloadsStore } from '../useDownloadsStore';
describe('useDownloadsStore', () => {
beforeEach(() => useDownloadsStore.setState(useDownloadsStore.getInitialState()));
it('starts with initialState', () => {
expect(useDownloadsStore.getState().items).toEqual([]);
});
it('reset() restores initialState', () => {
useDownloadsStore.setState({ items: [{ id: '1' } as any] });
useDownloadsStore.getState().reset();
expect(useDownloadsStore.getState().items).toEqual([]);
});
});
Run npm test -- --testPathPattern="stores" and confirm green before claiming done.
-
Final verification gate. Run all three and confirm zero failures:
npm run typecheck
npm run lint
npm test -- --testPathPattern="stores"