Skip to content

React-Style Components#

Build reusable UI components with JSX syntax.

Prerequisites


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:pub exports the component
  • props contains 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>;
    }
}
/* .styles.css */
.container {
    max-width: 800px;
    margin: 0 auto;
}
.title {
    color: #333;
}

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#