Skip to content

Commit 6ecbcf6

Browse files
committed
server sim
1 parent 4443a8d commit 6ecbcf6

14 files changed

Lines changed: 496 additions & 200 deletions

File tree

examples/benchmark-react/bench/runner.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ async function runScenario(
249249
state: 'attached',
250250
});
251251

252+
await (bench as any).evaluate((api: any) => api.flushPendingMutations());
253+
252254
if (scenario.networkDelayMs) {
253255
await (bench as any).evaluate((api: any) => api.setNetworkDelay(0));
254256
}

examples/benchmark-react/src/baseline/index.tsx

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ function TripleListView() {
8282
<div style={TRIPLE_LIST_STYLE}>
8383
{openItems.length > 0 && (
8484
<div data-status-list="open">
85+
<span data-status-count>{openItems.length}</span>
8586
<List
8687
style={LIST_STYLE}
8788
rowHeight={ITEM_HEIGHT}
@@ -93,6 +94,7 @@ function TripleListView() {
9394
)}
9495
{closedItems.length > 0 && (
9596
<div data-status-list="closed">
97+
<span data-status-count>{closedItems.length}</span>
9698
<List
9799
style={LIST_STYLE}
98100
rowHeight={ITEM_HEIGHT}
@@ -104,6 +106,7 @@ function TripleListView() {
104106
)}
105107
{inProgressItems.length > 0 && (
106108
<div data-status-list="in_progress">
109+
<span data-status-count>{inProgressItems.length}</span>
107110
<List
108111
style={LIST_STYLE}
109112
rowHeight={ITEM_HEIGHT}
@@ -165,17 +168,36 @@ function BenchmarkHarness() {
165168
setInProgressItems([]);
166169
}, [unmountBase]);
167170

171+
const refetchActiveList = useCallback(() => {
172+
if (tripleListCount != null) {
173+
return Promise.all([
174+
ItemResource.getList({ status: 'open', count: tripleListCount }).then(
175+
setOpenItems,
176+
),
177+
ItemResource.getList({
178+
status: 'closed',
179+
count: tripleListCount,
180+
}).then(setClosedItems),
181+
ItemResource.getList({
182+
status: 'in_progress',
183+
count: tripleListCount,
184+
}).then(setInProgressItems),
185+
]);
186+
}
187+
return ItemResource.getList({ count: listViewCount! }).then(setItems);
188+
}, [listViewCount, tripleListCount]);
189+
168190
const updateEntity = useCallback(
169191
(id: string) => {
170192
const item = FIXTURE_ITEMS_BY_ID.get(id);
171193
if (!item) return;
172194
measureUpdate(() =>
173195
ItemResource.update({ id }, { label: `${item.label} (updated)` }).then(
174-
() => ItemResource.getList({ count: listViewCount! }).then(setItems),
196+
refetchActiveList,
175197
),
176198
);
177199
},
178-
[measureUpdate, listViewCount],
200+
[measureUpdate, refetchActiveList],
179201
);
180202

181203
const updateAuthor = useCallback(
@@ -186,32 +208,26 @@ function BenchmarkHarness() {
186208
AuthorResource.update(
187209
{ id: authorId },
188210
{ name: `${author.name} (updated)` },
189-
).then(() =>
190-
ItemResource.getList({ count: listViewCount! }).then(setItems),
191-
),
211+
).then(refetchActiveList),
192212
);
193213
},
194-
[measureUpdate, listViewCount],
214+
[measureUpdate, refetchActiveList],
195215
);
196216

197217
const unshiftItem = useCallback(() => {
198218
const author = FIXTURE_AUTHORS[0];
199219
measureUpdate(() =>
200-
ItemResource.create({ label: 'New Item', author }).then(() =>
201-
ItemResource.getList({ count: listViewCount! }).then(setItems),
220+
ItemResource.create({ label: 'New Item', author }).then(
221+
refetchActiveList,
202222
),
203223
);
204-
}, [measureUpdate, listViewCount]);
224+
}, [measureUpdate, refetchActiveList]);
205225

206226
const deleteEntity = useCallback(
207227
(id: string) => {
208-
measureUpdate(() =>
209-
ItemResource.delete({ id }).then(() =>
210-
ItemResource.getList({ count: listViewCount! }).then(setItems),
211-
),
212-
);
228+
measureUpdate(() => ItemResource.delete({ id }).then(refetchActiveList));
213229
},
214-
[measureUpdate, listViewCount],
230+
[measureUpdate, refetchActiveList],
215231
);
216232

217233
const moveItem = useCallback(
@@ -238,16 +254,22 @@ function BenchmarkHarness() {
238254
const source = containerRef.current?.querySelector(
239255
'[data-status-list="open"]',
240256
);
241-
return source?.querySelector(`[data-item-id="${id}"]`) == null;
257+
const dest = containerRef.current?.querySelector(
258+
'[data-status-list="closed"]',
259+
);
260+
return (
261+
source?.querySelector(`[data-item-id="${id}"]`) == null &&
262+
dest?.querySelector(`[data-item-id="${id}"]`) != null
263+
);
242264
},
243265
);
244266
},
245267
[measureUpdate, tripleListCount, containerRef],
246268
);
247269

