Skip to content

Commit 0b76d37

Browse files
authored
fix: allow page to skip the rendering (#266)
1 parent dee7d36 commit 0b76d37

File tree

8 files changed

+122
-1
lines changed

8 files changed

+122
-1
lines changed

packages/core/src/core/core.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
StorageInterface,
2121
StoredRoute,
2222
} from "@rx-lab/common";
23-
import { RedirectError } from "@rx-lab/errors";
23+
import { RedirectError, SkipError } from "@rx-lab/errors";
2424
import { encodeStateKey, stateCache } from "@rx-lab/storage";
2525
import React from "react";
2626
import { Renderer } from "./renderer";
@@ -331,6 +331,11 @@ export class Core<T extends Container<BaseChatroomInfo, BaseMessage>>
331331
// ignore it
332332
return;
333333
}
334+
335+
if (e instanceof SkipError) {
336+
return;
337+
}
338+
334339
console.error(e);
335340
const props: ErrorPageProps = {
336341
error: e,
@@ -512,6 +517,13 @@ export class Core<T extends Container<BaseChatroomInfo, BaseMessage>>
512517
},
513518
);
514519
}
520+
521+
// skip the current render
522+
if (e instanceof SkipError) {
523+
// immediately timeout the current render
524+
this.lastCommitUpdateTime = 0;
525+
}
526+
515527
throw e;
516528
}
517529
}

packages/errors/src/errorCode.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ export enum ErrorCode {
1616
DuplicateKeyProps = 1004,
1717
// Redirect
1818
RedirectToNewLocation = 1005,
19+
// Skip
20+
Skip = 1006,
1921
}

packages/errors/src/errors/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from "./unsupportedReactInstance";
33
export * from "./missingRequiredKeyProps";
44
export * from "./duplicatedKeyProps";
55
export * from "./redirect";
6+
export * from "./skip";

packages/errors/src/errors/skip.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ErrorCode } from "../errorCode";
2+
import { CustomError } from "./error";
3+
4+
/**
5+
* Represents an error thrown when a page's rendering should be skipped for the current request.
6+
* This is typically used in middleware or routing logic to indicate that the regular page
7+
* rendering flow should be bypassed without treating it as a failure condition.
8+
*
9+
* @throws {SkipError} When the page rendering should be skipped
10+
* @extends {CustomError}
11+
*/
12+
export class SkipError extends CustomError {
13+
constructor() {
14+
super("Skip error", ErrorCode.Skip);
15+
}
16+
}

packages/router/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export {
88
matchRouteWithPath,
99
} from "./router";
1010
export { redirect } from "./redirect";
11+
export { skip } from "./skip";
1112

1213
interface SuspendableProps {
1314
children: any;

packages/router/src/skip.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { SkipError } from "@rx-lab/errors";
2+
3+
/**
4+
* Immediately terminates the current render operation and triggers a new render.
5+
* Useful for handling conditional rendering flows or invalidating the current render pass.
6+
*
7+
* Note: This operation can only be called during render, and will throw an error if called
8+
* during event handlers or effects.
9+
*
10+
* @throws {Error} If called outside of component render
11+
*
12+
* @example
13+
* ```tsx
14+
* export default function Page() {
15+
* if (shouldSkip) {
16+
* skip(); // Abandon current render and start fresh
17+
* }
18+
*
19+
* return <div>Content</div>;
20+
* }
21+
* ```
22+
*/
23+
export function skip() {
24+
throw new SkipError();
25+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Telegram, Utils } from "@rx-lab/testing";
2+
3+
const {
4+
PORT,
5+
Api,
6+
initialize,
7+
TestingEnvironment,
8+
MessageType,
9+
DEFAULT_RENDERING_WAIT_TIME,
10+
} = Telegram;
11+
const { sleep } = Utils;
12+
13+
const chatroomId = 2104;
14+
15+
describe("skip the page", () => {
16+
let api: Telegram.Api<any>;
17+
let coreApi: any;
18+
19+
beforeAll(async () => {
20+
api = new Api({
21+
baseUrl: `http://0.0.0.0:${PORT}`,
22+
});
23+
});
24+
25+
it("should skip the page rendering", async () => {
26+
const { core } = await initialize({
27+
filename: import.meta.url,
28+
environment: TestingEnvironment.LongPolling,
29+
api,
30+
chatroomId,
31+
});
32+
coreApi = core;
33+
34+
await api.chatroom.sendMessageToChatroom(chatroomId, {
35+
content: "Hello",
36+
type: MessageType.Text,
37+
});
38+
await sleep(DEFAULT_RENDERING_WAIT_TIME);
39+
let messages = await api.chatroom.getMessagesByChatroom(chatroomId);
40+
expect(messages.data.count).toBe(2);
41+
42+
await api.chatroom.sendMessageToChatroom(chatroomId, {
43+
content: "skip",
44+
type: MessageType.Text,
45+
});
46+
await sleep(DEFAULT_RENDERING_WAIT_TIME);
47+
messages = await api.chatroom.getMessagesByChatroom(chatroomId);
48+
expect(messages.data.count).toBe(3);
49+
});
50+
51+
afterEach(async () => {
52+
await coreApi?.onDestroy();
53+
});
54+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { PageProps } from "@rx-lab/common";
2+
import { skip } from "@rx-lab/router";
3+
4+
export default function Page({ text }: PageProps) {
5+
if (text === "skip") {
6+
skip();
7+
}
8+
9+
return <div>Page</div>;
10+
}

0 commit comments

Comments
 (0)