diff --git a/.github/workflows/client_js.yml b/.github/workflows/client_js.yml index 8b00b01..77a6b6c 100644 --- a/.github/workflows/client_js.yml +++ b/.github/workflows/client_js.yml @@ -1,4 +1,4 @@ -name: CI (@topical/core) +name: Client (JavaScript) on: push: diff --git a/.github/workflows/client_react.yml b/.github/workflows/client_react.yml new file mode 100644 index 0000000..4e307bd --- /dev/null +++ b/.github/workflows/client_react.yml @@ -0,0 +1,93 @@ +name: Client (React) + +on: + push: + branches: [main] + tags: ["client_react/v*"] + paths: + - "client_react/**" + - ".github/workflows/client_react.yml" + pull_request: + branches: [main] + paths: + - "client_react/**" + - ".github/workflows/client_react.yml" + +jobs: + test: + name: Test + runs-on: ubuntu-latest + + defaults: + run: + working-directory: client_react + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + cache: "npm" + cache-dependency-path: client_react/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Check + run: npm run check + + release: + name: Release to npm + needs: test + if: startsWith(github.ref, 'refs/tags/client_react/v') + runs-on: ubuntu-latest + + concurrency: + group: release-client-react + cancel-in-progress: false + + permissions: + contents: write + id-token: write + + defaults: + run: + working-directory: client_react + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + registry-url: "https://registry.npmjs.org" + cache: "npm" + cache-dependency-path: client_react/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Verify tag matches package.json version + run: | + TAG_VERSION="${GITHUB_REF#refs/tags/client_react/v}" + PKG_VERSION=$(node -p "require('./package.json').version") + echo "Tag version: $TAG_VERSION" + echo "package.json version: $PKG_VERSION" + if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then + echo "::error::Tag version ($TAG_VERSION) does not match package.json version ($PKG_VERSION)" + exit 1 + fi + + - name: Publish to npm + run: npm publish + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true diff --git a/client_react/CHANGELOG.md b/client_react/CHANGELOG.md index 48cc5e2..13b2a8a 100644 --- a/client_react/CHANGELOG.md +++ b/client_react/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## 0.3.2 + +### Features + +- Adds support for optional topic parameters. ## 0.3.1 diff --git a/client_react/biome.json b/client_react/biome.json new file mode 100644 index 0000000..1e3ba42 --- /dev/null +++ b/client_react/biome.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", + "files": { + "includes": ["src/**"] + }, + "assist": { "actions": { "source": { "organizeImports": "on" } } }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2 + } +} diff --git a/client_react/package-lock.json b/client_react/package-lock.json index ec92ff0..b087460 100644 --- a/client_react/package-lock.json +++ b/client_react/package-lock.json @@ -9,19 +9,185 @@ "version": "0.3.1", "license": "Apache-2.0", "dependencies": { - "@topical/core": "^0.2.2" + "@topical/core": "^0.3.0" }, "devDependencies": { - "@types/react": "^18.0.26" + "@biomejs/biome": "^2.3.11", + "@types/react": "^18.0.26", + "typescript": "^5.9.3" }, "peerDependencies": { "react": "^18.2.0" } }, + "node_modules/@biomejs/biome": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.11.tgz", + "integrity": "sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.3.11", + "@biomejs/cli-darwin-x64": "2.3.11", + "@biomejs/cli-linux-arm64": "2.3.11", + "@biomejs/cli-linux-arm64-musl": "2.3.11", + "@biomejs/cli-linux-x64": "2.3.11", + "@biomejs/cli-linux-x64-musl": "2.3.11", + "@biomejs/cli-win32-arm64": "2.3.11", + "@biomejs/cli-win32-x64": "2.3.11" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.11.tgz", + "integrity": "sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.11.tgz", + "integrity": "sha512-fh7nnvbweDPm2xEmFjfmq7zSUiox88plgdHF9OIW4i99WnXrAC3o2P3ag9judoUMv8FCSUnlwJCM1B64nO5Fbg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.11.tgz", + "integrity": "sha512-l4xkGa9E7Uc0/05qU2lMYfN1H+fzzkHgaJoy98wO+b/7Gl78srbCRRgwYSW+BTLixTBrM6Ede5NSBwt7rd/i6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.11.tgz", + "integrity": "sha512-XPSQ+XIPZMLaZ6zveQdwNjbX+QdROEd1zPgMwD47zvHV+tCGB88VH+aynyGxAHdzL+Tm/+DtKST5SECs4iwCLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.11.tgz", + "integrity": "sha512-/1s9V/H3cSe0r0Mv/Z8JryF5x9ywRxywomqZVLHAoa/uN0eY7F8gEngWKNS5vbbN/BsfpCG5yeBT5ENh50Frxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.11.tgz", + "integrity": "sha512-vU7a8wLs5C9yJ4CB8a44r12aXYb8yYgBn+WeyzbMjaCMklzCv1oXr8x+VEyWodgJt9bDmhiaW/I0RHbn7rsNmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.11.tgz", + "integrity": "sha512-PZQ6ElCOnkYapSsysiTy0+fYX+agXPlWugh6+eQ6uPKI3vKAqNp6TnMhoM3oY2NltSB89hz59o8xIfOdyhi9Iw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.11.tgz", + "integrity": "sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@topical/core": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@topical/core/-/core-0.2.2.tgz", - "integrity": "sha512-DoiN/n1QERC1wW8C0sPdsh/AokMHtTE8BKJrceo3VEdIjLuVttq2j5JNlrEwPWafF5ilPCvX8mrxTn5AJSqaDw==" + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@topical/core/-/core-0.3.0.tgz", + "integrity": "sha512-H/4nF6TfI9W2fUiDUmL8rtxCXNuTwcoAIJMPsjY/SuMFowCwS0RQr3543VWOMsmoe8hnHCKt//VltuwcBJNMxg==", + "license": "Apache-2.0" }, "node_modules/@types/prop-types": { "version": "15.7.5", @@ -81,13 +247,99 @@ "engines": { "node": ">=0.10.0" } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } } }, "dependencies": { + "@biomejs/biome": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.11.tgz", + "integrity": "sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ==", + "dev": true, + "requires": { + "@biomejs/cli-darwin-arm64": "2.3.11", + "@biomejs/cli-darwin-x64": "2.3.11", + "@biomejs/cli-linux-arm64": "2.3.11", + "@biomejs/cli-linux-arm64-musl": "2.3.11", + "@biomejs/cli-linux-x64": "2.3.11", + "@biomejs/cli-linux-x64-musl": "2.3.11", + "@biomejs/cli-win32-arm64": "2.3.11", + "@biomejs/cli-win32-x64": "2.3.11" + } + }, + "@biomejs/cli-darwin-arm64": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.11.tgz", + "integrity": "sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA==", + "dev": true, + "optional": true + }, + "@biomejs/cli-darwin-x64": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.11.tgz", + "integrity": "sha512-fh7nnvbweDPm2xEmFjfmq7zSUiox88plgdHF9OIW4i99WnXrAC3o2P3ag9judoUMv8FCSUnlwJCM1B64nO5Fbg==", + "dev": true, + "optional": true + }, + "@biomejs/cli-linux-arm64": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.11.tgz", + "integrity": "sha512-l4xkGa9E7Uc0/05qU2lMYfN1H+fzzkHgaJoy98wO+b/7Gl78srbCRRgwYSW+BTLixTBrM6Ede5NSBwt7rd/i6g==", + "dev": true, + "optional": true + }, + "@biomejs/cli-linux-arm64-musl": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.11.tgz", + "integrity": "sha512-XPSQ+XIPZMLaZ6zveQdwNjbX+QdROEd1zPgMwD47zvHV+tCGB88VH+aynyGxAHdzL+Tm/+DtKST5SECs4iwCLg==", + "dev": true, + "optional": true + }, + "@biomejs/cli-linux-x64": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.11.tgz", + "integrity": "sha512-/1s9V/H3cSe0r0Mv/Z8JryF5x9ywRxywomqZVLHAoa/uN0eY7F8gEngWKNS5vbbN/BsfpCG5yeBT5ENh50Frxg==", + "dev": true, + "optional": true + }, + "@biomejs/cli-linux-x64-musl": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.11.tgz", + "integrity": "sha512-vU7a8wLs5C9yJ4CB8a44r12aXYb8yYgBn+WeyzbMjaCMklzCv1oXr8x+VEyWodgJt9bDmhiaW/I0RHbn7rsNmw==", + "dev": true, + "optional": true + }, + "@biomejs/cli-win32-arm64": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.11.tgz", + "integrity": "sha512-PZQ6ElCOnkYapSsysiTy0+fYX+agXPlWugh6+eQ6uPKI3vKAqNp6TnMhoM3oY2NltSB89hz59o8xIfOdyhi9Iw==", + "dev": true, + "optional": true + }, + "@biomejs/cli-win32-x64": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.11.tgz", + "integrity": "sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg==", + "dev": true, + "optional": true + }, "@topical/core": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@topical/core/-/core-0.2.2.tgz", - "integrity": "sha512-DoiN/n1QERC1wW8C0sPdsh/AokMHtTE8BKJrceo3VEdIjLuVttq2j5JNlrEwPWafF5ilPCvX8mrxTn5AJSqaDw==" + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@topical/core/-/core-0.3.0.tgz", + "integrity": "sha512-H/4nF6TfI9W2fUiDUmL8rtxCXNuTwcoAIJMPsjY/SuMFowCwS0RQr3543VWOMsmoe8hnHCKt//VltuwcBJNMxg==" }, "@types/prop-types": { "version": "15.7.5", @@ -141,6 +393,12 @@ "requires": { "loose-envify": "^1.1.0" } + }, + "typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true } } } diff --git a/client_react/package.json b/client_react/package.json index cfa3813..e30127d 100644 --- a/client_react/package.json +++ b/client_react/package.json @@ -1,6 +1,6 @@ { "name": "@topical/react", - "version": "0.3.1", + "version": "0.3.2", "description": "React Topical client.", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -8,7 +8,11 @@ "test": "echo \"Error: no test specified\" && exit 1", "prepublishOnly": "npm run clean && npm run build", "build": "tsc", - "clean": "rm -rf dist" + "clean": "rm -rf dist", + "lint": "biome check", + "typecheck": "tsc --noEmit", + "check": "npm run lint && npm run typecheck", + "format": "biome format --write" }, "repository": { "type": "git", @@ -24,9 +28,11 @@ "react": "^18.2.0" }, "devDependencies": { - "@types/react": "^18.0.26" + "@biomejs/biome": "^2.3.11", + "@types/react": "^18.0.26", + "typescript": "^5.9.3" }, "dependencies": { - "@topical/core": "^0.2.2" + "@topical/core": "^0.3.0" } } diff --git a/client_react/src/index.ts b/client_react/src/index.ts index 34d5de5..b9233d0 100644 --- a/client_react/src/index.ts +++ b/client_react/src/index.ts @@ -1,3 +1,3 @@ export { default as SocketProvider } from "./provider"; -export { default as useTopic } from "./useTopic"; export { default as useSocket } from "./useSocket"; +export { default as useTopic } from "./useTopic"; diff --git a/client_react/src/provider.ts b/client_react/src/provider.ts index 2843f36..0901504 100644 --- a/client_react/src/provider.ts +++ b/client_react/src/provider.ts @@ -1,11 +1,11 @@ +import { Socket } from "@topical/core"; import { createContext, createElement, - ReactNode, + type ReactNode, useEffect, useState, } from "react"; -import { Socket } from "@topical/core"; export const Context = createContext(undefined); diff --git a/client_react/src/useSocket.ts b/client_react/src/useSocket.ts index a74ee3a..9e2b148 100644 --- a/client_react/src/useSocket.ts +++ b/client_react/src/useSocket.ts @@ -1,5 +1,5 @@ -import { useState, useEffect, useContext } from "react"; -import { Socket, SocketState } from "@topical/core"; +import type { Socket, SocketState } from "@topical/core"; +import { useContext, useEffect, useState } from "react"; import { Context } from "./provider"; diff --git a/client_react/src/useTopic.ts b/client_react/src/useTopic.ts index f51f3f6..55d9a39 100644 --- a/client_react/src/useTopic.ts +++ b/client_react/src/useTopic.ts @@ -1,45 +1,93 @@ -import { useState, useEffect, useCallback, useContext } from "react"; +import type { ParamsInput } from "@topical/core"; +import { useCallback, useContext, useEffect, useRef, useState } from "react"; import { Context } from "./provider"; -function arrayEqual(a: any[], b: any[]) { - return a.length == b.length && a.every((v, i) => v == b[i]); +function subscriptionReady( + topic: (string | undefined)[], + params: ParamsInput, +): boolean { + return ( + topic.every((p) => typeof p !== "undefined") && + Object.keys(params).every((k) => typeof params[k] !== "undefined") + ); +} + +// Generate a stable key for topic + params (handles undefined values) +function identityKey( + topic: (string | undefined)[], + params: ParamsInput, +): string { + const topicPart = topic.map((p) => encodeURIComponent(p ?? "")).join("/"); + const paramsPart = Object.keys(params) + .sort() + .map( + (k) => `${encodeURIComponent(k)}=${encodeURIComponent(params[k] ?? "")}`, + ) + .join("&"); + return `${topicPart}?${paramsPart}`; } -export default function useTopic(...topicParts: (string | undefined)[]): [ +// Stabilize topic and params - only update references when the key changes +function useStableIdentity( + topic: (string | undefined)[], + params: ParamsInput, + key: string, +): [(string | undefined)[], ParamsInput] { + const keyRef = useRef(key); + const stableRef = useRef({ topic, params }); + if (keyRef.current !== key) { + keyRef.current = key; + stableRef.current = { topic, params }; + } + return [stableRef.current.topic, stableRef.current.params]; +} + +export default function useTopic( + topic: (string | undefined)[], + params: ParamsInput = {}, +): [ T | undefined, { - notify: (action: string, ...args: any[]) => void; - execute: (action: string, ...args: any[]) => Promise; - error: any; + notify: (action: string, args?: unknown[]) => void; + execute: (action: string, args?: unknown[]) => Promise | undefined; + error: unknown; loading: boolean; }, ] { const socket = useContext(Context); - const [state, setState] = - useState<[(string | undefined)[], T | undefined, any]>(); + const [state, setState] = useState<[string, T | undefined, unknown]>(); + + const key = identityKey(topic, params); + const [stableTopic, stableParams] = useStableIdentity(topic, params, key); + const notify = useCallback( - (action: string, ...args: any[]) => { - return socket!.notify(topicParts, action, ...args); + (action: string, args: unknown[] = []) => { + return socket?.notify(stableTopic, action, args, stableParams); }, - [socket, ...topicParts], + [socket, stableTopic, stableParams], ); + const execute = useCallback( - (action: string, ...args: any[]) => { - return socket!.execute(topicParts, action, ...args); + (action: string, args: unknown[] = []) => { + return socket?.execute(stableTopic, action, args, stableParams); }, - [socket, ...topicParts], + [socket, stableTopic, stableParams], ); + useEffect(() => { - if (!topicParts.some((p) => typeof p == "undefined")) { + if (subscriptionReady(stableTopic, stableParams)) { return socket?.subscribe( - topicParts, - (v) => setState([topicParts, v, undefined]), - (e) => setState([topicParts, undefined, e]), + stableTopic, + stableParams, + (v) => setState([key, v, undefined]), + (e) => setState([key, undefined, e]), ); } - }, [socket, ...topicParts]); - const [stateTopic, value, error] = state || [undefined, undefined, undefined]; - const loading = !stateTopic || !arrayEqual(topicParts, stateTopic); + }, [socket, stableTopic, stableParams, key]); + + const [stateKey, value, error] = state || [undefined, undefined, undefined]; + const loading = stateKey !== key; + return [value, { notify, execute, error, loading }]; } diff --git a/server_ex/docs/javascript-client.md b/server_ex/docs/javascript-client.md index 252b485..3ed1a5d 100644 --- a/server_ex/docs/javascript-client.md +++ b/server_ex/docs/javascript-client.md @@ -107,9 +107,11 @@ Then use the `useTopic` hook in your components to subscribe to your topic: import { useTopic } from "@topical/react"; function List({ id }) { - const [list, { execute, notify, loading, error }] = useTopic("lists", id); + const [list, { execute, notify, loading, error }] = useTopic( + ["lists", id] + ); const addItem = useCallback( - (text: string) => execute("add_item", text), + (text: string) => execute("add_item", [text]), [execute] ); if (loading) { @@ -124,6 +126,26 @@ function List({ id }) { } ``` +### Parameters with useTopic + +Pass params as the second argument: + +```typescript +function Leaderboard({ gameId, region }) { + const [leaderboard, { execute, loading, error }] = useTopic( + ["leaderboards", gameId], + { region } // params + ); + + const addScore = useCallback( + (player: string, score: number) => execute("add_score", [player, score]), + [execute] + ); + + // ... +} +``` + If you need access to the underlying socket (or the status), you can use the `useSocket` hook: ```typescript