Skip to content

Commit

Permalink
⚡ (synced-prefs) preloading in redux state to improve perf (#3544)
Browse files Browse the repository at this point in the history
  • Loading branch information
MatissJanis committed Oct 7, 2024
1 parent 2e56935 commit 888d2e9
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 55 deletions.
27 changes: 10 additions & 17 deletions packages/desktop-client/src/hooks/useSyncedPref.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { useQuery } from 'loot-core/client/query-hooks';
import { send } from 'loot-core/platform/client/fetch';
import { q } from 'loot-core/shared/query';
import { saveSyncedPrefs } from 'loot-core/client/actions';
import { type State } from 'loot-core/client/state-types';
import { type SyncedPrefs } from 'loot-core/src/types/prefs';

type SetSyncedPrefAction<K extends keyof SyncedPrefs> = (
Expand All @@ -12,21 +12,14 @@ type SetSyncedPrefAction<K extends keyof SyncedPrefs> = (
export function useSyncedPref<K extends keyof SyncedPrefs>(
prefName: K,
): [SyncedPrefs[K], SetSyncedPrefAction<K>] {
const { data: queryData, overrideData: setQueryData } = useQuery<
[{ value: string | undefined }]
>(
() => q('preferences').filter({ id: prefName }).select('value'),
[prefName],
);

const setLocalPref = useCallback<SetSyncedPrefAction<K>>(
newValue => {
const value = String(newValue);
setQueryData([{ value }]);
send('preferences/save', { id: prefName, value });
const dispatch = useDispatch();
const setPref = useCallback<SetSyncedPrefAction<K>>(
value => {
dispatch(saveSyncedPrefs({ [prefName]: value }));
},
[prefName, setQueryData],
[prefName, dispatch],
);
const pref = useSelector((state: State) => state.prefs.synced[prefName]);

return [queryData?.[0]?.value, setLocalPref];
return [pref, setPref];
}
39 changes: 11 additions & 28 deletions packages/desktop-client/src/hooks/useSyncedPrefs.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,22 @@
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { useQuery } from 'loot-core/client/query-hooks';
import { send } from 'loot-core/platform/client/fetch';
import { q } from 'loot-core/shared/query';
import { saveSyncedPrefs } from 'loot-core/client/actions';
import { type State } from 'loot-core/client/state-types';
import { type SyncedPrefs } from 'loot-core/src/types/prefs';

type SetSyncedPrefsAction = (value: Partial<SyncedPrefs>) => void;

/** @deprecated: please use `useSyncedPref` (singular) */
export function useSyncedPrefs(): [SyncedPrefs, SetSyncedPrefsAction] {
const { data: queryData } = useQuery<{ id: string; value: string }[]>(
() => q('preferences').select(['id', 'value']),
[],
const dispatch = useDispatch();
const setPrefs = useCallback<SetSyncedPrefsAction>(
newValue => {
dispatch(saveSyncedPrefs(newValue));
},
[dispatch],
);

const prefs = useMemo<SyncedPrefs>(
() =>
(queryData ?? []).reduce(
(carry, { id, value }) => ({
...carry,
[id]: value,
}),
{},
),
[queryData],
);

const setPrefs = useCallback<SetSyncedPrefsAction>(newValue => {
Object.entries(newValue).forEach(([id, value]) => {
send('preferences/save', {
id: id as keyof SyncedPrefs,
value: String(value),
});
});
}, []);
const prefs = useSelector((state: State) => state.prefs.synced);

return [prefs, setPrefs];
}
25 changes: 24 additions & 1 deletion packages/loot-core/src/client/actions/prefs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { send } from '../../platform/client/fetch';
import { type GlobalPrefs, type MetadataPrefs } from '../../types/prefs';
import {
type GlobalPrefs,
type MetadataPrefs,
type SyncedPrefs,
} from '../../types/prefs';
import * as constants from '../constants';

import { closeModal } from './modals';
Expand All @@ -19,6 +23,7 @@ export function loadPrefs() {
type: constants.SET_PREFS,
prefs,
globalPrefs: await send('load-global-prefs'),
syncedPrefs: await send('preferences/get'),
});

return prefs;
Expand All @@ -42,6 +47,7 @@ export function loadGlobalPrefs() {
type: constants.SET_PREFS,
prefs: getState().prefs.local,
globalPrefs,
syncedPrefs: getState().prefs.synced,
});
return globalPrefs;
};
Expand All @@ -60,3 +66,20 @@ export function saveGlobalPrefs(
onSaveGlobalPrefs?.();
};
}

export function saveSyncedPrefs(prefs: SyncedPrefs) {
return async (dispatch: Dispatch) => {
await Promise.all(
Object.entries(prefs).map(([prefName, value]) =>
send('preferences/save', {
id: prefName as keyof SyncedPrefs,
value,
}),
),
);
dispatch({
type: constants.MERGE_SYNCED_PREFS,
syncedPrefs: prefs,
});
};
}
8 changes: 5 additions & 3 deletions packages/loot-core/src/client/actions/sync.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-strict-ignore
import { send } from '../../platform/client/fetch';
import { getUploadError } from '../../shared/errors';

Expand Down Expand Up @@ -32,7 +31,6 @@ export function resetSync() {
}
} else {
await dispatch(sync());
await dispatch(loadPrefs());
}
};
}
Expand All @@ -45,8 +43,12 @@ export function sync() {
if ('error' in result) {
return { error: result.error };
}
return {};

// Update the prefs
await dispatch(loadPrefs());
}

