refactor: migrate appDetail from Zustand to TanStack Query

- Remove appDetail and setAppDetail from Zustand store
- Use useAppDetail hook for server state management
- Child components now call useAppDetail(appId) directly via useParams()
- Replace setAppDetail calls with useInvalidateAppDetail for cache invalidation
- Keep only client UI state in Zustand (sidebar, modals)
- Split sidebar initialization useEffect for clearer separation of concerns
- Update test mocks to use TanStack Query pattern
- Fix missing dependencies in use-checklist.ts useMemo/useCallback hooks
This commit is contained in:
yyh
2026-01-18 23:07:33 +08:00
parent 7b66bbc35a
commit 91856b09ca
43 changed files with 439 additions and 382 deletions

View File

@ -1,15 +1,15 @@
import { produce } from 'immer'
import { useParams } from 'next/navigation'
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
import { useStore as useAppStore } from '@/app/components/app/store'
import { BlockEnum } from '@/app/components/workflow/types'
import { fetchWebhookUrl } from '@/service/apps'
export const useAutoGenerateWebhookUrl = () => {
const reactFlowStore = useStoreApi()
const { appId } = useParams()
return useCallback(async (nodeId: string) => {
const appId = useAppStore.getState().appDetail?.id
if (!appId)
return
@ -22,7 +22,7 @@ export const useAutoGenerateWebhookUrl = () => {
return
try {
const response = await fetchWebhookUrl({ appId, nodeId })
const response = await fetchWebhookUrl({ appId: appId as string, nodeId })
const { getNodes: getLatestNodes, setNodes } = reactFlowStore.getState()
let hasUpdated = false
const updatedNodes = produce(getLatestNodes(), (draft) => {
@ -44,5 +44,5 @@ export const useAutoGenerateWebhookUrl = () => {
catch (error: unknown) {
console.error('Failed to auto-generate webhook URL:', error)
}
}, [reactFlowStore])
}, [reactFlowStore, appId])
}

View File

@ -14,6 +14,7 @@ import type {
import type { Emoji } from '@/app/components/tools/types'
import type { DataSet } from '@/models/datasets'
import type { I18nKeysWithPrefix } from '@/types/i18n'
import { useParams } from 'next/navigation'
import {
useCallback,
useMemo,
@ -21,7 +22,6 @@ import {
} from 'react'
import { useTranslation } from 'react-i18next'
import { useEdges, useStoreApi } from 'reactflow'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useToastContext } from '@/app/components/base/toast'
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
@ -29,6 +29,7 @@ import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
import { MAX_TREE_DEPTH } from '@/config'
import { useGetLanguage } from '@/context/i18n'
import { fetchDatasets } from '@/service/datasets'
import { useAppDetail } from '@/service/use-apps'
import { useStrategyProviders } from '@/service/use-strategy'
import {
useAllBuiltInTools,
@ -96,7 +97,9 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
const { data: triggerPlugins } = useAllTriggerPlugins()
const datasetsDetail = useDatasetsDetailStore(s => s.datasetsDetail)
const getToolIcon = useGetToolIcon()
const appMode = useAppStore.getState().appDetail?.mode
const { appId } = useParams()
const { data: appDetail } = useAppDetail(appId as string)
const appMode = appDetail?.mode
const shouldCheckStartNode = appMode === AppModeEnum.WORKFLOW || appMode === AppModeEnum.ADVANCED_CHAT
const map = useNodesAvailableVarList(nodes)
@ -249,7 +252,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
})
return list
}, [nodes, nodesExtraData, edges, buildInTools, customTools, workflowTools, language, dataSourceList, getToolIcon, strategyProviders, getCheckData, t, map, shouldCheckStartNode])
}, [nodes, nodesExtraData, edges, buildInTools, customTools, workflowTools, language, dataSourceList, getToolIcon, strategyProviders, triggerPlugins, getCheckData, t, map, shouldCheckStartNode])
return needWarningNodes
}
@ -270,7 +273,9 @@ export const useChecklistBeforePublish = () => {
const { data: buildInTools } = useAllBuiltInTools()
const { data: customTools } = useAllCustomTools()
const { data: workflowTools } = useAllWorkflowTools()
const appMode = useAppStore.getState().appDetail?.mode
const { appId } = useParams()
const { data: appDetail } = useAppDetail(appId as string)
const appMode = appDetail?.mode
const shouldCheckStartNode = appMode === AppModeEnum.WORKFLOW || appMode === AppModeEnum.ADVANCED_CHAT
const getCheckData = useCallback((data: CommonNodeType<{}>, datasets: DataSet[]) => {
@ -419,7 +424,7 @@ export const useChecklistBeforePublish = () => {
}
return true
}, [store, notify, t, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData, workflowStore, buildInTools, customTools, workflowTools, shouldCheckStartNode])
}, [store, notify, t, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData, workflowStore, buildInTools, customTools, workflowTools, shouldCheckStartNode, getNodesAvailableVarList])
return {
handleCheckBeforePublish,

View File

@ -10,6 +10,7 @@ import type {
ValueSelector,
} from '../types'
import { uniqBy } from 'es-toolkit/compat'
import { useParams } from 'next/navigation'
import {
useCallback,
} from 'react'
@ -18,9 +19,9 @@ import {
getOutgoers,
useStoreApi,
} from 'reactflow'
import { useStore as useAppStore } from '@/app/components/app/store'
import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants'
import { useAppDetail } from '@/service/use-apps'
import { AppModeEnum } from '@/types/app'
import { useNodesMetaData } from '.'
import {
@ -43,7 +44,8 @@ import {
import { useAvailableBlocks } from './use-available-blocks'
export const useIsChatMode = () => {
const appDetail = useAppStore(s => s.appDetail)
const { appId } = useParams()
const { data: appDetail } = useAppDetail(appId as string)
return appDetail?.mode === AppModeEnum.ADVANCED_CHAT
}