No results

    Search failed

    npm GitHub

    React components in a static site

    Sometimes you want a React component inside an otherwise-static page โ€” an interactive counter, a clock, a filterable table โ€” without turning the whole site into an SPA. That's what reactor is for: render the component to HTML at build time, inject it into a template, and optionally hydrate it on the client.

    The component

    A plain component with a default export:

    // src/js/Counter.jsx
    import { useState } from 'react'
    
    export default function Counter() {
      const [n, setN] = useState(0)
      return (
        <button onClick={() => setN(n + 1)}>
          Clicked {n} times
        </button>
      )
    }

    The hydration entry

    A tiny client entry that hydrates the pre-rendered markup:

    // src/js/counter-hydrate.jsx
    import { hydrateRoot } from 'react-dom/client'
    import Counter from './Counter.jsx'
    
    hydrateRoot(document.getElementById('counter'), <Counter />)

    The config

    {
      "reactor": [
        {
          "component": "src/js/Counter.jsx",
          "inject": "counter_html",
          "in": "src/js/counter-hydrate.jsx",
          "out": "dist/js/counter.js",
          "options": { "minify": true, "target": "es2019" }
        }
      ]
    }

    Poops renders Counter with renderToString and stores the HTML under the inject name. The client entry is bundled to out.

    The template

    Drop the rendered HTML in, then load the hydration bundle:

    <h1>Dashboard</h1>
    <div id="counter">{{ counter_html | safe }}</div>
    <script src="{{ relativePathPrefix }}js/counter.min.js"></script>

    The page ships as real HTML โ€” the button is visible before any JS runs โ€” and becomes interactive once React hydrates it.

    Note

    React comes from your project's node_modules (npm i react react-dom), and reactor is its own pipeline, independent from scripts โ€” details in React.

    Server-only components

    Need the HTML but no interactivity (an icon, a formatted block)? Omit in/out and skip the hydration bundle entirely:

    {
      "reactor": [
        { "component": "src/js/Icon.jsx", "inject": "icon_html" }
      ]
    }

    Tip

    Multiple components? Add multiple reactor entries, each with its own inject name, and use each in the templates that need it.

    Next: A complete React static site.