⚠️Early Alpha — Org-press is experimental. Perfect for hackers and tinkerers, not ready for production. Documentation may be incomplete or inaccurate.

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

ParameterDescription
:use reactEnable React mode for this block
\vert withSourceCodeShow source alongside rendered output

Supported Languages

LanguageExtensionDescription
TSX.tsxTypeScript with JSX (recommended)
JSX.jsxJavaScript with JSX
TypeScript.tsTypeScript (requires manual createElement)
JavaScript.jsJavaScript (requires manual createElement)

Comparison with DOM Mode

Feature:use dom:use react
DependenciesNoneReact, ReactDOM
State managementManualuseState, useReducer
Side effectsManualuseEffect
JSX supportNoYes
Virtual DOMNoYes
Component lifecycleManualAutomatic

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