React-Style Components#
Build reusable UI components with JSX syntax.
Prerequisites
- Completed: Project Setup
- Time: ~30 minutes
Basic Component#
cl {
def:pub Greeting(props: dict) -> any {
return <h1>Hello, {props.name}!</h1>;
}
def:pub app() -> any {
return <div>
<Greeting name="Alice" />
<Greeting name="Bob" />
</div>;
}
}
Key points:
- Components are functions returning JSX
def:pubexports the componentpropscontains passed attributes- Self-closing tags:
<Component />
JSX Syntax#
HTML Elements#
cl {
def:pub MyComponent() -> any {
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() -> any {
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) -> any {
return <span>
{("Active" if props.active else "Inactive")}
</span>;
}
}
Logical AND#
cl {
def:pub Notification(props: dict) -> any {
return <div>
{props.count > 0 and <span>You have {props.count} messages</span>}
</div>;
}
}
If Statement#
cl {
def:pub UserGreeting(props: dict) -> any {
if props.isLoggedIn {
return <h1>Welcome back!</h1>;
}
return <h1>Please sign in</h1>;
}
}
Lists and Iteration#
cl {
def:pub TodoList(props: dict) -> any {
return <ul>
{props.items.map(lambda item: any -> any {
return <li key={item.id}>{item.text}</li>;
})}
</ul>;
}
def:pub app() -> any {
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() -> any {
def handle_click() -> None {
print("Button clicked!");
}
return <button onClick={lambda -> None { handle_click(); }}>
Click me
</button>;
}
}
Input Events#
cl {
def:pub SearchBox() -> any {
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() -> any {
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) -> any {
return <div className="card">
<div className="card-header">{props.title}</div>
<div className="card-body">{props.children}</div>
</div>;
}
def:pub app() -> any {
return <Card title="Welcome">
<p>This is the card content.</p>
<button>Action</button>
</Card>;
}
}
Nested Components#
cl {
def:pub Header() -> any {
return <header>
<h1>My App</h1>
<Nav />
</header>;
}
def:pub Nav() -> any {
return <nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>;
}
def:pub Footer() -> any {
return <footer>© 2024</footer>;
}
def:pub app() -> any {
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) -> any {
return <header>
<h1>{props.title}</h1>
</header>;
}
main.jac#
cl {
import from "./Header.cl.jac" { Header }
def:pub app() -> any {
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() -> any {
return <Button
label="Click me"
onClick={lambda -> None { print("Clicked!"); }}
/>;
}
}
Styling Components#
Inline Styles#
cl {
def:pub StyledBox() -> any {
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() -> any {
return <div className="container">
<h1 className="title">Hello</h1>
</div>;
}
}
Key Takeaways#
| Concept | Syntax |
|---|---|
| Define component | def:pub Name(props: dict) -> any { } |
| JSX element | <div className="x">content</div> |
| Expression | {expression} |
| Event handler | onClick={lambda -> None { ... }} |
| List rendering | {items.map(lambda x -> any { <li>{x}</li> })} |
| Conditional | {condition ? <A /> : <B />} |
| Children | {props.children} |
| Import component | import from "./File.cl.jac" { Component } |
Next Steps#
- State Management - Reactive state with
has - Backend Integration - Connect to walkers