docs/docs/03-syntax-and-usage/20-using-react-with-templ.md
templ is great for server-side rendering. Combined with htmx, it's even more powerful, since htmx can be used to replace elements within the page with updated HTML fetched from the server, providing many of the benefits of React with reduced overall complexity. See /server-side-rendering/htmx for an example.
However, React has a huge ecosystem of rich interactive components, so being able to tap into the ecosystem is very useful.
With templ, it's more likely that you will use React components as islands of interactivity rather than taking over all aspects of displaying your app, with templ taking over server-side rendering, but using React to provide specific features on the client side.
First, lets start by rendering simple React components.
To use React components in your templ app, create your React components using TSX (TypeScript) or JSX as usual.
export const Header = () => (<h1>React component Header</h1>);
export const Body = () => (<div>This is client-side content from React</div>);
Next, use templ to create a page containing HTML elements with specific IDs.
:::note
This page defines elements with ids of react-header and react-content.
A <script> element loads in a JavaScript bundle that we haven't created yet.
:::
package main
templ page() {
<html>
<body>
<div id="react-header"></div>
<div id="react-content"></div>
<div>This is server-side content from templ.</div>
<!-- Load the React bundle created using esbuild -->
<script src="static/index.js"></script>
</body>
</html>
}
:::tip
Remember to run templ generate when you've finished writing your templ file.
:::
Write TypeScript or JavaScript to render the React components into the HTML elements that are rendered by templ.
import { createRoot } from 'react-dom/client';
import { Header, Body } from './components';
// Render the React component into the templ page at the react-header.
const headerRoot = document.getElementById('react-header');
if (!headerRoot) {
throw new Error('Could not find element with id react-header');
}
const headerReactRoot = createRoot(headerRoot);
headerReactRoot.render(Header());
// Add the body React component.
const contentRoot = document.getElementById('react-content');
if (!contentRoot) {
throw new Error('Could not find element with id react-content');
}
const contentReactRoot = createRoot(contentRoot);
contentReactRoot.render(Body());
To turn the JSX, TSX, TypeScript and JavaScript code into a bundle that can run in the browser, use a bundling tool.
https://esbuild.github.io/ is commonly used for this task. It's fast, it's easy to use, and it's written in Go.
Executing esbuild with the following arguments creates an index.js file in the static directory.
esbuild --bundle index.ts --outdir=../static --minify
To serve the server-side rendered templ template, and the client-side JavaScript bundle created in the previous step, setup a Go web server.
package main
import (
"fmt"
"log"
"net/http"
"github.com/a-h/templ"
)
func main() {
mux := http.NewServeMux()
// Serve the templ page.
mux.Handle("/", templ.Handler(page()))
// Serve static content.
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
// Start the server.
fmt.Println("listening on localhost:8080")
if err := http.ListenAndServe("localhost:8080", mux); err != nil {
log.Printf("error listening: %v", err)
}
}
Putting this together results in a web page that renders server-side HTML using templ. The server-side HTML includes a link to the static React bundle.
sequenceDiagram
browser->>app: GET /
activate app
app->>templ_component: Render
activate templ_component
templ_component->>app: HTML
deactivate templ_component
app->>browser: HTML
deactivate app
browser->>app: GET /static/index.js
activate app
app->>browser: JS bundle containing React components
deactivate app
browser->>browser: render components into react-header and react-content
Moving on from the previous example, it's possible to pass data to client-side React components.
First, add a new component.
export const Hello = (name: string) => (
<div>Hello {name} (Client-side React, rendering server-side data)</div>
);
// Update the import to add the new Hello React component.
import { Header, Body, Hello } from './components';
// Previous script contents...
export function renderHello(e: HTMLElement) {
const name = e.getAttribute('data-name') ?? "";
createRoot(e).render(Hello(name));
}
Now that we have a renderHello function that will render the React component to the given element, we can update the templ components to use it.
In templ, we can add a Hello component that does two things:
data-name attribute to the value of the server-side name field.renderHello function to mount the React component into the element.:::note
The template renders three copies of the Hello React component, passing in a distinct name parameter ("Alice", "Bob" and "Charlie").
:::
package main
import "fmt"
templ Hello(name string) {
<div data-name={ name }>
<script>
bundle.renderHello(document.currentScript.closest('div'));
</script>
</div>
}
templ page() {
<html>
<head>
<title>React integration</title>
</head>
<body>
<div id="react-header"></div>
<div id="react-content"></div>
<div>
This is server-side content from templ.
</div>
<!-- Load the React bundle that was created using esbuild -->
<!-- Since the bundle was coded to expect the react-header and react-content elements to exist already, in this case, the script has to be loaded after the elements are on the page -->
<script src="static/index.js"></script>
<!-- Now that the React bundle is loaded, we can use the functions that are in it -->
<!-- the renderName function in the bundle can be used, but we want to pass it some server-side data -->
for _, name := range []string{"Alice", "Bob", "Charlie"} {
@Hello(name)
}
</body>
</html>
}
esbuild commandThe bundle namespace in JavaScript is created by adding a --global-name argument to esbuild. The argument causes any exported functions in index.ts to be added to that namespace.
esbuild --bundle index.ts --outdir=../static --minify --global-name=bundle
The HTML that's rendered is:
<html>
<head>
<title>React integration</title>
</head>
<body>
<div id="react-header"></div>
<div id="react-content"></div>
<div>This is server-side content from templ.</div>
<script src="static/index.js"></script>
<div data-name="Alice">
<script>
// Place the React component into the parent div.
bundle.renderHello(document.currentScript.closest('div'));
</script>
</div>
<div data-name="Bob">
<script>
// Place the React component into the parent div.
bundle.renderHello(document.currentScript.closest('div'));
</script>
</div>
<div data-name="Charlie">
<script>
// Place the React component into the parent div.
bundle.renderHello(document.currentScript.closest('div'));
</script>
</div>
</body>
</html>
And the browser shows the expected content after rendering the client side React components.
React component Header
This is client-side content from React
This is server-side content from templ.
Hello Alice (Client-side React, rendering server-side data)
Hello Bob (Client-side React, rendering server-side data)
Hello Charlie (Client-side React, rendering server-side data)
See https://github.com/a-h/templ/tree/main/examples/integration-react for a complete example.