React Plugin
The React plugin enables writing React components directly in org-press documents with full support for hooks, state management, and TypeScript.
Installation
npm install @org-press/react
Configuration
Add the React plugin to your org-press configuration:
// .org-press/config.ts
import type { OrgPressUserConfig } from "org-press/config-types";
import { reactPlugin } from "@org-press/react";
const config: OrgPressUserConfig = {
plugins: [reactPlugin],
};
export default config;
Basic Usage
Use :use react to create React components:
#+begin_src tsx :use react
import { useState } from 'react';
export function render() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}
#+end_src
The render Function
Every React block must export a render function that returns JSX:
export function render() {
return <div>Your component here</div>;
}
This function is called by org-press to render your component into the page.
Hooks Support
All React hooks work as expected:
useState
#+begin_src tsx :use react
import { useState } from 'react';
export function render() {
const [name, setName] = useState('');
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
<p>Hello, {name || 'stranger'}!</p>
</div>
);
}
#+end_src
useEffect
#+begin_src tsx :use react
import { useState, useEffect } from 'react';
export function render() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const interval = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(interval);
}, []);
return <p>Current time: {time.toLocaleTimeString()}</p>;
}
#+end_src
useReducer
#+begin_src tsx :use react
import { useReducer } from 'react';
type State = { count: number };
type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
case 'reset': return { count: 0 };
}
}
export function render() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
#+end_src
Custom Hooks
#+begin_src tsx :use react
import { useState, useCallback } from 'react';
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue(v => !v), []);
return [value, toggle] as const;
}
export function render() {
const [isOpen, toggleOpen] = useToggle();
return (
<div>
<button onClick={toggleOpen}>
{isOpen ? 'Close' : 'Open'}
</button>
{isOpen && <p>Content is visible!</p>}
</div>
);
}
#+end_src
TypeScript Support
The React plugin has full TypeScript support:
#+begin_src tsx :use react
import { useState } from 'react';
interface Todo {
id: number;
text: string;
completed: boolean;
}
export function render() {
const [todos, setTodos] = useState<Todo[]>([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (!input.trim()) return;
setTodos(prev => [
...prev,
{ id: Date.now(), text: input, completed: false }
]);
setInput('');
};
const toggleTodo = (id: number) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
return (
<div>
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
/>
<button onClick={addTodo}>Add</button>
</div>
<ul>
{todos.map(todo => (
<li
key={todo.id}
onClick={() => toggleTodo(todo.id)}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
</li>
))}
</ul>
</div>
);
}
#+end_src
Styling Components
Inline Styles
#+begin_src tsx :use react
export function render() {
return (
<button style={{
padding: '0.5rem 1rem',
background: '#4ecdc4',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
}}>
Styled Button
</button>
);
}
#+end_src
CSS Classes
Use CSS blocks to define styles, then apply them in your React components:
#+begin_src css
.my-component {
padding: 1rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 8px;
color: white;
}
.my-component button {
background: white;
color: #667eea;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
}
#+end_src
#+begin_src tsx :use react
export function render() {
return (
<div className="my-component">
<h3>Styled Component</h3>
<button>Click me</button>
</div>
);
}
#+end_src
Combining with Wrappers
Use the pipe syntax to add wrappers:
#+begin_src tsx :use react | withSourceCode
import { useState } from 'react';
export function render() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
#+end_src
This displays both the source code and the rendered component.
Custom Layouts
Create custom layouts using React:
// .org-press/themes/layout.tsx
import type { LayoutProps } from "org-press/types";
export function Layout({ children, frontmatter }: LayoutProps) {
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{frontmatter.title}</title>
</head>
<body>
<header>
<nav>
<a href="/">Home</a>
<a href="/docs">Docs</a>
</nav>
</header>
<main>{children}</main>
<footer>
<p>Built with org-press</p>
</footer>
</body>
</html>
);
}
Configure it in your config:
// .org-press/config.ts
import type { OrgPressUserConfig } from "org-press/config-types";
import { reactPlugin } from "@org-press/react";
const config: OrgPressUserConfig = {
theme: ".org-press/themes/layout.tsx",
plugins: [reactPlugin],
};
export default config;
API Reference
reactPlugin
The main plugin export. Add to your config's plugins array.
import { reactPlugin } from "@org-press/react";
// In config
plugins: [reactPlugin]
Block Parameters
| Parameter | Description |
|---|---|
:use react | Enable React mode for this block |
\vert withSourceCode | Show source alongside rendered output |
Supported Languages
| Language | Extension | Description |
|---|---|---|
| TSX | .tsx | TypeScript with JSX (recommended) |
| JSX | .jsx | JavaScript with JSX |
| TypeScript | .ts | TypeScript (requires manual createElement) |
| JavaScript | .js | JavaScript (requires manual createElement) |
Comparison with DOM Mode
| Feature | :use dom | :use react |
|---|---|---|
| Dependencies | None | React, ReactDOM |
| State management | Manual | useState, useReducer |
| Side effects | Manual | useEffect |
| JSX support | No | Yes |
| Virtual DOM | No | Yes |
| Component lifecycle | Manual | Automatic |
When to Use React Mode
- Complex interactive UIs
- Components with state
- When you need React hooks
- Building reusable components
When to Use DOM Mode
- Simple displays
- Minimal interactivity
- Avoid framework dependencies
- Performance-critical scenarios
Troubleshooting
Component not rendering
Ensure you export a render function:
// ❌ Wrong
function MyComponent() { return <div>Hello</div>; }
// ✅ Correct
export function render() { return <div>Hello</div>; }
Hooks not working
Make sure hooks are called inside the render function:
// ❌ Wrong - hooks outside render
const [count, setCount] = useState(0);
export function render() { return <div>{count}</div>; }
// ✅ Correct - hooks inside render
export function render() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
TypeScript errors
Ensure your block uses tsx language:
#+begin_src tsx :use react
// TypeScript + JSX works here
#+end_src
See Also
- Rendering Modes - Overview of all modes
- Plugins - All available plugins
- Getting Started - Basic setup guide