Skip to content

Commit 31bc16c

Browse files
fix(react,js): blocks sometimes not showing up during initial request (#394)
* feat(react,js): fix blocks state * feat: update version to latest canary * @flows/react@1.9.1-canary.3 * test: that the blocks don't get overwritten --------- Co-authored-by: flows-bot[bot] <170794745+flows-bot[bot]@users.noreply.github.com>
1 parent 0291105 commit 31bc16c

File tree

6 files changed

+64
-22
lines changed

6 files changed

+64
-22
lines changed

workspaces/e2e/tests/init.spec.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
1-
import { test } from "@playwright/test";
1+
import { Block, BlockUpdatesPayload } from "@flows/shared";
2+
import { expect, Route, test, WebSocketRoute } from "@playwright/test";
3+
import { randomUUID } from "crypto";
24

5+
let ws: WebSocketRoute | null = null;
36
test.beforeEach(async ({ page }) => {
47
await page.routeWebSocket(
58
(url) => url.pathname === "/ws/sdk/block-updates",
6-
() => {},
9+
(_ws) => {
10+
ws = _ws;
11+
},
712
);
813
});
914

15+
const getBlock = (): Block => ({
16+
id: randomUUID(),
17+
workflowId: randomUUID(),
18+
type: "component",
19+
componentType: "Modal",
20+
data: { title: "Hello world", body: "" },
21+
exitNodes: [],
22+
slottable: false,
23+
propertyMeta: [],
24+
});
25+
1026
const run = (packageName: string) => {
1127
test(`${packageName} - should call blocks with correct parameters`, async ({ page }) => {
1228
await page.route("**/v2/sdk/blocks", (route) => {
@@ -50,6 +66,23 @@ const run = (packageName: string) => {
5066
);
5167
await blocksReq;
5268
});
69+
test(`${packageName} - shouldn't overwrite blocks state by /blocks result`, async ({ page }) => {
70+
let blocksRoute: Route | null = null;
71+
await page.route("**/v2/sdk/blocks", (route) => {
72+
blocksRoute = route;
73+
});
74+
await page.goto(`/${packageName}.html`);
75+
await expect(page.locator(".current-blocks")).toHaveText(JSON.stringify([]));
76+
const block = getBlock();
77+
const payload: BlockUpdatesPayload = {
78+
exitedBlockIds: [],
79+
updatedBlocks: [block],
80+
};
81+
ws?.send(JSON.stringify(payload));
82+
await expect(page.getByText("Hello world", { exact: true })).toBeVisible();
83+
(blocksRoute as Route | null)?.fulfill({ json: { blocks: [] } });
84+
await expect(page.getByText("Hello world", { exact: true })).toBeVisible();
85+
});
5386
};
5487

5588
run("js");

workspaces/js/src/lib/blocks.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { type BlockUpdatesPayload, getApi, log, type UserProperties } from "@flows/shared";
1+
import {
2+
type BlockUpdatesPayload,
3+
getApi,
4+
log,
5+
deduplicateBlocks,
6+
type UserProperties,
7+
} from "@flows/shared";
28
import { blocks } from "../store";
39
import { type Disconnect, websocket } from "./websocket";
410
import { packageAndVersion } from "./constants";
@@ -25,7 +31,7 @@ export const connectToWebsocketAndFetchBlocks = (props: Props): void => {
2531
void getApi(apiUrl, packageAndVersion)
2632
.getBlocks({ ...params, userProperties: props.userProperties })
2733
.then((res) => {
28-
blocks.value = res.blocks;
34+
blocks.value = deduplicateBlocks(blocks.value, res.blocks);
2935
// Disconnect if the user is usage limited
3036
if (res.meta?.usage_limited) disconnect?.();
3137
})
@@ -35,14 +41,9 @@ export const connectToWebsocketAndFetchBlocks = (props: Props): void => {
3541
};
3642
const onMessage = (event: MessageEvent<unknown>): void => {
3743
const data = JSON.parse(event.data as string) as BlockUpdatesPayload;
38-
const exitedOrUpdatedBlockIdsSet = new Set([
39-
...data.exitedBlockIds,
40-
...data.updatedBlocks.map((b) => b.id),
41-
]);
42-
blocks.value = [
43-
...blocks.value.filter((block) => !exitedOrUpdatedBlockIdsSet.has(block.id)),
44-
...data.updatedBlocks,
45-
];
44+
const exitedBlockIdsSet = new Set(data.exitedBlockIds);
45+
const filteredBlocks = blocks.value.filter((b) => !exitedBlockIdsSet.has(b.id));
46+
blocks.value = deduplicateBlocks(filteredBlocks, data.updatedBlocks);
4647
};
4748

4849
// Disconnect previous connection if it exists

workspaces/react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@flows/react",
3-
"version": "1.9.0",
3+
"version": "1.9.1-canary.3",
44
"description": "Flows React SDK – Build native product growth experiences, your way",
55
"keywords": [
66
"react",

workspaces/react/src/hooks/use-blocks.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
type Block,
77
type TourStep,
88
type BlockUpdatesPayload,
9+
deduplicateBlocks,
910
} from "@flows/shared";
1011
import { packageAndVersion } from "../lib/constants";
1112
import { type RemoveBlock, type UpdateBlock } from "../flows-context";
@@ -49,7 +50,7 @@ export const useBlocks = ({
4950
void getApi(apiUrl, packageAndVersion)
5051
.getBlocks({ ...params, userProperties: userPropertiesRef.current })
5152
.then((res) => {
52-
setBlocks(res.blocks);
53+
setBlocks((prevBlocks) => deduplicateBlocks(prevBlocks, res.blocks));
5354
if (res.meta?.usage_limited) setUsageLimited(true);
5455
})
5556
.catch((err: unknown) => {
@@ -67,14 +68,11 @@ export const useBlocks = ({
6768
// TODO: add debug logging
6869
// console.log("Message from server", event.data);
6970
const data = JSON.parse(event.data as string) as BlockUpdatesPayload;
70-
const exitedOrUpdatedBlockIdsSet = new Set([
71-
...data.exitedBlockIds,
72-
...data.updatedBlocks.map((b) => b.id),
73-
]);
74-
setBlocks((prevBlocks) => [
75-
...prevBlocks.filter((block) => !exitedOrUpdatedBlockIdsSet.has(block.id)),
76-
...data.updatedBlocks,
77-
]);
71+
const exitedBlockIdsSet = new Set(data.exitedBlockIds);
72+
setBlocks((prevBlocks) => {
73+
const filteredBlocks = prevBlocks.filter((b) => !exitedBlockIdsSet.has(b.id));
74+
return deduplicateBlocks(filteredBlocks, data.updatedBlocks);
75+
});
7876
}, []);
7977
useWebsocket({ url: websocketUrl, onMessage, onOpen: fetchBlocks });
8078

workspaces/shared/src/blocks.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { type Block } from "./types";
2+
3+
export const deduplicateBlocks = (prevBlocks: Block[], updatedBlocks: Block[]): Block[] => {
4+
const blocksById = new Map(prevBlocks.map((b) => [b.id, b]));
5+
updatedBlocks.forEach((updatedBlock) => {
6+
blocksById.set(updatedBlock.id, updatedBlock);
7+
});
8+
return Array.from(blocksById.values());
9+
};

workspaces/shared/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from "./api";
2+
export * from "./blocks";
23
export * from "./component-props";
34
export * from "./log";
45
export * from "./matchers";

0 commit comments

Comments
 (0)