Skip to content

Commit 5cb5d3a

Browse files
eluce2claude
andcommitted
docs(webviewer): add Initial Props page
Document the pull-not-push pattern for bootstrap data: web viewer fetches initial props via fmFetch once mounted, rather than FileMaker pushing via HTML substitution or PerformJavaScriptInWebViewer (which races the bundle load). Includes initial-route and current-user examples. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 5624681 commit 5cb5d3a

2 files changed

Lines changed: 189 additions & 0 deletions

File tree

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
---
2+
title: "Initial Props"
3+
description: "Pull bootstrap data from FileMaker into the Web Viewer at startup."
4+
---
5+
6+
import { Callout } from "fumadocs-ui/components/callout";
7+
8+
The **initial props** pattern is how a Web Viewer app gets bootstrap data from FileMaker at startup &mdash; things like the current user, the active record ID, or a starting route.
9+
10+
## Pull, don't push
11+
12+
The Web Viewer asks FileMaker for its initial props. FileMaker does not push them in.
13+
14+
Concretely: the Web Viewer calls a FileMaker script via [`fmFetch`](/docs/webviewer/fmFetch) once it has mounted, and uses the returned data to finish bootstrapping.
15+
16+
<Callout type="warn" title="Don't push from FileMaker">
17+
Do **not** seed initial props by:
18+
19+
- Substituting values into the HTML / web viewer URL before render.
20+
- Calling `FileMaker.PerformJavaScriptInWebViewer` from an `OnLayoutEnter` or `OnRecordLoad` script trigger when the viewer opens.
21+
22+
Both paths race against the web app loading. The script step or substitution can fire before your JavaScript bundle has parsed and your handler is attached, and the props are silently lost.
23+
</Callout>
24+
25+
The pull direction inverts the timing problem. By the time the Web Viewer makes the `fmFetch` call, it has confirmed three things at once:
26+
27+
1. The JavaScript bundle has loaded and executed.
28+
2. `window.FileMaker` has been injected by Pro / Go and is callable.
29+
3. Any handlers the FileMaker script needs to call back into (via the `SendCallBack` script) are wired up.
30+
31+
In the ProofKit Web Viewer template, the `fmFetch` call fires the moment the web code loads &mdash; **before** the router mounts and **before** the first route renders. The router then receives the resolved props as part of its context, so the first screen a user sees can already depend on FileMaker state (the signed-in user, the active record, a starting route) without a flash of empty UI or a post-mount re-render.
32+
33+
## Basic shape
34+
35+
```ts title="bootstrap.ts"
36+
import { fmFetch } from "@proofkit/webviewer";
37+
import { z } from "zod";
38+
39+
const initialPropsSchema = z.object({
40+
user: z.object({
41+
id: z.string(),
42+
name: z.string(),
43+
email: z.email(),
44+
}),
45+
initialRoute: z.string().optional(),
46+
});
47+
48+
type InitialProps = z.infer<typeof initialPropsSchema>;
49+
50+
export async function getInitialProps(): Promise<InitialProps> {
51+
const result = await fmFetch("GetInitialProps");
52+
return initialPropsSchema.parse(result);
53+
}
54+
```
55+
56+
On the FileMaker side, `GetInitialProps` collects whatever the app needs and sends it back through the standard `fmFetch` callback (see [fmFetch](/docs/webviewer/fmFetch) for the script shape).
57+
58+
<Callout type="info">
59+
Validate the script result with [zod](https://zod.dev) (or similar). The Web
60+
Viewer cannot trust shape inference across the FileMaker boundary.
61+
</Callout>
62+
63+
## Example: initial route
64+
65+
A Web Viewer that uses a client-side router (e.g. TanStack Router with hash history) can be told where to start. Useful when one FileMaker layout hosts a viewer that should land on different screens depending on context.
66+
67+
```ts title="src/router.ts"
68+
import { fmFetch } from "@proofkit/webviewer";
69+
import { createHashHistory, createRouter } from "@tanstack/react-router";
70+
import { z } from "zod";
71+
import { routeTree } from "./route-tree";
72+
73+
const initialPropsSchema = z.object({
74+
initialRoute: z.string().optional(),
75+
});
76+
77+
const GET_INITIAL_PROPS_SCRIPT = "GetInitialProps";
78+
79+
export async function createAppRouter() {
80+
const result = await fmFetch(GET_INITIAL_PROPS_SCRIPT);
81+
const { initialRoute } = initialPropsSchema.parse(result);
82+
83+
if (initialRoute && !window.location.hash) {
84+
window.location.hash = initialRoute;
85+
}
86+
87+
return createRouter({
88+
history: createHashHistory(),
89+
routeTree,
90+
});
91+
}
92+
```
93+
94+
```FileMaker title="GetInitialProps"
95+
Set Variable [ $json ; Value: Get ( ScriptParameter ) ]
96+
Set Variable [ $callback ; Value: JSONGetElement ( $json ; "callback" ) ]
97+
98+
# Pick a starting route based on whatever FileMaker context matters.
99+
If [ not IsEmpty ( Customers::id ) ]
100+
Set Variable [ $route ; Value: "/customers/" & Customers::id ]
101+
Else
102+
Set Variable [ $route ; Value: "/" ]
103+
End If
104+
105+
Set Variable [ $result ; Value: JSONSetElement ( "" ;
106+
[ "initialRoute" ; $route ; JSONString ]
107+
) ]
108+
109+
Set Variable [ $callback ; Value: JSONSetElement ( $callback ;
110+
[ "result" ; $result ; JSONObject ] ;
111+
[ "webViewerName" ; "web" ; JSONString ]
112+
) ]
113+
Perform Script [ Specified: From list ; "SendCallBack" ; Parameter: $callback ]
114+
```
115+
116+
The check on `window.location.hash` matters: if the user has already navigated inside the viewer, you should not yank them back to the initial route on a refresh.
117+
118+
## Example: current user
119+
120+
Hand the app the user identity it needs to render.
121+
122+
```ts title="src/lib/initial-props.ts"
123+
import { fmFetch } from "@proofkit/webviewer";
124+
import { z } from "zod";
125+
126+
export const initialPropsSchema = z.object({
127+
user: z.object({
128+
accountName: z.string(),
129+
fullName: z.string(),
130+
privilegeSet: z.string(),
131+
}),
132+
});
133+
134+
export type InitialProps = z.infer<typeof initialPropsSchema>;
135+
136+
export async function fetchInitialProps(): Promise<InitialProps> {
137+
const result = await fmFetch("GetInitialProps");
138+
return initialPropsSchema.parse(result);
139+
}
140+
```
141+
142+
```FileMaker title="GetInitialProps"
143+
Set Variable [ $json ; Value: Get ( ScriptParameter ) ]
144+
Set Variable [ $callback ; Value: JSONGetElement ( $json ; "callback" ) ]
145+
146+
Set Variable [ $result ; Value: JSONSetElement ( "" ;
147+
[ "user.accountName" ; Get ( AccountName ) ; JSONString ] ;
148+
[ "user.fullName" ; Get ( UserName ) ; JSONString ] ;
149+
[ "user.privilegeSet" ; Get ( AccountPrivilegeSetName ) ; JSONString ]
150+
) ]
151+
152+
Set Variable [ $callback ; Value: JSONSetElement ( $callback ;
153+
[ "result" ; $result ; JSONObject ] ;
154+
[ "webViewerName" ; "web" ; JSONString ]
155+
) ]
156+
Perform Script [ Specified: From list ; "SendCallBack" ; Parameter: $callback ]
157+
```
158+
159+
In the app, gate render on the bootstrap:
160+
161+
```tsx title="src/main.tsx"
162+
import React from "react";
163+
import ReactDOM from "react-dom/client";
164+
import App from "./app";
165+
import { fetchInitialProps } from "./lib/initial-props";
166+
167+
const propsPromise = fetchInitialProps();
168+
169+
function Boot() {
170+
const props = React.use(propsPromise);
171+
return <App initialProps={props} />;
172+
}
173+
174+
ReactDOM.createRoot(document.querySelector("#root")!).render(
175+
<React.StrictMode>
176+
<React.Suspense fallback={<div>Loading…</div>}>
177+
<Boot />
178+
</React.Suspense>
179+
</React.StrictMode>,
180+
);
181+
```
182+
183+
## When initial props are not the right tool
184+
185+
Initial props are for **bootstrap**. They are fetched once. If a value can change while the viewer is open (the active record, a selected portal row, a setting toggled elsewhere in the file), prefer:
186+
187+
- An explicit refresh via [`fmFetch`](/docs/webviewer/fmFetch) triggered by a user action.
188+
- A FileMaker-initiated push via `FileMaker.PerformJavaScriptInWebViewer` once you know the viewer is ready &mdash; e.g. after the initial-props handshake has completed.

apps/docs/content/docs/webviewer/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"data-access",
1414
"filemaker-scripts-as-backend",
1515
"commands",
16+
"initial-props",
1617
"platform-notes",
1718
"deployment-methods",
1819
"---Reference---",

0 commit comments

Comments
 (0)