lobehub/.agents/skills/zustand/references/action-patterns.md
Innei fcdaf9d814 🔧 chore: update eslint v2 configuration and suppressions (#12133)
* v2 init

* chore: update eslint suppressions and package dependencies

- Removed several eslint suppressions related to array sorting and reversing from eslint-suppressions.json to clean up the configuration.
- Updated @lobehub/lint package version from 2.0.0-beta.6 to 2.0.0-beta.7 in package.json for improvements and bug fixes.
- Made minor formatting adjustments in vitest.config.mts and various SKILL.md files for better readability and consistency.

Signed-off-by: Innei <tukon479@gmail.com>

* fix: clean up import statements and formatting

- Removed unnecessary whitespace in replaceComponentImports.ts for improved readability.
- Standardized import statements in contextEngineering.ts and createAgentExecutors.ts by adding missing spaces for consistency.

Signed-off-by: Innei <tukon479@gmail.com>

* chore: update eslint suppressions and clean up code formatting

* 🐛 fix: use vi.hoisted for mock variable initialization

Fix TDZ error in persona service test by using vi.hoisted() to ensure
mock variables are available when vi.mock factory runs.

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-02-11 13:04:48 +08:00

3 KiB

Zustand Action Patterns

Optimistic Update Implementation

Standard Flow

internal_updateMessageContent: async (id, content, extra) => {
  const { internal_dispatchMessage, refreshMessages } = get();

  // 1. Immediately update frontend
  internal_dispatchMessage({
    id,
    type: 'updateMessage',
    value: { content },
  });

  // 2. Call backend
  await messageService.updateMessage(id, { content });

  // 3. Refresh for consistency
  await refreshMessages();
},

Create Operations

internal_createMessage: async (message, context) => {
  let tempId = context?.tempMessageId;
  if (!tempId) {
    tempId = internal_createTmpMessage(message);
    internal_toggleMessageLoading(true, tempId);
  }

  try {
    const id = await messageService.createMessage(message);
    await refreshMessages();
    internal_toggleMessageLoading(false, tempId);
    return id;
  } catch (e) {
    internal_toggleMessageLoading(false, tempId);
    internal_dispatchMessage({
      id: tempId,
      type: 'updateMessage',
      value: { error: { type: ChatErrorType.CreateMessageError } },
    });
  }
},

Delete Operations (No Optimistic Update)

internal_removeGenerationTopic: async (id: string) => {
  get().internal_updateGenerationTopicLoading(id, true);

  try {
    await generationTopicService.deleteTopic(id);
    await get().refreshGenerationTopics();
  } finally {
    get().internal_updateGenerationTopicLoading(id, false);
  }
},

Loading State Management

// Define in initialState.ts
export interface ChatMessageState {
  messageEditingIds: string[];
}

// Manage in action
toggleMessageEditing: (id, editing) => {
  set(
    { messageEditingIds: toggleBooleanList(get().messageEditingIds, id, editing) },
    false,
    'toggleMessageEditing',
  );
};

SWR Integration

useFetchMessages: (enable, sessionId, activeTopicId) =>
  useClientDataSWR<ChatMessage[]>(
    enable ? [SWR_USE_FETCH_MESSAGES, sessionId, activeTopicId] : null,
    async ([, sessionId, topicId]) => messageService.getMessages(sessionId, topicId),
    {
      onSuccess: (messages) => {
        const nextMap = { ...get().messagesMap, [messageMapKey(sessionId, activeTopicId)]: messages };
        if (get().messagesInit && isEqual(nextMap, get().messagesMap)) return;
        set({ messagesInit: true, messagesMap: nextMap }, false, n('useFetchMessages'));
      },
    }
  ),

// Cache invalidation
refreshMessages: async () => {
  await mutate([SWR_USE_FETCH_MESSAGES, get().activeId, get().activeTopicId]);
};

Reducer Pattern

export const messagesReducer = (state: ChatMessage[], payload: MessageDispatch): ChatMessage[] => {
  switch (payload.type) {
    case 'updateMessage': {
      return produce(state, (draftState) => {
        const index = draftState.findIndex((i) => i.id === payload.id);
        if (index < 0) return;
        draftState[index] = merge(draftState[index], {
          ...payload.value,
          updatedAt: Date.now(),
        });
      });
    }
    // ...other cases
  }
};