Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"@release-it/conventional-changelog": "^10.0.1",
"@types/jest": "^29.5.14",
"@types/react": "^19.1.12",
"@types/react-test-renderer": "^19.1.0",
"commitlint": "^19.8.1",
"del-cli": "^6.0.0",
"eslint": "^9.35.0",
Expand All @@ -85,6 +86,7 @@
"react-native-enriched-markdown": "0.6.0",
"react-native-sse": "1.2.1",
"react-native-worklets": "0.10.2",
"react-test-renderer": "19.2.3",
"release-it": "^19.0.4",
"remend": "1.3.0",
"turbo": "^2.5.6",
Expand Down
55 changes: 55 additions & 0 deletions src/__tests__/useStreamdownMarkdown.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { act } from 'react';
import TestRenderer from 'react-test-renderer';
import { useStreamdownMarkdown } from '../hooks/useStreamdownMarkdown';

jest.mock('../worklets/remendWorklet', () => ({
processRemendInWorklet: jest.fn(
(markdown: string, onComplete: (r: string) => void) => {
onComplete(markdown);
}
),
}));

const { processRemendInWorklet } = jest.requireMock(
'../worklets/remendWorklet'
) as {
processRemendInWorklet: jest.Mock;
};

function Consumer({ markdown, config }: { markdown: string; config: unknown }) {
useStreamdownMarkdown(markdown, { remendConfig: config as never });
return null;
}

describe('useStreamdownMarkdown', () => {
beforeEach(() => {
processRemendInWorklet.mockClear();
});

it('does not re-run the worklet job on idle re-renders when remendConfig is an inline literal', () => {
const Parent = ({ markdown }: { markdown: string }) => (
<Consumer markdown={markdown} config={{ katex: true }} />
);

let renderer!: TestRenderer.ReactTestRenderer;
act(() => {
renderer = TestRenderer.create(<Parent markdown="hi" />);
});
expect(processRemendInWorklet).toHaveBeenCalledTimes(1);

// Idle parent re-renders (same markdown) must not reschedule the job.
act(() => {
renderer.update(<Parent markdown="hi" />);
});
act(() => {
renderer.update(<Parent markdown="hi" />);
});
expect(processRemendInWorklet).toHaveBeenCalledTimes(1);

// A markdown change still schedules a new job.
act(() => {
renderer.update(<Parent markdown="hi there" />);
});
expect(processRemendInWorklet).toHaveBeenCalledTimes(2);
});
});
9 changes: 8 additions & 1 deletion src/hooks/useStreamdownMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export function useStreamdownMarkdown(
const [isStreaming, setIsStreaming] = useState(false);
const versionRef = useRef(0);

// Gate the effect on the config's content, not its identity: an inline
// remendConfig is a fresh object every render and would otherwise reschedule
// the worklet job on every commit.
const remendConfigKey = JSON.stringify(options?.remendConfig);

useEffect(() => {
if (markdown === '') {
setProcessedMarkdown('');
Expand All @@ -39,7 +44,9 @@ export function useStreamdownMarkdown(
},
options?.remendConfig
);
}, [markdown, options?.remendConfig]);
// Reads the live options?.remendConfig; remendConfigKey gates re-runs.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [markdown, remendConfigKey]);

return { processedMarkdown, isStreaming };
}
41 changes: 40 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3625,6 +3625,24 @@ __metadata:
languageName: node
linkType: hard

"@types/react-test-renderer@npm:^19.1.0":
version: 19.1.0
resolution: "@types/react-test-renderer@npm:19.1.0"
dependencies:
"@types/react": "npm:*"
checksum: 10c0/799654e430df10aeaf267d71507fb64ec151359ead7e3774111bfd4abce7e0911dba461811195c06c22a6d17496ea92537d3185320ff4112fe29954cad1b9152
languageName: node
linkType: hard

"@types/react@npm:*":
version: 19.2.18
resolution: "@types/react@npm:19.2.18"
dependencies:
csstype: "npm:^3.2.2"
checksum: 10c0/d04216172b4b4362b310017c210dfbe019fb4f4e7dffd0313e70b3acb3051d5c3e76e7e84e3cf4c6a3c824993ffadfe3949e589a6398a09d4200e7670e2962de
languageName: node
linkType: hard

"@types/react@npm:^19.1.12, @types/react@npm:^19.2.0":
version: 19.2.14
resolution: "@types/react@npm:19.2.14"
Expand Down Expand Up @@ -10446,6 +10464,13 @@ __metadata:
languageName: node
linkType: hard

"react-is@npm:^19.2.3":
version: 19.2.8
resolution: "react-is@npm:19.2.8"
checksum: 10c0/ed5322c84efe035c8fc814b1614ff5ca7fb8c2872a7c045197daf99f9b1bd68f32a2c79ffcbcfcff2fc5d93924ff9f9447400898f5b1534e6302bb0736a257f0
languageName: node
linkType: hard

"react-native-builder-bob@npm:^0.40.18":
version: 0.40.18
resolution: "react-native-builder-bob@npm:0.40.18"
Expand Down Expand Up @@ -10564,6 +10589,7 @@ __metadata:
"@release-it/conventional-changelog": "npm:^10.0.1"
"@types/jest": "npm:^29.5.14"
"@types/react": "npm:^19.1.12"
"@types/react-test-renderer": "npm:^19.1.0"
commitlint: "npm:^19.8.1"
del-cli: "npm:^6.0.0"
eslint: "npm:^9.35.0"
Expand All @@ -10578,6 +10604,7 @@ __metadata:
react-native-enriched-markdown: "npm:0.6.0"
react-native-sse: "npm:1.2.1"
react-native-worklets: "npm:0.10.2"
react-test-renderer: "npm:19.2.3"
release-it: "npm:^19.0.4"
remend: "npm:1.3.0"
turbo: "npm:^2.5.6"
Expand Down Expand Up @@ -10674,6 +10701,18 @@ __metadata:
languageName: node
linkType: hard

"react-test-renderer@npm:19.2.3":
version: 19.2.3
resolution: "react-test-renderer@npm:19.2.3"
dependencies:
react-is: "npm:^19.2.3"
scheduler: "npm:^0.27.0"
peerDependencies:
react: ^19.2.3
checksum: 10c0/842b82239dbddbc536083a6260c3e1b0507c02a3400bd05879fc19160468fd0f8ab79fec5dceffa6113b131835cc7621212f8415b46ea5156ab66bbfd7e24297
languageName: node
linkType: hard

"react@npm:19.2.3":
version: 19.2.3
resolution: "react@npm:19.2.3"
Expand Down Expand Up @@ -11065,7 +11104,7 @@ __metadata:
languageName: node
linkType: hard

"scheduler@npm:0.27.0":
"scheduler@npm:0.27.0, scheduler@npm:^0.27.0":
version: 0.27.0
resolution: "scheduler@npm:0.27.0"
checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452
Expand Down
Loading