Skip to main content

Create a dynamic component

For a component to have dynamic behavior -that is, to being able to update or re-render its contents without reloading the whole page- it needs to be explicitely configured as a dynamic component.

Currently, creating a dynamic process is a bit more convolved process than creating a static component, and it requires the usage of the GetDynamicComponent helper function:

export const MyDynamicComponent = GetDynamicComponent<MyData,SXLGlobalContext>(
"my-dynamic-component",
async () => {
const data = await getData()
return data;
},
(data, props) => {
if (data.isPending) {
return <>Loading...</>;
}

return <div>Loaded: {data.value}</div>
},
);

// ...

<MyDynamicComponent.Render />

The GetDynamicComponent function receives three arguments:

  • Component ID (string): A unique ID used to identify the dynamic component.
  • Data Fetcher (() => MyData): A function that returns a Promise with the data the component needs to fetch. The type for the Promise contents is passed in the GetDynamicComponent first type parameter (e.g. MyData).
  • Render function: ((data:TrackedPromise<MyData>, props: SXL.Props) => JSX.Element): A function that receives the promise returned by the data fetcher, wrapped in TrackedPromise -which is an extension for Promise that exposes methods for tracking its state-, and the props passed to the component.

Updating a dynamic component

All dynamic components can be updated using the webAction helper:

<button
onclick={webAction({}, (ev, webContext) => {
webContext?
.actions?
.refetchElement("my-dynamic-component", {
// pass query parameters
});
})}>
Reload
</button>

This helper creates all bindings necessary to update a dynamic component.

The refetchElement receives two parameters:

  • Component ID (string): The ID for the component to update
  • Query parameters (Record<string, string>): A map of query parameters for the updated component.

Example: Rendering the server's date

Let us create an example to understand how to update dynamic components.

First, we will create a dynamic component called ServerDateComponent:

export const ServerDateComponent = GetDynamicComponent<
Date,
SXLGlobalContext & { mmDDYY?: boolean }
>(
"my-server-date-component",
async () => {
const serverDate = await getServerDate();
return serverDate;
},
(data, props) => {
if (data.isPending) {
return <>Loading...</>;
}

const serverDate: Date = data.value;

if (props?.globalContext?.mmDDYY) {
const dateFormatted = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "2-digit",
day: "2-digit",
}).format(serverDate);
return <div>Server date: {dateFormatted}</div>;
}
return <div>Server date: {serverDate.toISOString()}</div>;
},
);

// ...

<ServerDateComponent.Render />

The ServerDateComponent does a couple of things:

  • Two type parameters are passed to the GetDynamicComponent signature:
    • Date: The type for the data returned by the fetcher.
    • SXLGlobalContext & { mmDDYY?: boolean }: An extension of the global context SXLGlobalContext with the boolean query parameter mmDDYY.
  • We pass a fetcher function that retrieves a date from the server using getServerDate().
  • While the component is fetching the server date, a Loading message will be displayed.
  • Once the promise is resolved, the server Date object is retrieved using data.value.
  • Then, depending whether mmDDYY is true or false, it renders a string representation either in format mm/dd/yy or ISO.

Note: All this logic happens on the server side. The dynamic component on the browser-side will receive the fully rendered HTML content returned by the component. Any temporary state contained by the component on the browser (e.g. the text in any input field) will be discarded if it's not explicitely sent to the server as a query parameter.

Now, let's create a second static component which will update the contents of our dynamic ServerDateComponent:

export function ReplacerComponent() {
return (
<>
<button
onclick={webAction({}, (ev, webContext) => {
webContext?.actions?.refetchElement(
"my-server-date-component",
{
mmDDYY: true,
},
);
})}
>
Get server date on mm/dd/yyyy format
</button>
<button
onclick={webAction({}, (ev, webContext) => {
console.log("Replace");
webContext?.actions?.refetchElement(
"my-server-date-component",
{},
);
})}
>
Get server date on ISO format
</button>
</>
);
}
// ...
<ReplacerComponent/>

Notice a few things here:

  • Static components can use the webAction helper. A component doesn't need to be a dynamic component to update another dynamic component.
  • The component renders two buttons, both of which updates the dynamic component using its ID ("my-server-date-component"):
    • The first button passes the mmDDYY query parameter set to true.
    • The second button passes no query parameters, which will re-render the dynamic component using its default state (rendering the date in ISO format).

After rendering both components, we can see the dynamic component being updated correctly:

A GIF animation showing how the server date rendered by the dynamic component is updated every time one of the buttons is clicked

Configuring query params in the middlware

Currently, query parameters like mmDDYY need to be configured in LeanJSX middleware to be parsed from the request query parameters if they are not part of the global context:

app.use(
LeanApp.middleware({
configResponse: (resp) => resp.set("Content-Security-Policy", CSP),
globalContextParser: (req, componentId) => {
if (componentId === ServerDateComponent.contentId) {
return {
...parseQueryParams(req),
...{
mmDDYY: Boolean(req.query?.mmDDYY),
},
};
}
return parseQueryParams(req);
},
}),
);

We add a check in globalContextParser to see if componentId matches our component's ID, and if it does, we parse the mmDDYY query param. This allows us have query params that are specific to a component without polluting the global context.

Note: The middleware configuration may not be needed in future versions of LeanJSX.