mirror of
https://github.com/langgenius/dify.git
synced 2026-06-24 13:01:16 +08:00
fix(web): stabilize deployment state hydration (#37818)
This commit is contained in:
parent
4ac8c5a30e
commit
b56e5f74e7
@ -27,25 +27,26 @@ export default async function Layout({ children }: { children: ReactNode }) {
|
||||
<OAuthRegistrationAnalytics />
|
||||
<EducationVerifyActionRecorder />
|
||||
<CommonLayoutHydrationBoundary>
|
||||
<AppContextProvider>
|
||||
<EventEmitterContextProvider>
|
||||
<ProviderContextProvider>
|
||||
<ModalContextProvider>
|
||||
<NextRouteStateBridge />
|
||||
<MainNavLayout>
|
||||
<RoleRouteGuard>
|
||||
{children}
|
||||
</RoleRouteGuard>
|
||||
</MainNavLayout>
|
||||
<InSiteMessageNotification />
|
||||
<PartnerStack />
|
||||
<ReadmePanel />
|
||||
<GotoAnything />
|
||||
<WorkflowGeneratorMount />
|
||||
</ModalContextProvider>
|
||||
</ProviderContextProvider>
|
||||
</EventEmitterContextProvider>
|
||||
</AppContextProvider>
|
||||
<NextRouteStateBridge>
|
||||
<AppContextProvider>
|
||||
<EventEmitterContextProvider>
|
||||
<ProviderContextProvider>
|
||||
<ModalContextProvider>
|
||||
<MainNavLayout>
|
||||
<RoleRouteGuard>
|
||||
{children}
|
||||
</RoleRouteGuard>
|
||||
</MainNavLayout>
|
||||
<InSiteMessageNotification />
|
||||
<PartnerStack />
|
||||
<ReadmePanel />
|
||||
<GotoAnything />
|
||||
<WorkflowGeneratorMount />
|
||||
</ModalContextProvider>
|
||||
</ProviderContextProvider>
|
||||
</EventEmitterContextProvider>
|
||||
</AppContextProvider>
|
||||
</NextRouteStateBridge>
|
||||
</CommonLayoutHydrationBoundary>
|
||||
<Zendesk />
|
||||
</>
|
||||
|
||||
@ -7,6 +7,8 @@ type NextRouteState = {
|
||||
params: NextRouteParams
|
||||
}
|
||||
|
||||
// Mirrors Next router state. NextRouteStateBridge force-hydrates this atom on
|
||||
// render so feature atoms can read route state without calling router hooks.
|
||||
const nextRouteStateAtom = atom<NextRouteState>({
|
||||
pathname: '',
|
||||
params: {},
|
||||
|
||||
@ -1,24 +1,25 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import type { NextRouteParams } from './atoms'
|
||||
import { useIsomorphicLayoutEffect } from 'foxact/use-isomorphic-layout-effect'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { useHydrateAtoms } from 'jotai/utils'
|
||||
import { useParams, usePathname } from '@/next/navigation'
|
||||
import {
|
||||
setNextRouteStateAtom,
|
||||
} from './atoms'
|
||||
|
||||
export function NextRouteStateBridge() {
|
||||
export function NextRouteStateBridge({ children }: {
|
||||
children: ReactNode
|
||||
}) {
|
||||
const pathname = usePathname()
|
||||
const params = useParams<NextRouteParams>()
|
||||
const setNextRouteState = useSetAtom(setNextRouteStateAtom)
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
setNextRouteState({
|
||||
useHydrateAtoms([
|
||||
[setNextRouteStateAtom, {
|
||||
pathname,
|
||||
params,
|
||||
})
|
||||
}, [params, pathname, setNextRouteState])
|
||||
}],
|
||||
] as const, { dangerouslyForceHydrate: true })
|
||||
|
||||
return null
|
||||
return children
|
||||
}
|
||||
|
||||
@ -43,9 +43,11 @@ function CreateReleaseCloseButton() {
|
||||
|
||||
export function CreateReleaseDialogContent() {
|
||||
return (
|
||||
<ScopeProvider atoms={[createReleaseFormAtom]}>
|
||||
<CreateReleaseDialogSurface />
|
||||
</ScopeProvider>
|
||||
<DialogContent className="top-[18dvh] w-140 max-w-[calc(100vw-32px)] translate-y-0 overflow-hidden p-0">
|
||||
<ScopeProvider atoms={[createReleaseFormAtom]} name="CreateReleaseForm">
|
||||
<CreateReleaseDialogSurface />
|
||||
</ScopeProvider>
|
||||
</DialogContent>
|
||||
)
|
||||
}
|
||||
|
||||
@ -75,7 +77,7 @@ function CreateReleaseDialogSurface() {
|
||||
}
|
||||
|
||||
return (
|
||||
<DialogContent className="top-[18dvh] w-140 max-w-[calc(100vw-32px)] translate-y-0 overflow-hidden p-0">
|
||||
<>
|
||||
<CreateReleaseCloseButton />
|
||||
<form
|
||||
noValidate
|
||||
@ -105,6 +107,6 @@ function CreateReleaseDialogSurface() {
|
||||
|
||||
<CreateReleaseActions />
|
||||
</form>
|
||||
</DialogContent>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,37 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { ScopeProvider } from 'jotai-scope'
|
||||
import { useQueryState } from 'nuqs'
|
||||
import {
|
||||
deploymentsListEnvironmentIdAtom,
|
||||
deploymentsListKeywordsAtom,
|
||||
envFilterQueryState,
|
||||
keywordsQueryState,
|
||||
} from './state'
|
||||
import { DeploymentsListStateBoundary } from './state'
|
||||
import { DeploymentsListShell } from './ui/shell'
|
||||
|
||||
function DeploymentsListStateBoundary({ children }: {
|
||||
children: ReactNode
|
||||
}) {
|
||||
const [envFilter] = useQueryState('env', envFilterQueryState)
|
||||
const [keywords] = useQueryState('keywords', keywordsQueryState)
|
||||
const stateKey = `${envFilter ?? 'all'}:${keywords}`
|
||||
|
||||
return (
|
||||
<ScopeProvider
|
||||
key={stateKey}
|
||||
atoms={[
|
||||
[deploymentsListEnvironmentIdAtom, envFilter],
|
||||
[deploymentsListKeywordsAtom, keywords],
|
||||
]}
|
||||
name="DeploymentsList"
|
||||
>
|
||||
{children}
|
||||
</ScopeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export function DeploymentsList() {
|
||||
return (
|
||||
<DeploymentsListStateBoundary>
|
||||
|
||||
@ -2,10 +2,12 @@
|
||||
|
||||
import type { ListAppInstanceSummariesResponse } from '@dify/contracts/enterprise/types.gen'
|
||||
import type { InfiniteData, QueryKey } from '@tanstack/react-query'
|
||||
import type { ReactNode } from 'react'
|
||||
import { keepPreviousData } from '@tanstack/react-query'
|
||||
import { atom } from 'jotai'
|
||||
import { atomWithInfiniteQuery, atomWithQuery } from 'jotai-tanstack-query'
|
||||
import { parseAsString } from 'nuqs'
|
||||
import { useHydrateAtoms } from 'jotai/utils'
|
||||
import { parseAsString, useQueryState } from 'nuqs'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { getNextPageParamFromPagination, SOURCE_APPS_PAGE_SIZE } from '../../shared/domain/pagination'
|
||||
import { deploymentStatusPollingInterval } from '../../shared/domain/runtime-status'
|
||||
@ -13,8 +15,24 @@ import { deploymentStatusPollingInterval } from '../../shared/domain/runtime-sta
|
||||
export const envFilterQueryState = parseAsString.withOptions({ history: 'push' })
|
||||
export const keywordsQueryState = parseAsString.withDefault('').withOptions({ history: 'push' })
|
||||
|
||||
export const deploymentsListKeywordsAtom = atom('')
|
||||
export const deploymentsListEnvironmentIdAtom = atom<string | null>(null)
|
||||
// Mirrors nuqs URL state. DeploymentsListStateBoundary force-hydrates these
|
||||
// atoms on render so query atoms can read URL filters through Jotai.
|
||||
const deploymentsListKeywordsAtom = atom('')
|
||||
const deploymentsListEnvironmentIdAtom = atom<string | null>(null)
|
||||
|
||||
export function DeploymentsListStateBoundary({ children }: {
|
||||
children: ReactNode
|
||||
}) {
|
||||
const [envFilter] = useQueryState('env', envFilterQueryState)
|
||||
const [keywords] = useQueryState('keywords', keywordsQueryState)
|
||||
|
||||
useHydrateAtoms([
|
||||
[deploymentsListEnvironmentIdAtom, envFilter],
|
||||
[deploymentsListKeywordsAtom, keywords],
|
||||
] as const, { dangerouslyForceHydrate: true })
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
function listDeploymentStatusPollingInterval(data?: InfiniteData<ListAppInstanceSummariesResponse>) {
|
||||
const rows = data?.pages?.flatMap(page =>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user