Revalidate Docs / Revalidate Docs (push) Failing after 2s
E2E CI / Check Duplicate Run (push) Failing after 5s
Test CI / Check Duplicate Run (push) Failing after 6s
E2E CI / Test Web App (push) Has been skipped
Test CI / Test Packages (push) Has been skipped
Test CI / Test App (shard 1/3) (push) Has been skipped
Test CI / Test App (shard 2/3) (push) Has been skipped
Test CI / Test App (shard 3/3) (push) Has been skipped
Test CI / Test Desktop App (push) Has been skipped
🔄 Branch Synchronization / sync-branches (push) Failing after 11s
Test CI / Test Database (push) Has been skipped
Test CI / Merge and Upload App Coverage (push) Has been skipped
Database Schema Visualization CI / build (push) Failing after 4m14s
3.1 KiB
3.1 KiB
Zustand Slice Organization
Top-Level Store Structure
Key aggregation files:
src/store/chat/initialState.ts: Aggregate all slice initial statessrc/store/chat/store.ts: Define top-levelChatStore, combine all slice actionssrc/store/chat/selectors.ts: Export all slice selectorssrc/store/chat/helpers.ts: Chat helper functions
Store Aggregation Pattern
// src/store/chat/initialState.ts
import { ChatTopicState, initialTopicState } from './slices/topic/initialState';
import { ChatMessageState, initialMessageState } from './slices/message/initialState';
export type ChatStoreState = ChatTopicState & ChatMessageState & ...
export const initialState: ChatStoreState = {
...initialMessageState,
...initialTopicState,
...
};
// src/store/chat/store.ts
export interface ChatStoreAction
extends ChatMessageAction, ChatTopicAction, ...
const createStore: StateCreator<ChatStore, [['zustand/devtools', never]]> = (...params) => ({
...initialState,
...chatMessage(...params),
...chatTopic(...params),
});
export const useChatStore = createWithEqualityFn<ChatStore>()(
subscribeWithSelector(devtools(createStore)),
shallow
);
Single Slice Structure
src/store/chat/slices/
└── [sliceName]/
├── action.ts # Define actions (or actions/ directory)
├── initialState.ts # State structure and initial values
├── reducer.ts # (Optional) Reducer pattern
├── selectors.ts # Define selectors
└── index.ts # (Optional) Re-exports
initialState.ts
export interface ChatTopicState {
activeTopicId?: string;
topicMaps: Record<string, ChatTopic[]>;
topicsInit: boolean;
topicLoadingIds: string[];
}
export const initialTopicState: ChatTopicState = {
activeTopicId: undefined,
topicMaps: {},
topicsInit: false,
topicLoadingIds: [],
};
selectors.ts
const currentTopics = (s: ChatStoreState): ChatTopic[] | undefined => s.topicMaps[s.activeId];
const getTopicById =
(id: string) =>
(s: ChatStoreState): ChatTopic | undefined =>
currentTopics(s)?.find((topic) => topic.id === id);
// Core pattern: Use xxxSelectors aggregate
export const topicSelectors = {
currentTopics,
getTopicById,
};
Complex Actions Sub-directory
src/store/chat/slices/aiChat/
├── actions/
│ ├── generateAIChat.ts
│ ├── rag.ts
│ ├── memory.ts
│ └── index.ts
├── initialState.ts
└── selectors.ts
State Design Patterns
Map Structure for Associated Data
topicMaps: Record<string, ChatTopic[]>;
messagesMap: Record<string, ChatMessage[]>;
Arrays for Loading State
messageLoadingIds: string[]
topicLoadingIds: string[]
Optional Fields for Active Items
activeId: string
activeTopicId?: string
Best Practices
- Slice division: By functional domain (message, topic, aiChat)
- File naming: camelCase for directories, consistent patterns
- State structure: Flat, avoid deep nesting
- Type safety: Clear TypeScript interfaces for each slice