return {};
};
}

Expand Down
1 change: 1 addition & 0 deletions packages/loot-core/src/client/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const LOAD_PAYEES = 'LOAD_PAYEES';
export const SET_PREFS = 'SET_PREFS';
export const MERGE_LOCAL_PREFS = 'MERGE_LOCAL_PREFS';
export const MERGE_GLOBAL_PREFS = 'MERGE_GLOBAL_PREFS';
export const MERGE_SYNCED_PREFS = 'MERGE_SYNCED_PREFS';
export const SET_BUDGETS = 'SET_BUDGETS';
export const SET_REMOTE_FILES = 'SET_REMOTE_FILES';
export const SET_ALL_FILES = 'SET_ALL_FILES';
Expand Down
18 changes: 14 additions & 4 deletions packages/loot-core/src/client/reducers/prefs.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
// @ts-strict-ignore
import * as constants from '../constants';
import type { Action } from '../state-types';
import type { PrefsState } from '../state-types/prefs';

const initialState: PrefsState = {
local: null,
global: null,
local: {},
global: {},
synced: {},
};

export function update(state = initialState, action: Action): PrefsState {
switch (action.type) {
case constants.SET_PREFS:
return { local: action.prefs, global: action.globalPrefs };
return {
...state,
local: action.prefs,
global: action.globalPrefs,
synced: action.syncedPrefs,
};
case constants.MERGE_LOCAL_PREFS:
return {
...state,
Expand All @@ -22,6 +27,11 @@ export function update(state = initialState, action: Action): PrefsState {
...state,
global: { ...state.global, ...action.globalPrefs },
};
case constants.MERGE_SYNCED_PREFS:
return {
...state,
synced: { ...state.synced, ...action.syncedPrefs },
};

default:
}
Expand Down
16 changes: 14 additions & 2 deletions packages/loot-core/src/client/state-types/prefs.d.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import type { GlobalPrefs, MetadataPrefs } from '../../types/prefs';
import type {
GlobalPrefs,
MetadataPrefs,
SyncedPrefs,
} from '../../types/prefs';
import type * as constants from '../constants';

export type PrefsState = {
local: MetadataPrefs;
global: GlobalPrefs;
synced: SyncedPrefs;
};

export type SetPrefsAction = {
type: typeof constants.SET_PREFS;
prefs: MetadataPrefs;
globalPrefs: GlobalPrefs;
syncedPrefs: SyncedPrefs;
};

export type MergeLocalPrefsAction = {
Expand All @@ -22,7 +28,13 @@ export type MergeGlobalPrefsAction = {
globalPrefs: GlobalPrefs;
};

export type MergeSyncedPrefsAction = {
type: typeof constants.MERGE_SYNCED_PREFS;
syncedPrefs: SyncedPrefs;
};

export type PrefsActions =
| SetPrefsAction
| MergeLocalPrefsAction
| MergeGlobalPrefsAction;
| MergeGlobalPrefsAction
| MergeSyncedPrefsAction;
13 changes: 13 additions & 0 deletions packages/loot-core/src/server/preferences/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,17 @@ const savePreferences = async ({
await db.update('preferences', { id, value });
};

const getPreferences = async (): Promise<SyncedPrefs> => {
const prefs = (await db.all('SELECT id, value FROM preferences')) as Array<{
id: string;
value: string;
}>;

return prefs.reduce<SyncedPrefs>((carry, { value, id }) => {
carry[id as keyof SyncedPrefs] = value;
return carry;
}, {});
};

app.method('preferences/save', mutator(undoable(savePreferences)));
app.method('preferences/get', getPreferences);
2 changes: 2 additions & 0 deletions packages/loot-core/src/server/preferences/types/handlers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ export interface PreferencesHandlers {
id: keyof SyncedPrefs;
value: string | undefined;
}) => Promise<void>;

'preferences/get': () => Promise<SyncedPrefs>;
}
6 changes: 6 additions & 0 deletions upcoming-release-notes/3544.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---

SyncedPrefs: preload in redux state and fetch from there; improved performance.

0 comments on commit 888d2e9

Please sign in to comment.