Packages
React inside Phoenix LiveView — async code splitting, client-side props diffing, and zero-config component discovery.
Retired package: Release invalid - Not production-ready
Current section
Files
Jump to
Current section
Files
assets/server.ts
import { createElement } from "react";
import { renderToString } from "react-dom/server";
import { getComponentTree } from "./utils";
import { resolveComponent } from "./hooks";
import type { ComponentsOrResolve, HookProps } from "./types";
function getChildren(slots: Record<string, string | undefined>): React.ReactNode[] {
if (!slots?.default) {
return [];
}
return [
createElement("div", {
dangerouslySetInnerHTML: { __html: slots.default.trim() },
}),
];
}
/**
* Create a server-side render function for the given component input.
*
* Supports the same three input shapes as `getHooks()`:
* 1. Static object: `getRender({ Counter, Chart })`
* 2. Async loader functions: `getRender({ Counter: () => import("./Counter") })`
* 3. Resolve function: `getRender((name) => import("./components/" + name))`
*
* @param input — a component map (static or lazy) or a resolve function
* @returns an async render function that accepts (name, props, slots) and returns HTML
*/
export function getRender(input: ComponentsOrResolve) {
return async function render(
name: string,
props: Record<string, unknown>,
slots: Record<string, string | undefined>,
): Promise<string> {
const Component = await resolveComponent(input, name);
const children = getChildren(slots);
// Build a no-op HookProps for SSR context (server has no live connection)
const noop = () => {};
const hookProps: HookProps = {
pushEvent: noop as HookProps["pushEvent"],
pushEventTo: noop as HookProps["pushEventTo"],
handleEvent: noop as unknown as HookProps["handleEvent"],
removeHandleEvent: noop,
upload: noop,
uploadTo: noop,
};
const fullProps = { ...props, ...hookProps };
const tree = getComponentTree(
Component,
fullProps as Record<string, unknown> & HookProps,
children,
);
return renderToString(tree);
};
}