Skip to content

Step 2: First Component#

** Quick Tip: Each step has two parts. Part 1 shows you what to build. Part 2** explains why it works. Want to just build? Skip all Part 2 sections!

In this step, you'll create your first reusable component - a TodoItem that displays a single todo.


Part 1: Building the App#

Step 2.1: Create a TodoItem Component#

Update your app.jac:

cl {
    # A component to display a single todo
    def TodoItem(props: any) -> any {
        return <div>
            <input type="checkbox" />
            <span>Learn Jac basics</span>
            <button>Delete</button>
        </div>;
    }

    def app() -> any {
        return <div>
            <h1>My Todos</h1>
            <TodoItem />
        </div>;
    }
}

Try it! Save and refresh your browser. You should see a todo item with a checkbox, text, and delete button.

Step 2.2: Make It Reusable with Props#

Now let's make the TodoItem display different text:

cl {
    # Component that accepts data via props
    def TodoItem(props: any) -> any {
        return <div>
            <input type="checkbox" checked={props.done} />
            <span>{props.text}</span>
            <button>Delete</button>
        </div>;
    }

    def app() -> any {
        return <div>
            <h1>My Todos</h1>
            <TodoItem text="Learn Jac basics" done={false} />
            <TodoItem text="Build a todo app" done={false} />
            <TodoItem text="Deploy to production" done={true} />
        </div>;
    }
}

Try it! You should now see three different todos. Notice how the third one has the checkbox checked!

Step 2.3: Create Multiple Components#

Let's add more components to organize our app:

cl {
    # Component 1: TodoInput (input field + Add button)
    def TodoInput(props: any) -> any {
        return <div>
            <input type="text" placeholder="What needs to be done?" />
            <button>Add</button>
        </div>;
    }

    # Component 2: TodoFilters (filter buttons)
    def TodoFilters(props: any) -> any {
        return <div>
            <button>All</button>
            <button>Active</button>
            <button>Completed</button>
        </div>;
    }

    # Component 3: TodoItem (single todo)
    def TodoItem(props: any) -> any {
        return <div>
            <input type="checkbox" checked={props.done} />
            <span>{props.text}</span>
            <button>Delete</button>
        </div>;
    }

    # Component 4: TodoList (list of todos)
    def TodoList(props: any) -> any {
        return <div>
            <TodoItem text="Learn Jac basics" done={true} />
            <TodoItem text="Build a todo app" done={false} />
            <TodoItem text="Deploy to production" done={false} />
        </div>;
    }

    # Main app - combines all components
    def app() -> any {
        return <div>
            <h1>My Todos</h1>
            <TodoInput />
            <TodoFilters />
            <TodoList />
        </div>;
    }
}

Try it! Your app now has a clear structure with separate components.


⏭ Want to skip the theory? Jump to Step 3: Styling


Part 2: Understanding the Concepts#

What is a Component?#

A component is a function that returns UI (JSX).

Think of components like Python functions:

# Python - returns text
def greet_user(name):
    return f"Hello, {name}!"

print(greet_user("Alice"))  # Hello, Alice!
print(greet_user("Bob"))    # Hello, Bob!
# Jac - returns UI
def TodoItem(props: any) -> any {
    return <div>{props.text}</div>;
}

# Usage
<TodoItem text="Learn Jac" />
<TodoItem text="Build app" />

Why Use Components?#

1. Reusability - Write once, use many times

<TodoItem text="Task 1" done={false} />
<TodoItem text="Task 2" done={true} />
<TodoItem text="Task 3" done={false} />

2. Organization - Break complex UI into manageable pieces

app
├── TodoInput
├── TodoFilters
└── TodoList
    ├── TodoItem
    ├── TodoItem
    └── TodoItem

3. Maintainability - Easy to find and fix bugs

If there's a bug in how todos display, you know to check TodoItem.

Component Naming Rules#

1. Use PascalCase (first letter capitalized)

