Skip to content

React-Style Components#

Jac's client-side code uses JSX syntax (the same HTML-in-code approach popularized by React) to build UI components. Components are functions declared inside cl { } blocks that return JsxElement values. They support props for data passing, composition for building complex UIs from simple parts, and all the familiar React patterns -- conditional rendering, list mapping, and event handling.

The key difference from a standard React setup: there's no separate JavaScript project, no webpack configuration, and no build toolchain to manage. You write components in Jac syntax, the compiler generates optimized JavaScript, and the dev server bundles and serves it automatically.

Prerequisites


Basic Component#

cl {
    def:pub Greeting(props: dict) -> JsxElement {
        return <h1>Hello, {props.name}!</h1>;
    }

    def:pub app() -> JsxElement {
        return <div>
            <Greeting name="Alice" />
            <Greeting name="Bob" />
        </div>;
    }
}

Key points:

  • Components are functions returning JSX
  • def:pub exports the component
  • props contains passed attributes
  • Self-closing tags: <Component />

JSX Syntax#

HTML Elements#

cl {
    def:pub MyComponent() -> JsxElement {
        return <div className="container">
            <h1>Title</h1>
            <p>Paragraph text</p>
            <a href="/about">Link</a>
            <img src="/logo.png" alt="Logo" />
        </div>;
    }
}

Note: Use className not class (like React).

JavaScript Expressions#

cl {
    def:pub MyComponent() -> JsxElement {
        name = "World";
        items = [1, 2, 3];

        return <div>
            <p>Hello, {name}!</p>
            <p>Sum: {1 + 2 + 3}</p>
            <p>Items: {len(items)}</p>
        </div>;
    }
}

Use { } to embed any Jac expression.


Conditional Rendering#

Ternary Operator#

cl {
    def:pub Status(props: dict) -> JsxElement {
        return <span>
            {("Active" if props.active else "Inactive")}
        </span>;
    }
}

Logical AND#

cl {
    def:pub Notification(props: dict) -> JsxElement {
        return <div>
            {props.count > 0 and <span>You have {props.count} messages</span>}
        </div>;
    }
}

If Statement#

cl {
    def:pub UserGreeting(props: dict) -> JsxElement {
        if props.isLoggedIn {
            return <h1>Welcome back!</h1>;
        }
        return <h1>Please sign in</h1>;
    }
}

Lists and Iteration#

cl {
    def:pub TodoList(props: dict) -> JsxElement {
        return <ul>
            {[<li key={item.id}>{item.text}</li> for item in props.items]}
        </ul>;
    }

    def:pub app() -> JsxElement {
        todos = [
            {"id": 1, "text": "Learn Jac"},
            {"id": 2, "text": "Build app"},
            {"id": 3, "text": "Deploy"}
        ];

        return <TodoList items={todos} />;
    }
}

Important: Always provide a key prop for list items.


Event Handling#

Click Events#

cl {
    def:pub Button() -> JsxElement {
        def handle_click() -> None {
            print("Button clicked!");
        }

        return <button onClick={lambda -> None { handle_click(); }}>
            Click me
        </button>;
    }
}

Input Events#

cl {
    def:pub SearchBox() -> JsxElement {
        has query: str = "";

        return <input
            type="text"
            value={query}
            onChange={lambda e: any -> None { query = e.target.value; }}
            placeholder="Search..."
        />;
    }
}

Form Submit#

cl {
    def:pub LoginForm() -> JsxElement {
        has username: str = "";
        has password: str = "";

        def handle_submit(e: any) -> None {
            e.preventDefault();
            print(f"Login: {username}");
        }

        return <form onSubmit={lambda e: any -> None { handle_submit(e); }}>
            <input
                value={username}
                onChange={lambda e: any -> None { username = e.target.value; }}
            />
            <input
                type="password"
                value={password}
                onChange={lambda e: any -> None { password = e.target.value; }}
            />
            <button type="submit">Login</button>
        </form>;
    }
}

Component Composition#

Children#

cl {
    def:pub Card(props: dict) -> JsxElement {
        return <div className="card">
            <div className="card-header">{props.title}</div>
            <div className="card-body">{props.children}</div>
        </div>;
    }

    def:pub app() -> JsxElement {
        return <Card title="Welcome">
            <p>This is the card content.</p>
            <button>Action</button>
        </Card>;
    }
}

Nested Components#

cl {
    def:pub Header() -> JsxElement {
        return <header>
            <h1>My App</h1>
            <Nav />
        </header>;
    }

    def:pub Nav() -> JsxElement {
        return <nav>
            <a href="/">Home</a>
            <a href="/about">About</a>
        </nav>;
    }

    def:pub Footer() -> JsxElement {
        return <footer>© 2024</footer>;
    }

    def:pub app() -> JsxElement {
        return <div>
            <Header />
            <main>Content here</main>
            <Footer />
        </div>;
    }
}

Separate Component Files#

Header.cl.jac#

# No cl { } needed for .cl.jac files

def:pub Header(props: dict) -> JsxElement {
    return <header>
        <h1>{props.title}</h1>
    </header>;
}

main.jac#

cl {
    import from "./Header.cl.jac" { Header }

    def:pub app() -> JsxElement {
        return <div>
            <Header title="My App" />
            <main>Content</main>
        </div>;
    }
}

TypeScript Components#

You can use TypeScript components:

Button.tsx#

interface ButtonProps {
  label: string;
  onClick: () => void;
}

export function Button({ label, onClick }: ButtonProps) {
  return <button onClick={onClick}>{label}</button>;
}

main.jac#

cl {
    import from "./Button.tsx" { Button }

    def:pub app() -> JsxElement {
        return <Button
            label="Click me"
            onClick={lambda -> None { print("Clicked!"); }}
        />;
    }
}

Styling Components#

Inline Styles#

cl {
    def:pub StyledBox() -> JsxElement {
        return <div style={{
            "backgroundColor": "#f0f0f0",
            "padding": "20px",
            "borderRadius": "8px",
            "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
        }}>
            Styled content
        </div>;
    }
}

CSS Classes#

cl {
    import "./styles.css";

    def:pub app() -> JsxElement {
        return <div className="container">
            <h1 className="title">Hello</h1>
        </div>;
    }
}
/* .styles.css */
.container {
    max-width: 800px;
    margin: 0 auto;
}
.title {
    color: #333;
}

Key Takeaways#

Concept Syntax
Define component def:pub Name(props: dict) -> JsxElement { }
JSX element <div className="x">content</div>
Expression {expression}
Event handler onClick={lambda -> None { ... }}
List rendering {[<li>{x}</li> for x in items]}
Conditional {("A" if condition else "B")}
Children {props.children}
Import component import from "./File.cl.jac" { Component }

Next Steps#