diff --git a/client/packages/lowcoder/src/comps/controls/codeControl.tsx b/client/packages/lowcoder/src/comps/controls/codeControl.tsx index 2ae75d7a0..7daa097d5 100644 --- a/client/packages/lowcoder/src/comps/controls/codeControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/codeControl.tsx @@ -189,6 +189,7 @@ export function codeControl< const cardContent = params.disableCard ? "" : getCardContent(this.unevaledValue, this.valueAndMsg, codeControlParams); + const { key, ...restParams } = params; return ( {(editorState) => ( @@ -197,7 +198,8 @@ export function codeControl< <> { - const timer = setTimeout(() => { - if (searchValue.length > 2 || searchValue === "") - setSearchValues(searchValue) - }, 500); - return () => clearTimeout(timer); - }, [searchValue]) + const debouncedSearchValue = useDebouncedValue(searchValue, 500); + + useEffect(() => { + if (debouncedSearchValue.trim().length > 0 || debouncedSearchValue === "") + setSearchValues(debouncedSearchValue); + }, [debouncedSearchValue]); return ( <> diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/HomeView.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/HomeView.tsx index 3a435a6b8..a49355541 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/HomeView.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/HomeView.tsx @@ -3,7 +3,8 @@ import { HomeLayout } from "./HomeLayout"; import { getUser } from "../../redux/selectors/usersSelectors"; import { Helmet } from "react-helmet"; import { trans } from "i18n"; -import {useState, useEffect } from "react"; +import { useState, useEffect } from "react"; +import { useDebouncedValue } from "util/hooks"; import {fetchFolderElements} from "@lowcoder-ee/util/pagination/axios"; import {ApplicationCategoriesEnum, ApplicationMeta, FolderMeta} from "@lowcoder-ee/constants/applicationConstants"; import {ApplicationPaginationType} from "@lowcoder-ee/util/pagination/type"; @@ -53,13 +54,13 @@ export function HomeView() { }, [searchValues] ); - useEffect(()=> { - const timer = setTimeout(() => { - if (searchValue.length > 2 || searchValue === "") - setSearchValues(searchValue) - }, 500); - return () => clearTimeout(timer); - }, [searchValue]) + const debouncedSearchValue = useDebouncedValue(searchValue, 500); + + useEffect(() => { + if (debouncedSearchValue.trim().length > 0 || debouncedSearchValue === "") { + setSearchValues(debouncedSearchValue); + } + }, [debouncedSearchValue]); const user = useSelector(getUser); diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/TrashView.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/TrashView.tsx index 410a2632f..3b6e84390 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/TrashView.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/TrashView.tsx @@ -1,6 +1,7 @@ import { HomeLayout } from "./HomeLayout"; import { TRASH_URL } from "../../constants/routesURL"; -import {useEffect, useState} from "react"; +import { useEffect, useState } from "react"; +import { useDebouncedValue } from "util/hooks"; import { trans } from "../../i18n"; import { Helmet } from "react-helmet"; import {fetchApplicationElements} from "@lowcoder-ee/util/pagination/axios"; @@ -46,14 +47,13 @@ export function TrashView() { }, [searchValues] ); - //debouncing - useEffect(()=> { - const timer = setTimeout(() => { - if (searchValue.length > 2 || searchValue === "") - setSearchValues(searchValue) - }, 500); - return () => clearTimeout(timer); - }, [searchValue]) + const debouncedSearchValue = useDebouncedValue(searchValue, 500); + + useEffect(() => { + if (debouncedSearchValue.trim().length > 0 || debouncedSearchValue === "") { + setSearchValues(debouncedSearchValue); + } + }, [debouncedSearchValue]); return ( <> diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx index 14bf9df2e..20ba7f5ab 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx @@ -176,6 +176,7 @@ export default function ApplicationHome() { routePath: ALL_APPLICATIONS_URL, routeComp: HomeView, icon: ({ selected, ...otherProps }) => selected ? : , + onSelected: (_, currentPath) => currentPath === ALL_APPLICATIONS_URL || currentPath.startsWith("/folder"), }, ], }, diff --git a/client/packages/lowcoder/src/pages/datasource/datasourceList.tsx b/client/packages/lowcoder/src/pages/datasource/datasourceList.tsx index 61eb621b2..cc83e64e6 100644 --- a/client/packages/lowcoder/src/pages/datasource/datasourceList.tsx +++ b/client/packages/lowcoder/src/pages/datasource/datasourceList.tsx @@ -1,6 +1,7 @@ import styled from "styled-components"; import { EditPopover, PointIcon, Search, TacoButton } from "lowcoder-design"; -import {useEffect, useState} from "react"; +import { useState, useEffect } from "react"; +import { useDebouncedValue } from "util/hooks"; import { useDispatch, useSelector } from "react-redux"; import { getDataSourceTypesMap } from "../../redux/selectors/datasourceSelectors"; import { deleteDatasource } from "../../redux/reduxActions/datasourceActions"; @@ -124,13 +125,13 @@ export const DatasourceList = () => { const [pageSize, setPageSize] = useState(10); const [paginationLoading, setPaginationLoading] = useState(false); - useEffect(()=> { - const timer = setTimeout(() => { - if (searchValue.length > 2 || searchValue === "") - setSearchValues(searchValue) - }, 500); - return () => clearTimeout(timer); - }, [searchValue]) + const debouncedSearchValue = useDebouncedValue(searchValue, 500); + + useEffect(() => { + if (debouncedSearchValue.trim().length > 0 || debouncedSearchValue === "") { + setSearchValues(debouncedSearchValue); + } + }, [debouncedSearchValue]); useEffect( () => { setPaginationLoading(true); diff --git a/client/packages/lowcoder/src/pages/queryLibrary/LeftNav.tsx b/client/packages/lowcoder/src/pages/queryLibrary/LeftNav.tsx index 84bdade67..6df81dd6b 100644 --- a/client/packages/lowcoder/src/pages/queryLibrary/LeftNav.tsx +++ b/client/packages/lowcoder/src/pages/queryLibrary/LeftNav.tsx @@ -1,4 +1,5 @@ -import {useEffect, useState} from "react"; +import { useEffect, useState } from "react"; +import { useDebouncedValue } from "util/hooks"; import styled, { css } from "styled-components"; import { BluePlusIcon, @@ -174,14 +175,13 @@ export const LeftNav = (props: { const [searchValue, setSearchValue] = useState(""); const datasourceTypes = useSelector(getDataSourceTypesMap); - useEffect(()=> { - const timer = setTimeout(() => { - if (searchValue.length > 2 || searchValue === "") - setSearchValues(searchValue) - }, 500); - return () => clearTimeout(timer); - }, [searchValue]) + const debouncedSearchValue = useDebouncedValue(searchValue, 500); + useEffect(() => { + if (debouncedSearchValue.trim().length > 0 || debouncedSearchValue === "") { + setSearchValues(debouncedSearchValue); + } + }, [debouncedSearchValue]); return ( diff --git a/client/packages/lowcoder/src/pages/setting/advanced/AdvancedSetting.tsx b/client/packages/lowcoder/src/pages/setting/advanced/AdvancedSetting.tsx index a9998fa5d..5a85dd6e5 100644 --- a/client/packages/lowcoder/src/pages/setting/advanced/AdvancedSetting.tsx +++ b/client/packages/lowcoder/src/pages/setting/advanced/AdvancedSetting.tsx @@ -96,9 +96,18 @@ export function AdvancedSetting() { }, [currentUser.currentOrgId]) useEffect(() => { - dispatch(fetchCommonSettings({ orgId: currentUser.currentOrgId })); - dispatch(fetchAllApplications({})); - }, [currentUser.currentOrgId, dispatch]); + // Only fetch common settings if not already loaded + if (Object.keys(commonSettings).length === 0) { + dispatch(fetchCommonSettings({ orgId: currentUser.currentOrgId })); + } + }, [currentUser.currentOrgId, dispatch, commonSettings]); + + // Lazy load applications only when dropdown is opened + const handleDropdownOpen = () => { + if (appList.length === 0) { + dispatch(fetchAllApplications({})); + } + }; useEffect(() => { setSettings(commonSettings); @@ -110,9 +119,7 @@ export function AdvancedSetting() { } }, [canLeave]); - useEffect(() => { - dispatch(fetchCommonSettings({ orgId: currentUser.currentOrgId })); - }, [currentUser.currentOrgId, dispatch]); + const handleSave = (key: keyof typeof settings, onSuccess?: () => void) => { return (value?: any) => { @@ -178,6 +185,9 @@ export function AdvancedSetting() { onChange={(value: string) => { setSettings((v) => ({ ...v, defaultHomePage: value })); }} + onDropdownVisibleChange={(open) => { + if (open) handleDropdownOpen(); + }} options={appListOptions} filterOption={(input, option) => (option?.label as string).includes(input)} /> diff --git a/client/packages/lowcoder/src/util/hooks.ts b/client/packages/lowcoder/src/util/hooks.ts index 9c9b7777c..796ba7eed 100644 --- a/client/packages/lowcoder/src/util/hooks.ts +++ b/client/packages/lowcoder/src/util/hooks.ts @@ -29,6 +29,7 @@ import { constantColors } from "components/colorSelect/colorUtils"; import { AppState } from "@lowcoder-ee/redux/reducers"; import { getOrgUserStats } from "@lowcoder-ee/redux/selectors/orgSelectors"; import { fetchGroupsAction } from "@lowcoder-ee/redux/reduxActions/orgActions"; +import debounce from "lodash/debounce"; export const ForceViewModeContext = React.createContext(false); @@ -282,3 +283,21 @@ export const useOrgUserCount = (orgId: string) => { return userCount; }; + +/** + * Returns a debounced version of the incoming value that only updates + */ +export function useDebouncedValue(value: T, delay = 500): T { + const [debouncedValue, setDebouncedValue] = useState(value); + + const updater = useMemo(() => debounce(setDebouncedValue, delay), [delay]); + + useEffect(() => { + updater(value); + return () => { + updater.cancel(); + }; + }, [value, updater]); + + return debouncedValue; +}