react-express-view
v1.0.4
Published
A template engine for Express that allows rendering React components as client-side views.
Maintainers
Readme
React Express View
React Express View is a template engine for Express that allows rendering React components as client-side views. It supports both JavaScript (.jsx) and TypeScript (.tsx).
For example projects, check the example directory:
Installation
npm install react-express-viewUsage
1. Import and Configuration
To ensure compatibility and prevent "Invalid Hook Call" errors (due to multiple React instances), you should inject your application's React and ReactDOMServer instances into the engine.
JavaScript
const express = require("express");
const path = require("path");
const reactExpressView = require("react-express-view");
const app = express();
// Configure the engine and views folder
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jsx");
app.engine("jsx", reactExpressView.createEngine());
// Serve static files for client-side JavaScript
app.use(express.static(path.join(__dirname, "public")));
app.get("/", (req, res) => {
res.render("Home", {
title: "Home Page",
message: "Welcome to the Home Page!",
});
});
app.listen(3000, () => {
console.log("Server is running at http://localhost:3000");
});TypeScript
import express from "express";
import path from "path";
import { createEngine } from "react-express-view";
const app = express();
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "tsx");
app.engine(
"tsx",
createEngine({
transformViews: true,
React: require("react"),
ReactDOMServer: require("react-dom/server"),
})
);
app.use(express.static(path.join(__dirname, "public")));
app.get("/", (req, res) => {
res.render("Home", {
title: "TypeScript App",
message: "Welcome to the TypeScript Example!",
});
});
app.listen(3000, () => {
console.log("Server is running at http://localhost:3000");
});2. Creating a React Component
Inside the views folder, create a file Home.jsx (for JavaScript) or Home.tsx (for TypeScript).
JavaScript (Home.jsx)
const React = require("react");
const { useState } = require("react");
function Home(props) {
const [count, setCount] = useState(0);
return (
<div>
<h1>{props.title}</h1>
<p>{props.message}</p>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
module.exports = Home; // or export default Home;TypeScript (Home.tsx)
import React, { useState } from "react";
interface HomeProps {
title: string;
message: string;
}
function Home(props: HomeProps) {
const [count, setCount] = useState(0);
return (
<div>
<h1>{props.title}</h1>
<p>{props.message}</p>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default Home;3. Client-side Hydration
To make the React components interactive on the client side, you need a client entry point.
Inside the client folder, create index.jsx (for JavaScript) or index.tsx (for TypeScript).
JavaScript (index.jsx)
import React from "react";
import { hydrateRoot } from "react-dom/client";
// Dynamic import to support CommonJS and ES Modules
// Automatically register all components in the views directory
const requireComponent = require.context(
"../views",
true,
/\.(js|jsx|ts|tsx)$/
);
const components = {};
requireComponent.keys().forEach((fileName) => {
// Extract component name from filename (e.g., ./Home.jsx -> Home)
const componentName = fileName
.split("/")
.pop()
.replace(/\.\w+$/, "");
// Handle default exports and named exports
components[componentName] =
requireComponent(fileName).default || requireComponent(fileName);
});
// Retrieve the component name sent from the server
const componentName = window.__INITIAL_COMPONENT__;
const Component = components[componentName];
if (!Component) {
throw new Error(`Component "${componentName}" not found.`);
}
// Target root element
const rootElement = document.getElementById("root");
// Perform rehydration
if (rootElement) {
hydrateRoot(rootElement, <Component {...window.__INITIAL_PROPS__} />);
}TypeScript (index.tsx)
import React from "react";
import { hydrateRoot } from "react-dom/client";
declare global {
interface Window {
__INITIAL_COMPONENT__: string;
__INITIAL_PROPS__: any;
}
}
// Dynamic import to support CommonJS and ES Modules
// Automatically register all components in the views directory
const requireComponent = require.context(
"../views",
true,
/\.(js|jsx|ts|tsx)$/
);
const components: { [key: string]: any } = {};
requireComponent.keys().forEach((fileName: string) => {
// Extract component name from filename (e.g., ./Home.tsx -> Home)
const componentName = fileName
.split("/")
.pop()
?.replace(/\.\w+$/, "");
if (componentName) {
components[componentName] = requireComponent(fileName);
}
});
// Retrieve the component name sent from the server
const componentName = window.__INITIAL_COMPONENT__;
const Component =
components[componentName]?.default || components[componentName];
if (!Component) {
throw new Error(`Component "${componentName}" not found.`);
}
// Target root element
const rootElement = document.getElementById("root");
if (rootElement) {
// Perform rehydration
hydrateRoot(rootElement, <Component {...window.__INITIAL_PROPS__} />);
}4. Bundling
You will need a bundler like Webpack to compile your client-side code.
.babelrc or babel.config.json
Ensure you have the necessary presets:
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
]
}Note:
@babel/preset-typescriptis only required if you are using TypeScript.
Configuration
createEngine(options) accepts the following options:
doctype: Defines the doctype for the output HTML (default:<!DOCTYPE html>).transformViews: Enables runtime transpilation of views using@babel/register(default:true).React: Pass your application's React instance (fixes Hook errors).ReactDOMServer: Pass your application's ReactDOMServer instance.babel: Custom Babel configuration object passed to@babel/register.
License
This project is licensed under the MIT License.
