diff --git a/packages/desktop-client/src/components/ManageRules.tsx b/packages/desktop-client/src/components/ManageRules.tsx index b7b5f69438..e561fa700f 100644 --- a/packages/desktop-client/src/components/ManageRules.tsx +++ b/packages/desktop-client/src/components/ManageRules.tsx @@ -21,6 +21,7 @@ import { type NewRuleEntity } from 'loot-core/src/types/models'; import { useAccounts } from '../hooks/useAccounts'; import { useCategories } from '../hooks/useCategories'; import { usePayees } from '../hooks/usePayees'; +import { useSchedules } from '../hooks/useSchedules'; import { useSelected, SelectedProvider } from '../hooks/useSelected'; import { theme } from '../style'; @@ -32,7 +33,6 @@ import { Text } from './common/Text'; import { View } from './common/View'; import { RulesHeader } from './rules/RulesHeader'; import { RulesList } from './rules/RulesList'; -import { SchedulesQuery } from './rules/SchedulesQuery'; import { SimpleTable } from './rules/SimpleTable'; function mapValue( @@ -97,23 +97,23 @@ function ruleToString(rule, data) { ); } -type ManageRulesContentProps = { +type ManageRulesProps = { isModal: boolean; payeeId: string | null; setLoading?: Dispatch>; }; -function ManageRulesContent({ +export function ManageRules({ isModal, payeeId, - setLoading, -}: ManageRulesContentProps) { + setLoading = () => {}, +}: ManageRulesProps) { const [allRules, setAllRules] = useState([]); const [page, setPage] = useState(0); const [filter, setFilter] = useState(''); const dispatch = useDispatch(); - const { data: schedules } = SchedulesQuery.useQuery(); + const { data: schedules = [] } = useSchedules(); const { list: categories } = useCategories(); const payees = usePayees(); const accounts = useAccounts(); @@ -353,25 +353,3 @@ function EmptyMessage({ text, style }) { ); } - -type ManageRulesProps = { - isModal: boolean; - payeeId: string | null; - setLoading?: Dispatch>; -}; - -export function ManageRules({ - isModal, - payeeId, - setLoading = () => {}, -}: ManageRulesProps) { - return ( - - - - ); -} diff --git a/packages/desktop-client/src/components/reports/reports/CashFlow.tsx b/packages/desktop-client/src/components/reports/reports/CashFlow.tsx index eb5f65a840..8a9f8933fa 100644 --- a/packages/desktop-client/src/components/reports/reports/CashFlow.tsx +++ b/packages/desktop-client/src/components/reports/reports/CashFlow.tsx @@ -58,7 +58,7 @@ export function CashFlow() { } type CashFlowInnerProps = { - widget: CashFlowWidget; + widget?: CashFlowWidget; }; function CashFlowInner({ widget }: CashFlowInnerProps) { @@ -142,8 +142,12 @@ function CashFlowInner({ widget }: CashFlowInnerProps) { const { isNarrowWidth } = useResponsive(); async function onSaveWidget() { + if (!widget) { + throw new Error('No widget that could be saved.'); + } + await send('dashboard-update-widget', { - id: widget?.id, + id: widget.id, meta: { ...(widget.meta ?? {}), conditions, diff --git a/packages/desktop-client/src/components/reports/reports/Spending.tsx b/packages/desktop-client/src/components/reports/reports/Spending.tsx index 7efc495e6b..a04753b917 100644 --- a/packages/desktop-client/src/components/reports/reports/Spending.tsx +++ b/packages/desktop-client/src/components/reports/reports/Spending.tsx @@ -55,7 +55,7 @@ export function Spending() { } type SpendingInternalProps = { - widget: SpendingWidget; + widget?: SpendingWidget; }; function SpendingInternal({ widget }: SpendingInternalProps) { @@ -133,8 +133,12 @@ function SpendingInternal({ widget }: SpendingInternalProps) { const { isNarrowWidth } = useResponsive(); async function onSaveWidget() { + if (!widget) { + throw new Error('No widget that could be saved.'); + } + await send('dashboard-update-widget', { - id: widget?.id, + id: widget.id, meta: { ...(widget.meta ?? {}), conditions, diff --git a/packages/desktop-client/src/components/rules/ScheduleValue.tsx b/packages/desktop-client/src/components/rules/ScheduleValue.tsx index fb05b0b8f2..54e7dc0006 100644 --- a/packages/desktop-client/src/components/rules/ScheduleValue.tsx +++ b/packages/desktop-client/src/components/rules/ScheduleValue.tsx @@ -5,8 +5,8 @@ import { describeSchedule } from 'loot-core/src/shared/schedules'; import { type ScheduleEntity } from 'loot-core/src/types/models'; import { usePayees } from '../../hooks/usePayees'; +import { useSchedules } from '../../hooks/useSchedules'; -import { SchedulesQuery } from './SchedulesQuery'; import { Value } from './Value'; type ScheduleValueProps = { @@ -16,7 +16,7 @@ type ScheduleValueProps = { export function ScheduleValue({ value }: ScheduleValueProps) { const payees = usePayees(); const byId = getPayeesById(payees); - const { data: schedules } = SchedulesQuery.useQuery(); + const { data: schedules } = useSchedules(); return ( (() => q('schedules').select('*'), []); +} diff --git a/packages/loot-core/src/client/query-hooks.ts b/packages/loot-core/src/client/query-hooks.ts new file mode 100644 index 0000000000..f2a338919e --- /dev/null +++ b/packages/loot-core/src/client/query-hooks.ts @@ -0,0 +1,54 @@ +import { useState, useMemo, useEffect, type DependencyList } from 'react'; + +import { type Query } from '../shared/query'; + +import { liveQuery, type LiveQuery } from './query-helpers'; + +/** @deprecated: please use `useQuery`; usage is the same - only the returned value is different (object instead of only the data) */ +export function useLiveQuery( + makeQuery: () => Query, + deps: DependencyList, +): Response | null { + const { data } = useQuery(makeQuery, deps); + return data; +} + +export function useQuery( + makeQuery: () => Query, + deps: DependencyList, +): { + data: null | Response; + overrideData: (newData: Response) => void; + isLoading: boolean; +} { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + // eslint-disable-next-line react-hooks/exhaustive-deps + const query = useMemo(makeQuery, deps); + + useEffect(() => { + setIsLoading(true); + + let live: null | LiveQuery = liveQuery( + query, + async data => { + if (live) { + setIsLoading(false); + setData(data); + } + }, + ); + + return () => { + setIsLoading(false); + live?.unsubscribe(); + live = null; + }; + }, [query]); + + return { + data, + overrideData: setData, + isLoading, + }; +} diff --git a/packages/loot-core/src/client/query-hooks.tsx b/packages/loot-core/src/client/query-hooks.tsx deleted file mode 100644 index 8e4f5810aa..0000000000 --- a/packages/loot-core/src/client/query-hooks.tsx +++ /dev/null @@ -1,115 +0,0 @@ -// @ts-strict-ignore -import React, { - createContext, - useState, - useContext, - useMemo, - useEffect, - type DependencyList, -} from 'react'; - -import { type Query } from '../shared/query'; - -import { liveQuery, LiveQuery } from './query-helpers'; - -function makeContext(queryState, opts, QueryClass) { - const query = new QueryClass(queryState, null, opts); - const Context = createContext(null); - - function Provider({ children }) { - const [data, setData] = useState(query.getData()); - const value = useMemo(() => ({ data, query }), [data, query]); - - useEffect(() => { - if (query.getNumListeners() !== 0) { - throw new Error( - 'Query already has listeners. You cannot use the same query context `Provider` twice', - ); - } - - const unlisten = query.addListener(data => setData(data)); - - // Start the query if it hasn't run yet. Most likely it's not - // running, however the user can freely start the query early if - // they want - if (!query.isRunning()) { - query.run(); - } - - // There's a small chance data has changed between rendering and - // running this effect, so just in case set this again. Note - // this won't rerender if the data is the same - setData(query.getData()); - - return () => { - unlisten(); - query.unsubscribe(); - }; - }, []); - - return {children}; - } - - function useQuery() { - const queryData = useContext(Context); - if (queryData == null) { - throw new Error( - '`useQuery` tried to access a query that hasn’t been run. You need to put its `Provider` in a parent component', - ); - } - return queryData; - } - - return { - Provider, - useQuery, - }; -} - -export function liveQueryContext(query, opts?) { - return makeContext(query, opts, LiveQuery); -} - -export function useLiveQuery( - makeQuery: () => Query, - deps: DependencyList, -): Response { - const { data } = useQuery(makeQuery, deps); - return data; -} - -export function useQuery( - makeQuery: () => Query, - deps: DependencyList, -): { - data: null | Response; - overrideData: (newData: Response) => void; - isLoading: boolean; -} { - const [data, setData] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const query = useMemo(makeQuery, deps); - - useEffect(() => { - setIsLoading(true); - - let live = liveQuery(query, async data => { - if (live) { - setIsLoading(false); - setData(data); - } - }); - - return () => { - setIsLoading(false); - live.unsubscribe(); - live = null; - }; - }, [query]); - - return { - data, - overrideData: setData, - isLoading, - }; -} diff --git a/upcoming-release-notes/3512.md b/upcoming-release-notes/3512.md new file mode 100644 index 0000000000..e14d239550 --- /dev/null +++ b/upcoming-release-notes/3512.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +e2e: improve rules test stability.