#  Correct
def TodoItem() -> any { ... }
def UserProfile() -> any { ... }
def NavigationBar() -> any { ... }

#  Wrong
def todoItem() -> any { ... }      # camelCase
def user_profile() -> any { ... }  # snake_case
def navigation-bar() -> any { ... } # kebab-case

2. Name describes what it does

#  Good names
def TodoItem() -> any { ... }
def LoginForm() -> any { ... }
def ProductCard() -> any { ... }

#  Bad names
def Component1() -> any { ... }
def Thing() -> any { ... }
def X() -> any { ... }

Understanding Props#

Props = "Properties" = Data passed to a component

# Passing props (like function arguments)
<TodoItem text="Learn Jac" done={false} />

# Receiving props (in the component)
def TodoItem(props: any) -> any {
    let text = props.text;      # "Learn Jac"
    let done = props.done;      # false
    return <div>{text}</div>;
}

Important: In React (which Jac uses), components receive props as a single object, not individual parameters.

#  Correct way
def TodoItem(props: any) -> any {
    let text = props.text;
    let done = props.done;
    # ...
}

#  Wrong way (won't work)
def TodoItem(text: str, done: bool) -> any {
    # This doesn't work in React!
}

Accessing Props#

Three ways to access props:

Method 1: Direct access

def TodoItem(props: any) -> any {
    return <span>{props.text}</span>;
}

Method 2: Extract to variables

def TodoItem(props: any) -> any {
    let text = props.text;
    let done = props.done;
    return <span>{text}</span>;
}

Method 3: Dictionary access (explicit)

def TodoItem(props: any) -> any {
    let text = props["text"];
    let done = props["done"];
    return <span>{text}</span>;
}

All three ways work! Use whichever feels clearest to you.

Composing Components#

You can nest components inside other components:

# TodoList uses TodoItem
def TodoList() -> any {
    return <div>
        <TodoItem text="Task 1" done={false} />
        <TodoItem text="Task 2" done={true} />
    </div>;
}

# App uses TodoList
def app() -> any {
    return <div>
        <h1>My Todos</h1>
        <TodoList />
    </div>;
}

This creates a hierarchy:

app
└── TodoList
    ├── TodoItem
    └── TodoItem

Using JSX in Props#

You can pass any value as props:

# String
<TodoItem text="Hello" />

# Number
<TodoItem count={42} />

# Boolean
<TodoItem done={true} />

# Variable
let myText = "Learn Jac";
<TodoItem text={myText} />

# Expression
<TodoItem priority={5 + 3} />

What You've Learned#

  • Components are functions that return UI
  • How to create a component
  • PascalCase naming convention
  • Passing data to components with props
  • Receiving props as a single object
  • Composing components (nesting)
  • Organizing app into multiple components

Common Issues#

Issue: Component not showing up#

Check: - Is the name in PascalCase? TodoItem not todoItem - Did you use <TodoItem /> (with angle brackets)? - Does it have a return statement?

Issue: "object with keys {text, done}"#

Cause: Using individual parameters instead of props object

#  Wrong
def TodoItem(text: str, done: bool) -> any {
    # ...
}

#  Correct
def TodoItem(props: any) -> any {
    let text = props.text;
    # ...
}

Issue: Props are undefined#

Check: - Did you pass the props when using the component? - Are the prop names spelled the same in both places?

# Passing props
<TodoItem text="Learn" done={false} />

# Receiving props (names must match!)
def TodoItem(props: any) -> any {
    props.text  # "Learn"
    props.done  # false
}

Quick Exercise#

Try adding a new component:

def TodoStats(props: any) -> any {
    return <div>
        <p>Total: {props.total}</p>
        <p>Completed: {props.completed}</p>
    </div>;
}

# Use it in app
def app() -> any {
    return <div>
        <h1>My Todos</h1>
        <TodoStats total={3} completed={1} />
        # ... rest of your components
    </div>;
}

Next Step#

Great! You can now create and organize components. But they look plain. Let's make them beautiful with styling!

Continue to Step 3: Styling