248270
const mountSortedView = useCallback(
249-
(n: number) => {
250-
seedItemList(FIXTURE_ITEMS.slice(0, n));
271+
async (n: number) => {
272+
await seedItemList(FIXTURE_ITEMS.slice(0, n));
251273
measureMount(() => {
252274
setShowSortedView(true);
253275
});

examples/benchmark-react/src/data-client/index.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ function StatusListView({ status, count }: { status: string; count: number }) {
6464
const list = items as Item[];
6565
return (
6666
<div data-status-list={status}>
67+
<span data-status-count>{list.length}</span>
6768
<List
6869
style={LIST_STYLE}
6970
rowHeight={ITEM_HEIGHT}
@@ -164,16 +165,22 @@ function BenchmarkHarness() {
164165
const source = containerRef.current?.querySelector(
165166
'[data-status-list="open"]',
166167
);
167-
return source?.querySelector(`[data-item-id="${id}"]`) == null;
168+
const dest = containerRef.current?.querySelector(
169+
'[data-status-list="closed"]',
170+
);
171+
return (
172+
source?.querySelector(`[data-item-id="${id}"]`) == null &&
173+
dest?.querySelector(`[data-item-id="${id}"]`) != null
174+
);
168175
},
169176
);
170177
},
171178
[measureUpdate, controller, containerRef],
172179
);
173180

174181
const mountSortedView = useCallback(
175-
(n: number) => {
176-
seedItemList(FIXTURE_ITEMS.slice(0, n));
182+
async (n: number) => {
183+
await seedItemList(FIXTURE_ITEMS.slice(0, n));
177184
measureMount(() => {
178185
setSortedViewCount(n);
179186
setShowSortedView(true);
@@ -183,10 +190,10 @@ function BenchmarkHarness() {
183190
);
184191

185192
const invalidateAndResolve = useCallback(
186-
(id: string) => {
187-
const item = getItem(id);
193+
async (id: string) => {
194+
const item = await getItem(id);
188195
if (item) {
189-
patchItem(id, { label: `${item.label} (refetched)` });
196+
await patchItem(id, { label: `${item.label} (refetched)` });
190197
}
191198
measureUpdate(
192199
() => {

examples/benchmark-react/src/shared/benchHarness.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useCallback, useEffect, useRef, useState } from 'react';
22

33
import { captureSnapshot, getReport } from './refStability';
4-
import { setNetworkDelay } from './server';
4+
import { flushPendingMutations, setNetworkDelay } from './server';
55
import type { BenchAPI } from './types';
66

77
export function afterPaint(fn: () => void): void {
@@ -190,6 +190,7 @@ export function useBenchState() {
190190
captureRefSnapshot,
191191
getRefStabilityReport,
192192
setNetworkDelay,
193+
flushPendingMutations,
193194
...libraryActions,
194195
} as BenchAPI;
195196
};

examples/benchmark-react/src/shared/components.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,27 @@ export const VISIBLE_COUNT = 40;
88
export const LIST_STYLE = { height: ITEM_HEIGHT * VISIBLE_COUNT } as const;
99
export const TRIPLE_LIST_STYLE = { display: 'flex', gap: 8 } as const;
1010

11+
const PRIORITY_LABELS = ['', 'low', 'med', 'high', 'crit', 'max'] as const;
12+
1113
/**
12-
* Pure presentational component - no data-fetching logic.
13-
* Each library app wraps this with its own data-fetching hook.
14+
* Memoized row component. With referential equality (data-client, tanstack-query),
15+
* unchanged items skip this entirely. Without it (SWR, baseline), every row
16+
* re-renders on any list change — the extra render weight makes that visible.
1417
*/
15-
export function ItemRow({ item }: { item: Item }) {
18+
export const ItemRow = React.memo(function ItemRow({ item }: { item: Item }) {
19+
const tagStr = item.tags.join(', ');
20+
const prioLabel = PRIORITY_LABELS[item.priority] ?? item.priority;
1621
return (
1722
<div data-item-id={item.id} data-bench-item>
1823
<span data-label>{item.label}</span>
1924
<span data-author>{item.author.name}</span>
25+
<span data-priority>{prioLabel}</span>
26+
<span data-status>{item.status}</span>
27+
<span data-tags>{tagStr}</span>
28+
<span data-desc>{item.description}</span>
2029
</div>
2130
);
22-
}
31+
});
2332

2433
/** Generic react-window row that renders an ItemRow from an items array. */
2534
export function ItemsRow({

examples/benchmark-react/src/shared/resources.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ class ItemCollection<
5454
S extends any[] | PolymorphicInterface = any,
5555
Parent extends any[] = [urlParams: any, body?: any],
5656
> extends Collection<S, Parent> {
57+
constructor(schema: S, options?: any) {
58+
super(schema, options);
59+
(this as any).move = this.moveWith((existing: any, incoming: any) => [
60+
...incoming,
61+
...existing,
62+
]);
63+
}
64+
5765
nonFilterArgumentKeys(key: string) {
5866
return key === 'count';
5967
}
@@ -70,7 +78,7 @@ export const ItemResource = resource({
7078
dataExpiryLength: Infinity,
7179
}),
7280
getList: Base.getList.extend({
73-
fetch: serverFetchItemList,
81+
fetch: serverFetchItemList as any,
7482
dataExpiryLength: Infinity,
7583
}),
7684
update: Base.update.extend({
@@ -81,7 +89,8 @@ export const ItemResource = resource({
8189
fetch: serverDeleteItem as any,
8290
}),
8391
create: Base.getList.unshift.extend({
84-
fetch: serverCreateItem as any,
92+
fetch: ((...args: any[]) =>
93+
serverCreateItem(args.length > 1 ? args[1] : args[0])) as any,
8594
body: {} as {
8695
label: string;
8796
author: Author;

0 commit comments

Comments
 (0)