Skip to content

Commit 574e140

Browse files
authored
Fix input lag when changing node and edge colors (#1173)
* Add helper function useDeferredAtom * Refactor tests to be synchronous * Further enforce synchronous calls for setVertexStyle --------- Co-authored-by: David <75678655+David-Werth@users.noreply.github.com>
1 parent 9db162c commit 574e140

File tree

4 files changed

+51
-34
lines changed

4 files changed

+51
-34
lines changed

packages/graph-explorer/src/core/StateProvider/userPreferences.test.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,29 @@ describe("useVertexStyling", () => {
2424
expect(result.current.vertexStyle).toEqual(style);
2525
});
2626

27-
it("should insert the vertex style when none exist", async () => {
27+
it("should insert the vertex style when none exist", () => {
2828
const dbState = new DbState();
2929
const { result } = renderHookWithJotai(
3030
() => useVertexStyling("test"),
3131
snapshot => dbState.applyTo(snapshot)
3232
);
3333

34-
await act(() => result.current.setVertexStyle({ color: "red" }));
34+
act(() => result.current.setVertexStyle({ color: "red" }));
3535

3636
expect(result.current.vertexStyle).toEqual({ type: "test", color: "red" });
3737
});
3838

39-
it("should update the existing style, merging new styles", async () => {
39+
it("should update the existing style, merging new styles", () => {
4040
const dbState = new DbState();
4141
const { result } = renderHookWithJotai(
4242
() => useVertexStyling("test"),
4343
snapshot => dbState.applyTo(snapshot)
4444
);
4545

46-
await act(() =>
46+
act(() =>
4747
result.current.setVertexStyle({ color: "red", borderColor: "green" })
4848
);
49-
await act(() => result.current.setVertexStyle({ borderColor: "blue" }));
49+
act(() => result.current.setVertexStyle({ borderColor: "blue" }));
5050

5151
expect(result.current.vertexStyle).toEqual({
5252
type: "test",
@@ -55,7 +55,7 @@ describe("useVertexStyling", () => {
5555
});
5656
});
5757

58-
it("should reset the vertex style", async () => {
58+
it("should reset the vertex style", () => {
5959
const dbState = new DbState();
6060
dbState.addVertexStyle("test", { borderColor: "blue" });
6161

@@ -64,7 +64,7 @@ describe("useVertexStyling", () => {
6464
snapshot => dbState.applyTo(snapshot)
6565
);
6666

67-
await act(() => result.current.resetVertexStyle());
67+
act(() => result.current.resetVertexStyle());
6868

6969
expect(result.current.vertexStyle).toBeUndefined();
7070
});
@@ -92,32 +92,32 @@ describe("useEdgeStyling", () => {
9292
expect(result.current.edgeStyle).toEqual(style);
9393
});
9494

95-
it("should insert the edge style when none exist", async () => {
95+
it("should insert the edge style when none exist", () => {
9696
const dbState = new DbState();
9797
const { result } = renderHookWithJotai(
9898
() => useEdgeStyling("test"),
9999
snapshot => dbState.applyTo(snapshot)
100100
);
101101

102-
await act(() => result.current.setEdgeStyle({ lineColor: "red" }));
102+
act(() => result.current.setEdgeStyle({ lineColor: "red" }));
103103

104104
expect(result.current.edgeStyle).toEqual({
105105
type: "test",
106106
lineColor: "red",
107107
});
108108
});
109109

110-
it("should update the existing style, merging new styles", async () => {
110+
it("should update the existing style, merging new styles", () => {
111111
const dbState = new DbState();
112112
const { result } = renderHookWithJotai(
113113
() => useEdgeStyling("test"),
114114
snapshot => dbState.applyTo(snapshot)
115115
);
116116

117-
await act(() =>
117+
act(() =>
118118
result.current.setEdgeStyle({ lineColor: "red", labelColor: "green" })
119119
);
120-
await act(() => result.current.setEdgeStyle({ labelColor: "blue" }));
120+
act(() => result.current.setEdgeStyle({ labelColor: "blue" }));
121121

122122
expect(result.current.edgeStyle).toEqual({
123123
type: "test",
@@ -126,15 +126,15 @@ describe("useEdgeStyling", () => {
126126
});
127127
});
128128

129-
it("should reset the edge style", async () => {
129+
it("should reset the edge style", () => {
130130
const dbState = new DbState();
131131
const { result } = renderHookWithJotai(
132132
() => useEdgeStyling("test"),
133133
snapshot => dbState.applyTo(snapshot)
134134
);
135135

136-
await act(() => result.current.setEdgeStyle({ labelColor: "blue" }));
137-
await act(() => result.current.resetEdgeStyle());
136+
act(() => result.current.setEdgeStyle({ labelColor: "blue" }));
137+
act(() => result.current.resetEdgeStyle());
138138

139139
expect(result.current.edgeStyle).toBeUndefined();
140140
});

packages/graph-explorer/src/core/StateProvider/userPreferences.ts

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { atomWithLocalForage } from "./localForageEffect";
2-
import { useAtom } from "jotai";
2+
import { useAtom, WritableAtom } from "jotai";
33
import { clone } from "lodash";
4+
import { useDeferredValue, useEffect, useState } from "react";
45

56
export type ShapeStyle =
67
| "rectangle"
@@ -101,6 +102,22 @@ export const userStylingAtom = atomWithLocalForage<UserStyling>(
101102
"user-styling"
102103
);
103104

105+
function useDeferredAtom<Value, Result>(
106+
atom: WritableAtom<Value, [Value], Result>
107+
) {
108+
const [atomValue, setAtomValue] = useAtom(atom);
109+
const [reactValue, setReactValue] = useState(atomValue);
110+
const deferredValue = useDeferredValue(reactValue);
111+
112+
// Update the atom value in an effect when React rendering sees a gap
113+
useEffect(() => {
114+
setAtomValue(deferredValue);
115+
}, [deferredValue, setAtomValue]);
116+
117+
// Only return the React state since we are managing all the atom state internally
118+
return [reactValue, setReactValue] as const;
119+
}
120+
104121
type UpdatedVertexStyle = Omit<VertexPreferences, "type">;
105122

106123
/**
@@ -110,14 +127,14 @@ type UpdatedVertexStyle = Omit<VertexPreferences, "type">;
110127
* @returns The vertex style if it exists, an update function, and a reset function
111128
*/
112129
export function useVertexStyling(type: string) {
113-
const [allStyling, setAllStyling] = useAtom(userStylingAtom);
130+
const [allStyling, setAllStyling] = useDeferredAtom(userStylingAtom);
114131

115132
const vertexStyle = allStyling.vertices?.find(v => v.type === type);
116133

117-
const setVertexStyle = async (updatedStyle: UpdatedVertexStyle) => {
118-
await setAllStyling(async prevPromise => {
134+
const setVertexStyle = (updatedStyle: UpdatedVertexStyle) => {
135+
setAllStyling(prevPromise => {
119136
// Shallow clone so React re-renders properly
120-
const prev = clone(await prevPromise);
137+
const prev = clone(prevPromise);
121138

122139
const hasEntry = prev.vertices?.some(v => v.type === type);
123140
if (hasEntry) {
@@ -143,9 +160,9 @@ export function useVertexStyling(type: string) {
143160
});
144161
};
145162

146-
const resetVertexStyle = async () =>
147-
await setAllStyling(async prevPromise => {
148-
const prev = clone(await prevPromise);
163+
const resetVertexStyle = () =>
164+
setAllStyling(prevPromise => {
165+
const prev = clone(prevPromise);
149166
prev.vertices = prev.vertices?.filter(v => v.type !== type);
150167
return prev;
151168
});
@@ -166,14 +183,14 @@ type UpdatedEdgeStyle = Omit<EdgePreferences, "type">;
166183
* @returns The edge style if it exists, an update function, and a reset function
167184
*/
168185
export function useEdgeStyling(type: string) {
169-
const [allStyling, setAllStyling] = useAtom(userStylingAtom);
186+
const [allStyling, setAllStyling] = useDeferredAtom(userStylingAtom);
170187

171188
const edgeStyle = allStyling.edges?.find(v => v.type === type);
172189

173-
const setEdgeStyle = async (updatedStyle: UpdatedEdgeStyle) => {
174-
await setAllStyling(async prevPromise => {
190+
const setEdgeStyle = (updatedStyle: UpdatedEdgeStyle) => {
191+
setAllStyling(prevPromise => {
175192
// Shallow clone so React re-renders properly
176-
const prev = clone(await prevPromise);
193+
const prev = clone(prevPromise);
177194

178195
const hasEntry = prev.edges?.some(v => v.type === type);
179196
if (hasEntry) {
@@ -199,9 +216,9 @@ export function useEdgeStyling(type: string) {
199216
});
200217
};
201218

202-
const resetEdgeStyle = async () =>
203-
await setAllStyling(async prevPromise => {
204-
const prev = clone(await prevPromise);
219+
const resetEdgeStyle = () =>
220+
setAllStyling(prevPromise => {
221+
const prev = clone(prevPromise);
205222
prev.edges = prev.edges?.filter(v => v.type !== type);
206223
return prev;
207224
});

packages/graph-explorer/src/modules/NodesStyling/NodeStyleDialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ function Content({ vertexType }: { vertexType: string }) {
125125
}
126126
try {
127127
const result = await file2Base64(file);
128-
await setVertexStyle({ iconUrl: result, iconImageType: file.type });
128+
setVertexStyle({ iconUrl: result, iconImageType: file.type });
129129
} catch (error) {
130130
console.error("Unable to convert uploaded image to base64: ", error);
131131
}

packages/graph-explorer/src/routes/DataExplorer/DataExplorer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,13 @@ function DisplayNameAndDescriptionOptions({
192192

193193
const { setVertexStyle: setPreferences } = useVertexStyling(vertexType);
194194
const onDisplayNameChange =
195-
(field: "name" | "longName") => async (value: string | string[]) => {
195+
(field: "name" | "longName") => (value: string | string[]) => {
196196
if (field === "name") {
197-
await setPreferences({ displayNameAttribute: value as string });
197+
setPreferences({ displayNameAttribute: value as string });
198198
}
199199

200200
if (field === "longName") {
201-
await setPreferences({ longDisplayNameAttribute: value as string });
201+
setPreferences({ longDisplayNameAttribute: value as string });
202202
}
203203
};
204204

0 commit comments

Comments
 (0)