Your First Component
Let's build a complete todo list component to understand all the core concepts.
Creating the Component
bash
php artisan make:diffyne TodoListComponent Structure
1. Define Properties and Methods
Edit app/Diffyne/TodoList.php:
php
<?php
namespace App\Diffyne;
use Diffyne\Attributes\Invokable;
use Diffyne\Component;
class TodoList extends Component
{
// Public properties are reactive
public array $todos = [];
public string $newTodo = '';
// Called when component mounts
public function mount()
{
$this->todos = [
'Learn Diffyne',
'Build something awesome',
];
}
// Add new todo
#[Invokable]
public function addTodo()
{
if (trim($this->newTodo) !== '') {
$this->todos[] = $this->newTodo;
$this->newTodo = ''; // Clear input
}
}
// Remove todo by index
#[Invokable]
public function removeTodo($index)
{
unset($this->todos[$index]);
$this->todos = array_values($this->todos); // Re-index
}
// Mark all complete (example)
#[Invokable]
public function clearAll()
{
$this->todos = [];
}
}2. Create the View
Edit resources/views/diffyne/todo-list.blade.php:
blade
<div class="max-w-md mx-auto bg-white rounded-lg shadow-lg p-6">
<h2 class="text-2xl font-bold mb-4">My Todo List</h2>
{{-- Add Todo Form --}}
<form diff:submit="addTodo" class="mb-4">
<div class="flex gap-2">
<input
type="text"
diff:model="newTodo"
placeholder="Add a new todo..."
class="flex-1 px-4 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<button
type="submit"
class="px-6 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
Add
</button>
</div>
</form>
{{-- Todo List --}}
@if(count($todos) > 0)
<ul class="space-y-2 mb-4">
@foreach($todos as $index => $todo)
<li class="flex items-center justify-between p-3 bg-gray-50 rounded">
<span>{{ $todo }}</span>
<button
diff:click="removeTodo({{ $index }})"
class="text-red-500 hover:text-red-700">
✕
</button>
</li>
@endforeach
</ul>
<button
diff:click="clearAll"
class="w-full py-2 bg-gray-200 rounded hover:bg-gray-300">
Clear All
</button>
@else
<p class="text-gray-500 text-center py-8">No todos yet. Add one above!</p>
@endif
</div>Understanding the Code
Reactive Properties
php
public array $todos = [];
public string $newTodo = '';Any public property is automatically reactive. When it changes, the UI updates.
Lifecycle Hook: mount()
php
public function mount()
{
$this->todos = ['Learn Diffyne', 'Build something awesome'];
}mount() runs once when the component first loads. Use it for initialization.
Event Directives
diff:click
blade
<button diff:click="removeTodo({{ $index }})">✕</button>Calls server method when clicked.
diff:submit
blade
<form diff:submit="addTodo">Handles form submission. Default form submission is automatically prevented.
Data Binding
blade
<input diff:model="newTodo">diff:model- Two-way binding- Without modifiers - Updates local state, syncs on change
Modifiers:
.live- Sync with server immediately on input.lazy- Sync on change event.live.debounce.300- Sync with server after 300ms of inactivity
Using the Component
Add to any page. Make sure your layout includes the required directives:
blade
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
@diffyneStyles
</head>
<body>
@extends('layouts.app')
@section('content')
@diffyne('TodoList')
@endsection
@diffyneScripts
</body>
</html>Important:
@diffyneStylesmust be in the<head>section@diffyneScriptsmust be before the closing</body>tag
How Updates Work
When you click "Add":
Browser sends AJAX request:
json{ "method": "addTodo", "state": { "todos": ["Learn Diffyne", "Build something awesome"], "newTodo": "New task" } }Server executes:
- Hydrates component with state
- Calls
addTodo()method $todosarray grows$newTodocleared- Re-renders view to Virtual DOM
Diff Engine compares:
- Old Virtual DOM vs New Virtual DOM
- Generates minimal patches
Response sent:
json{ "s": true, "c": { "i": "comp-1", "p": [ {"type": "add", "parent": "ul", "html": "<li>...</li>"}, {"type": "text", "node": "#input-1", "value": ""} ], "st": { "todos": ["Learn Diffyne", "Build...", "New task"], "newTodo": "" } } }Browser applies patches:
- Adds new
<li>element - Clears input field
- Total update time: ~50-100ms
- Adds new
Common Patterns
Passing Parameters
blade
<button diff:click="removeTodo({{ $index }})">Remove</button>php
use Diffyne\Attributes\Invokable;
#[Invokable]
public function removeTodo($index)
{
unset($this->todos[$index]);
$this->todos = array_values($this->todos);
}Loading States
blade
<button
diff:click="addTodo"
diff:loading.class.opacity-50>
Add
</button>Conditional Rendering
blade
@if(count($todos) > 0)
<ul>...</ul>
@else
<p>No todos yet!</p>
@endifNext Steps
Now that you've built your first component, learn more:
- Data Binding - Connect forms to component properties
- Forms & Validation - Handle form submissions and validation
- Loading States - Show feedback during updates
- Contact Form Example - See a complete form example
