Skip to content

Commit 4ffb6d3

Browse files
committed
Fix infinite re-renders when deleting a live record, fixes #21.
1 parent 8740751 commit 4ffb6d3

2 files changed

Lines changed: 112 additions & 2 deletions

File tree

packages/react-fate/src/__tests__/useLiveView.test.tsx

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
* @vitest-environment happy-dom
33
*/
44

5-
import { createClient, view, type ViewRef } from '@nkzw/fate';
6-
import { act, Suspense } from 'react';
5+
import { clientRoot, createClient, view, type ViewRef } from '@nkzw/fate';
6+
import { act, StrictMode, Suspense } from 'react';
77
import { createRoot } from 'react-dom/client';
88
import { expect, test, vi } from 'vite-plus/test';
99
import { FateClient } from '../context.tsx';
10+
import { useLiveListView } from '../useLiveListView.tsx';
1011
import { useLiveView } from '../useLiveView.tsx';
12+
import { useRequest } from '../useRequest.tsx';
1113

1214
// @ts-expect-error React 🤷‍♂️
1315
global.IS_REACT_ACT_ENVIRONMENT = true;
@@ -208,3 +210,107 @@ test('keeps the same live subscription when ref identity changes for the same en
208210

209211
expect(unsubscribe).toHaveBeenCalledTimes(1);
210212
});
213+
214+
test('deleting the final live item does not loop', async () => {
215+
let deletePost: ((id?: string | number) => void) | undefined;
216+
const client = createClient({
217+
roots: {
218+
posts: clientRoot<
219+
{
220+
items: ReadonlyArray<{
221+
node: Post;
222+
}>;
223+
},
224+
'Post'
225+
>('Post'),
226+
},
227+
transport: {
228+
async fetchById() {
229+
return [];
230+
},
231+
async fetchList() {
232+
return {
233+
items: [
234+
{
235+
cursor: 'post-1',
236+
node: {
237+
__typename: 'Post' as const,
238+
content: 'Apple',
239+
id: 'post-1',
240+
},
241+
},
242+
],
243+
pagination: {
244+
hasNext: false,
245+
hasPrevious: false,
246+
},
247+
};
248+
},
249+
subscribeById: (_type, _id, _select, _args, handlers) => {
250+
deletePost = handlers.onDelete;
251+
return () => {};
252+
},
253+
subscribeConnection: () => () => {},
254+
},
255+
types: [{ type: 'Post' }],
256+
});
257+
258+
const PostView = view<Post>()({
259+
content: true,
260+
id: true,
261+
});
262+
const PostConnectionView = {
263+
items: { node: PostView },
264+
live: { append: 'visible' as const },
265+
};
266+
267+
await client.request({
268+
posts: { list: PostConnectionView },
269+
});
270+
271+
const PostContent = ({ postRef }: { postRef: ViewRef<'Post'> }) => {
272+
const post = useLiveView(PostView, postRef);
273+
return <span>{post.content}</span>;
274+
};
275+
276+
const PostList = () => {
277+
const request = useRequest({
278+
posts: { list: PostConnectionView },
279+
});
280+
const [posts] = useLiveListView(PostConnectionView, request.posts);
281+
282+
return posts.length ? (
283+
posts.map(({ node }) => <PostContent key={node.id} postRef={node} />)
284+
) : (
285+
<span>No posts</span>
286+
);
287+
};
288+
289+
const container = document.createElement('div');
290+
const root = createRoot(container);
291+
292+
await act(async () => {
293+
root.render(
294+
<StrictMode>
295+
<FateClient client={client}>
296+
<Suspense fallback={null}>
297+
<PostList />
298+
</Suspense>
299+
</FateClient>
300+
</StrictMode>,
301+
);
302+
});
303+
304+
expect(container.textContent).toBe('Apple');
305+
expect(deletePost).toBeTypeOf('function');
306+
307+
await act(async () => {
308+
deletePost?.('post-1');
309+
});
310+
311+
expect(container.textContent).toBe('No posts');
312+
313+
await act(async () => {
314+
root.unmount();
315+
});
316+
});

packages/react-fate/src/useView.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ export function useView<V extends View<any, any>>(
115115

116116
mergedSnapshotRef.current = null;
117117
snapshotRef.current = null;
118+
if (!coverage.length) {
119+
return snapshot;
120+
}
121+
118122
return Promise.resolve(snapshot).then((value) => {
119123
const resolved = mergeCoverage(value);
120124
snapshotRef.current = resolved;

0 commit comments

Comments
 (0)