Skip to content

Commit 0a75265

Browse files
committed
implement memoizing and avoid invoking callbacks when not required
1 parent e6f239c commit 0a75265

File tree

4 files changed

+53
-5
lines changed

4 files changed

+53
-5
lines changed

chartlets.js/packages/lib/src/actions/handleHostStoreChange.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ import { invokeCallbacks } from "@/actions/helpers/invokeCallbacks";
1111
import type { ContributionState } from "@/types/state/contribution";
1212
import type { HostStore } from "@/types/state/host";
1313
import { store } from "@/store";
14+
import { shallowEqualArrays } from "@/utils/compare";
1415

1516
/**
1617
* A reference to a property of an input of a callback of a contribution.
1718
*/
1819
export interface PropertyRef extends ContribRef, CallbackRef, InputRef {
1920
/** The property. */
2021
property: string;
22+
/** Property ID for memoization */
23+
id: string;
2124
}
2225

2326
export function handleHostStoreChange() {
@@ -44,16 +47,23 @@ export function handleHostStoreChange() {
4447
hostStore,
4548
);
4649

47-
if (callbackRequests && callbackRequests.length > 0) {
48-
invokeCallbacks(callbackRequests);
50+
const filteredCallbackRequests = callbackRequests.filter(
51+
(callbackRequest): callbackRequest is CallbackRequest =>
52+
callbackRequest !== undefined,
53+
);
54+
if (filteredCallbackRequests && filteredCallbackRequests.length > 0) {
55+
invokeCallbacks(filteredCallbackRequests);
4956
}
5057
}
5158

52-
function getCallbackRequests(
59+
// Exporting for testing only
60+
export function getCallbackRequests(
5361
propertyRefs: PropertyRef[],
5462
contributionsRecord: Record<string, ContributionState[]>,
5563
hostStore: HostStore,
56-
): CallbackRequest[] {
64+
): (CallbackRequest | undefined)[] {
65+
const { configuration, lastInputValues } = store.getState();
66+
const { logging } = configuration;
5767
return propertyRefs.map((propertyRef) => {
5868
const contributions = contributionsRecord[propertyRef.contribPoint];
5969
const contribution = contributions[propertyRef.contribIndex];
@@ -63,6 +73,27 @@ function getCallbackRequests(
6373
contribution,
6474
hostStore,
6575
);
76+
const propRefId = propertyRef.id;
77+
if (
78+
lastInputValues?.[propRefId] &&
79+
shallowEqualArrays(lastInputValues?.[propRefId], inputValues)
80+
) {
81+
// Skip adding the inputValues if memoized values are returned.
82+
if (logging?.enabled) {
83+
console.groupCollapsed("Skipping callback request");
84+
console.log("inputValues", inputValues);
85+
console.groupEnd();
86+
}
87+
return;
88+
}
89+
if (lastInputValues) {
90+
lastInputValues[propRefId] = inputValues;
91+
store.setState({
92+
lastInputValues: { ...lastInputValues },
93+
});
94+
} else {
95+
store.setState({ lastInputValues: { [propRefId]: inputValues } });
96+
}
6697
return { ...propertyRef, inputValues };
6798
});
6899
}
@@ -92,6 +123,7 @@ function getHostStorePropertyRefs(): PropertyRef[] {
92123
callbackIndex,
93124
inputIndex,
94125
property: formatObjPath(input.property),
126+
id: `${contribPoint}-${contribIndex}-${callbackIndex}-${inputIndex}`,
95127
});
96128
}
97129
}),

chartlets.js/packages/lib/src/actions/helpers/getInputValues.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { formatObjPath, getValue, type ObjPathLike } from "@/utils/objPath";
1414
import { isObject } from "@/utils/isObject";
1515
import type { HostStore } from "@/types/state/host";
16+
import memoize from "fast-memoize";
1617

1718
export function getInputValues(
1819
inputs: Input[],
@@ -26,7 +27,9 @@ export function getInputValues(
2627

2728
const noValue = {};
2829

29-
export function getInputValue(
30+
export const getInputValue = memoize(_getInputValue);
31+
32+
function _getInputValue(
3033
input: Input,
3134
contributionState: ContributionState,
3235
hostStore?: HostStore,

chartlets.js/packages/lib/src/types/state/store.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,9 @@ export interface StoreState {
2525
* See hook `useThemeMode()`.
2626
*/
2727
themeMode?: ThemeMode;
28+
/**
29+
* Store last input values for callback requests to avoid invoking them if
30+
* there are no changes
31+
* */
32+
lastInputValues?: Record<string, unknown[]>;
2833
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function shallowEqualArrays(
2+
arr1?: unknown[],
3+
arr2?: unknown[],
4+
): boolean {
5+
if (!arr1 || !arr2) return false;
6+
if (arr1.length !== arr2.length) return false;
7+
return arr1.every((val, index) => val === arr2[index]);
8+
}

0 commit comments

Comments
 (0)