fix: toggling AppDetailNav causes unnecessary component rerenders (#3718)

This commit is contained in:
legao 2024-04-24 04:07:28 +00:00 committed by GitHub
parent 9eebe9d54e
commit 40e36e9b52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 314 additions and 276 deletions

View File

@ -5,6 +5,7 @@ import React, { useCallback, useEffect, useState } from 'react'
import { usePathname, useRouter } from 'next/navigation' import { usePathname, useRouter } from 'next/navigation'
import cn from 'classnames' import cn from 'classnames'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useShallow } from 'zustand/react/shallow'
import s from './style.module.css' import s from './style.module.css'
import { useStore } from '@/app/components/app/store' import { useStore } from '@/app/components/app/store'
import AppSideBar from '@/app/components/app-sidebar' import AppSideBar from '@/app/components/app-sidebar'
@ -32,7 +33,11 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
const media = useBreakpoints() const media = useBreakpoints()
const isMobile = media === MediaType.mobile const isMobile = media === MediaType.mobile
const { isCurrentWorkspaceManager } = useAppContext() const { isCurrentWorkspaceManager } = useAppContext()
const { appDetail, setAppDetail, setAppSiderbarExpand } = useStore() const { appDetail, setAppDetail, setAppSiderbarExpand } = useStore(useShallow(state => ({
appDetail: state.appDetail,
setAppDetail: state.setAppDetail,
setAppSiderbarExpand: state.setAppSiderbarExpand,
})))
const [navigation, setNavigation] = useState<Array<{ const [navigation, setNavigation] = useState<Array<{
name: string name: string
href: string href: string

View File

@ -26,7 +26,8 @@ export type ICardViewProps = {
const CardView: FC<ICardViewProps> = ({ appId }) => { const CardView: FC<ICardViewProps> = ({ appId }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const { appDetail, setAppDetail } = useAppStore() const appDetail = useAppStore(state => state.appDetail)
const setAppDetail = useAppStore(state => state.setAppDetail)
const updateAppDetail = async () => { const updateAppDetail = async () => {
fetchAppDetail({ url: '/apps', id: appId }).then((res) => { fetchAppDetail({ url: '/apps', id: appId }).then((res) => {

View File

@ -22,7 +22,7 @@ export type IChartViewProps = {
export default function ChartView({ appId }: IChartViewProps) { export default function ChartView({ appId }: IChartViewProps) {
const { t } = useTranslation() const { t } = useTranslation()
const { appDetail } = useAppStore() const appDetail = useAppStore(state => state.appDetail)
const isChatApp = appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow' const isChatApp = appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow'
const isWorkflow = appDetail?.mode === 'workflow' const isWorkflow = appDetail?.mode === 'workflow'
const [period, setPeriod] = useState<PeriodParams>({ name: t('appLog.filter.period.last7days'), query: { start: today.subtract(7, 'day').format(queryDateFormat), end: today.format(queryDateFormat) } }) const [period, setPeriod] = useState<PeriodParams>({ name: t('appLog.filter.period.last7days'), query: { start: today.subtract(7, 'day').format(queryDateFormat), end: today.format(queryDateFormat) } })

View File

@ -213,7 +213,7 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
document.title = `${datasetRes.name || 'Dataset'} - Dify` document.title = `${datasetRes.name || 'Dataset'} - Dify`
}, [datasetRes]) }, [datasetRes])
const { setAppSiderbarExpand } = useStore() const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand)
useEffect(() => { useEffect(() => {
const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand' const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'

View File

@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react' import React, { useEffect } from 'react'
import { useShallow } from 'zustand/react/shallow'
import NavLink from './navLink' import NavLink from './navLink'
import type { NavIcon } from './navLink' import type { NavIcon } from './navLink'
import AppBasic from './basic' import AppBasic from './basic'
@ -26,11 +27,13 @@ export type IAppDetailNavProps = {
} }
const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInfo, iconType = 'app' }: IAppDetailNavProps) => { const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInfo, iconType = 'app' }: IAppDetailNavProps) => {
const { appSidebarExpand, setAppSiderbarExpand } = useAppStore() const { appSidebarExpand, setAppSiderbarExpand } = useAppStore(useShallow(state => ({
appSidebarExpand: state.appSidebarExpand,
setAppSiderbarExpand: state.setAppSiderbarExpand,
})))
const media = useBreakpoints() const media = useBreakpoints()
const isMobile = media === MediaType.mobile const isMobile = media === MediaType.mobile
const [modeState, setModeState] = useState(appSidebarExpand) const expand = appSidebarExpand === 'expand'
const expand = modeState === 'expand'
const handleToggle = (state: string) => { const handleToggle = (state: string) => {
setAppSiderbarExpand(state === 'expand' ? 'collapse' : 'expand') setAppSiderbarExpand(state === 'expand' ? 'collapse' : 'expand')
@ -39,9 +42,9 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf
useEffect(() => { useEffect(() => {
if (appSidebarExpand) { if (appSidebarExpand) {
localStorage.setItem('app-detail-collapse-or-expand', appSidebarExpand) localStorage.setItem('app-detail-collapse-or-expand', appSidebarExpand)
setModeState(appSidebarExpand) setAppSiderbarExpand(appSidebarExpand)
} }
}, [appSidebarExpand]) }, [appSidebarExpand, setAppSiderbarExpand])
return ( return (
<div <div
@ -61,7 +64,7 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf
)} )}
{iconType !== 'app' && ( {iconType !== 'app' && (
<AppBasic <AppBasic
mode={modeState} mode={appSidebarExpand}
iconType={iconType} iconType={iconType}
icon={icon} icon={icon}
icon_background={icon_background} icon_background={icon_background}
@ -81,10 +84,10 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf
> >
{navigation.map((item, index) => { {navigation.map((item, index) => {
return ( return (
<NavLink key={index} mode={modeState} iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} /> <NavLink key={index} mode={appSidebarExpand} iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} />
) )
})} })}
{extraInfo && extraInfo(modeState)} {extraInfo && extraInfo(appSidebarExpand)}
</nav> </nav>
{ {
!isMobile && ( !isMobile && (
@ -96,7 +99,7 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf
> >
<div <div
className='flex items-center justify-center w-6 h-6 text-gray-500 cursor-pointer' className='flex items-center justify-center w-6 h-6 text-gray-500 cursor-pointer'
onClick={() => handleToggle(modeState)} onClick={() => handleToggle(appSidebarExpand)}
> >
{ {
expand expand

View File

@ -6,6 +6,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'
import { setAutoFreeze } from 'immer' import { setAutoFreeze } from 'immer'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { useShallow } from 'zustand/react/shallow'
import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api' import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api'
import FormattingChanged from '../base/warning-mask/formatting-changed' import FormattingChanged from '../base/warning-mask/formatting-changed'
import GroupName from '../base/group-name' import GroupName from '../base/group-name'
@ -367,7 +368,12 @@ const Debug: FC<IDebug> = ({
handleVisionConfigInMultipleModel() handleVisionConfigInMultipleModel()
}, [multipleModelConfigs, mode]) }, [multipleModelConfigs, mode])
const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal } = useAppStore() const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal } = useAppStore(useShallow(state => ({
currentLogItem: state.currentLogItem,
setCurrentLogItem: state.setCurrentLogItem,
showPromptLogModal: state.showPromptLogModal,
setShowPromptLogModal: state.setShowPromptLogModal,
})))
const [width, setWidth] = useState(0) const [width, setWidth] = useState(0)
const ref = useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)

View File

@ -8,6 +8,7 @@ import produce from 'immer'
import { useBoolean, useGetState } from 'ahooks' import { useBoolean, useGetState } from 'ahooks'
import { clone, isEqual } from 'lodash-es' import { clone, isEqual } from 'lodash-es'
import { CodeBracketIcon } from '@heroicons/react/20/solid' import { CodeBracketIcon } from '@heroicons/react/20/solid'
import { useShallow } from 'zustand/react/shallow'
import Button from '../../base/button' import Button from '../../base/button'
import Loading from '../../base/loading' import Loading from '../../base/loading'
import AppPublisher from '../app-publisher' import AppPublisher from '../app-publisher'
@ -65,7 +66,10 @@ type PublishConfig = {
const Configuration: FC = () => { const Configuration: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const { appDetail, setAppSiderbarExpand } = useAppStore() const { appDetail, setAppSiderbarExpand } = useAppStore(useShallow(state => ({
appDetail: state.appDetail,
setAppSiderbarExpand: state.setAppSiderbarExpand,
})))
const [formattingChanged, setFormattingChanged] = useState(false) const [formattingChanged, setFormattingChanged] = useState(false)
const { setShowAccountSettingModal } = useModalContext() const { setShowAccountSettingModal } = useModalContext()
const [hasFetchedDetail, setHasFetchedDetail] = useState(false) const [hasFetchedDetail, setHasFetchedDetail] = useState(false)

View File

@ -21,7 +21,7 @@ const LogAnnotation: FC<Props> = ({
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const router = useRouter() const router = useRouter()
const { appDetail } = useAppStore() const appDetail = useAppStore(state => state.appDetail)
const options = [ const options = [
{ value: PageType.log, text: t('appLog.title') }, { value: PageType.log, text: t('appLog.title') },

View File

@ -12,6 +12,7 @@ import {
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { debounce } from 'lodash-es' import { debounce } from 'lodash-es'
import classNames from 'classnames' import classNames from 'classnames'
import { useShallow } from 'zustand/react/shallow'
import type { import type {
ChatConfig, ChatConfig,
ChatItem, ChatItem,
@ -79,7 +80,14 @@ const Chat: FC<ChatProps> = ({
chatAnswerContainerInner, chatAnswerContainerInner,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal } = useAppStore() const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({
currentLogItem: state.currentLogItem,
setCurrentLogItem: state.setCurrentLogItem,
showPromptLogModal: state.showPromptLogModal,
setShowPromptLogModal: state.setShowPromptLogModal,
showAgentLogModal: state.showAgentLogModal,
setShowAgentLogModal: state.setShowAgentLogModal,
})))
const [width, setWidth] = useState(0) const [width, setWidth] = useState(0)
const chatContainerRef = useRef<HTMLDivElement>(null) const chatContainerRef = useRef<HTMLDivElement>(null)
const chatContainerInnerRef = useRef<HTMLDivElement>(null) const chatContainerInnerRef = useRef<HTMLDivElement>(null)

View File

@ -12,7 +12,7 @@ type IDevelopMainProps = {
} }
const DevelopMain = ({ appId }: IDevelopMainProps) => { const DevelopMain = ({ appId }: IDevelopMainProps) => {
const { appDetail } = useAppStore() const appDetail = useAppStore(state => state.appDetail)
const { t } = useTranslation() const { t } = useTranslation()
if (!appDetail) { if (!appDetail) {

View File

@ -40,7 +40,7 @@ const AppNav = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { appId } = useParams() const { appId } = useParams()
const { isCurrentWorkspaceManager } = useAppContext() const { isCurrentWorkspaceManager } = useAppContext()
const { appDetail } = useAppStore() const appDetail = useAppStore(state => state.appDetail)
const [showNewAppDialog, setShowNewAppDialog] = useState(false) const [showNewAppDialog, setShowNewAppDialog] = useState(false)
const [showNewAppTemplateDialog, setShowNewAppTemplateDialog] = useState(false) const [showNewAppTemplateDialog, setShowNewAppTemplateDialog] = useState(false)
const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false) const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false)

View File

@ -31,7 +31,7 @@ const Nav = ({
onLoadmore, onLoadmore,
isApp, isApp,
}: INavProps) => { }: INavProps) => {
const { setAppDetail } = useAppStore() const setAppDetail = useAppStore(state => state.setAppDetail)
const [hovered, setHovered] = useState(false) const [hovered, setHovered] = useState(false)
const segment = useSelectedLayoutSegment() const segment = useSelectedLayoutSegment()
const isActived = Array.isArray(activeSegment) ? activeSegment.includes(segment!) : segment === activeSegment const isActived = Array.isArray(activeSegment) ? activeSegment.includes(segment!) : segment === activeSegment

View File

@ -35,7 +35,7 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }:
const { t } = useTranslation() const { t } = useTranslation()
const router = useRouter() const router = useRouter()
const { isCurrentWorkspaceManager } = useAppContext() const { isCurrentWorkspaceManager } = useAppContext()
const { setAppDetail } = useAppStore() const setAppDetail = useAppStore(state => state.setAppDetail)
const handleScroll = useCallback(debounce((e) => { const handleScroll = useCallback(debounce((e) => {
if (typeof onLoadmore === 'function') { if (typeof onLoadmore === 'function') {

View File

@ -33,7 +33,7 @@ const Header: FC = () => {
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
const appDetail = useAppStore(s => s.appDetail) const appDetail = useAppStore(s => s.appDetail)
const appSidebarExpand = useAppStore(s => s.appSidebarExpand) const appSidebarExpand = useAppStore(s => s.appSidebarExpand)
const appID = useAppStore(state => state.appDetail?.id) const appID = appDetail?.id
const { const {
nodesReadOnly, nodesReadOnly,
getNodesReadOnly, getNodesReadOnly,

View File

@ -5,6 +5,7 @@ import {
import cn from 'classnames' import cn from 'classnames'
import useSWR from 'swr' import useSWR from 'swr'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useShallow } from 'zustand/react/shallow'
import { import {
useIsChatMode, useIsChatMode,
useWorkflow, useWorkflow,
@ -40,7 +41,11 @@ const ViewHistory = () => {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const { formatTimeFromNow } = useWorkflow() const { formatTimeFromNow } = useWorkflow()
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore() const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({
appDetail: state.appDetail,
setCurrentLogItem: state.setCurrentLogItem,
setShowMessageLogModal: state.setShowMessageLogModal,
})))
const historyWorkflowData = useStore(s => s.historyWorkflowData) const historyWorkflowData = useStore(s => s.historyWorkflowData)
const { handleBackupDraft } = useWorkflowRun() const { handleBackupDraft } = useWorkflowRun()
const { data: runList, isLoading: runListLoading } = useSWR((appDetail && !isChatMode && open) ? `/apps/${appDetail.id}/workflow-runs` : null, fetchWorkflowRunHistory) const { data: runList, isLoading: runListLoading } = useSWR((appDetail && !isChatMode && open) ? `/apps/${appDetail.id}/workflow-runs` : null, fetchWorkflowRunHistory)

View File

@ -4,6 +4,7 @@ import {
useMemo, useMemo,
} from 'react' } from 'react'
import { useNodes } from 'reactflow' import { useNodes } from 'reactflow'
import { useShallow } from 'zustand/react/shallow'
import type { CommonNodeType } from '../types' import type { CommonNodeType } from '../types'
import { Panel as NodePanel } from '../nodes' import { Panel as NodePanel } from '../nodes'
import { useStore } from '../store' import { useStore } from '../store'
@ -22,7 +23,12 @@ const Panel: FC = () => {
const showInputsPanel = useStore(s => s.showInputsPanel) const showInputsPanel = useStore(s => s.showInputsPanel)
const workflowRunningData = useStore(s => s.workflowRunningData) const workflowRunningData = useStore(s => s.workflowRunningData)
const historyWorkflowData = useStore(s => s.historyWorkflowData) const historyWorkflowData = useStore(s => s.historyWorkflowData)
const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal } = useAppStore() const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal } = useAppStore(useShallow(state => ({
currentLogItem: state.currentLogItem,
setCurrentLogItem: state.setCurrentLogItem,
showMessageLogModal: state.showMessageLogModal,
setShowMessageLogModal: state.setShowMessageLogModal,
})))
const { const {
showNodePanel, showNodePanel,
showDebugAndPreviewPanel, showDebugAndPreviewPanel,

View File

@ -25,7 +25,7 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
const { t } = useTranslation() const { t } = useTranslation()
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const [currentTab, setCurrentTab] = useState<string>(activeTab) const [currentTab, setCurrentTab] = useState<string>(activeTab)
const { appDetail } = useAppStore() const appDetail = useAppStore(state => state.appDetail)
const [loading, setLoading] = useState<boolean>(true) const [loading, setLoading] = useState<boolean>(true)
const [runDetail, setRunDetail] = useState<WorkflowRunDetailResponse>() const [runDetail, setRunDetail] = useState<WorkflowRunDetailResponse>()
const [list, setList] = useState<NodeTracing[]>([]) const [list, setList] = useState<NodeTracing[]>([])

View File

@ -1,8 +1,8 @@
import { useContext } from 'react' import { useContext } from 'react'
import { import {
create,
useStore as useZustandStore, useStore as useZustandStore,
} from 'zustand' } from 'zustand'
import { createStore } from 'zustand/vanilla'
import { debounce } from 'lodash-es' import { debounce } from 'lodash-es'
import type { Viewport } from 'reactflow' import type { Viewport } from 'reactflow'
import type { import type {
@ -70,9 +70,9 @@ type Shape = {
} }
export const createWorkflowStore = () => { export const createWorkflowStore = () => {
return create<Shape>(set => ({ return createStore<Shape>(set => ({
appId: '', appId: '',
workflowData: undefined, workflowRunningData: undefined,
setWorkflowRunningData: workflowRunningData => set(() => ({ workflowRunningData })), setWorkflowRunningData: workflowRunningData => set(() => ({ workflowRunningData })),
historyWorkflowData: undefined, historyWorkflowData: undefined,
setHistoryWorkflowData: historyWorkflowData => set(() => ({ historyWorkflowData })), setHistoryWorkflowData: historyWorkflowData => set(() => ({ historyWorkflowData })),