# Tutorial 2: Your First Interactive Component
**Video Length**: ~15 minutes | **Difficulty**: Beginner | **Series**: Getting Started
## Overview
Learn how to build your first interactive component using Leptos reactive signals and shadcn-ui building blocks. We'll create a counter component that demonstrates state management, event handling, and component composition.
## What You'll Learn
- Understanding reactive signals in Leptos
- Building components with state
- Handling user events
- Composing multiple shadcn-ui components
- Component props and children
## Prerequisites
- Completed [Tutorial 1: Installation](01-installation.md)
- Basic understanding of Rust ownership (helpful but not required)
## Video Outline
**[0:00]** Introduction to reactive programming
**[1:30]** Understanding signals in Leptos
**[3:00]** Creating a simple counter component
**[5:30]** Adding event handlers
**[7:00]** Styling with shadcn classes
**[9:00]** Composing multiple components
**[11:00]** Using Button variants
**[13:00]** Adding icons and labels
**[14:00]** Summary and next steps
## Step-by-Step Guide
### Understanding Reactive Signals
Leptos uses **signals** for reactive state management. A signal is a piece of state that automatically updates the UI when changed.
```rust
use leptos::*;
// Create a read-write signal
let (count, set_count) = create_signal(0);
// Read the signal
let current_value = count.get();
// Update the signal
set_count.update(|n| *n += 1);
```
### Creating Your Counter Component
Let's build an interactive counter:
```rust
use leptos::*;
use leptos_shadcn_button::Button;
use leptos_shadcn_card::Card;
#[component]
pub fn Counter() -> impl IntoView {
// Create reactive state
let (count, set_count) = create_signal(0);
view! {
"Counter"
// Display current count (reactive)
{count}
// Control buttons
// Show message based on count
{move || {
if count.get() == 0 {
"Start counting!".to_string()
} else if count.get() < 10 {
"Keep going!".to_string()
} else if count.get() < 20 {
"Almost there!".to_string()
} else {
"You're a counting champion!".to_string()
}
}}
}
}
```
### Adding Component Props
Make your component reusable with props:
```rust
#[component]
pub fn Counter(
/// Initial count value
#[prop(default = 0)]
initial: i32,
/// Step size for increment/decrement
#[prop(default = 1)]
step: i32,
/// Show reset button
#[prop(default = true)]
show_reset: bool,
) -> impl IntoView {
let (count, set_count) = create_signal(initial);
view! {
// ... component content
// ... rest of component
}
}
// Usage
view! {
}
```
### Composing Multiple Components
Build a stats dashboard with multiple counters:
```rust
#[component]
pub fn StatsDashboard() -> impl IntoView {
let (total_clicks, set_total_clicks) = create_signal(0);
let (streak, set_streak) = create_signal(0);
view! {
"Stats Dashboard"
10 {
set_streak.update(|n| *n += 1);
}
}
/>
{total_clicks}
"Total Actions"
}
}
```
### Using Derived Signals
Create computed values based on other signals:
```rust
#[component]
pub fn SmartCounter() -> impl IntoView {
let (count, set_count) = create_signal(0);
// Derived signal - automatically updates when count changes
let is_even = move || count.get() % 2 == 0;
let double_count = move || count.get() * 2;
let count_status = move || {
match count.get() {
n if n < 0 => "Negative".to_string(),
0 => "Zero".to_string(),
n if n % 2 == 0 => format!("Even: {}", n),
_ => format!("Odd: {}", n),
}
};
view! {
{count}
{count_status}
"Double: "
{double_count}
"Is Even: "
{is_even}
}
}
```
## Complete Example: Interactive Counter App
```rust
use leptos::*;
use leptos_shadcn_button::Button;
use leptos_shadcn_card::Card;
#[component]
pub fn App() -> impl IntoView {
view! {
"Interactive Counters"
"Your first reactive components!"
// Simple counter
// Stepped counter
// Smart counter with derived state
}
}
```
## Key Concepts Summary
| Concept | Description | Example |
|---------|-------------|---------|
| **Signal** | Reactive state container | `create_signal(0)` |
| **Getter** | Function to read signal value | `count.get()` |
| **Setter** | Function to update signal | `set_count.set(5)` |
| **Updater** | Modify current value | `set_count.update(\|n\| *n += 1)` |
| **Derived Signal** | Computed from other signals | `move \|| count.get() * 2` |
| **Event Handler** | Closure responding to events | `on_click=move \|\| {...}` |
## Common Patterns
### Pattern 1: Toggle State
```rust
let (is_open, set_is_open) = create_signal(false);
view! {
}
```
### Pattern 2: State Reset
```rust
let initial_value = 10;
let (value, set_value) = create_signal(initial_value);
view! {
}
```
### Pattern 3: Conditional Rendering
```rust
let (show_details, set_show_details) = create_signal(false);
view! {
{move || show_details.get().then(|| {
view! {
"Hidden content!"
}
})}
}
```
## Exercise
1. Create a counter that counts down from 60 to 0
2. Add a "step" selector with buttons: 1, 5, 10
3. Display the counter in different formats: decimal, hexadecimal, binary
4. Add a visual indicator (color change) when count reaches certain thresholds
## What's Next?
- [Tutorial 3: Basic Form Patterns](03-basic-forms.md) - Build input forms
- [Component API Reference](../../components/README.md) - Explore all components
- [State Management Guide](../../architecture/state-management.md) - Deep dive on reactivity
---
**Previous**: [Installation](01-installation.md) | **Next**: [Basic Form Patterns](03-basic-forms.md)