This page is part of a series that describes building a complete todo list application with LiveComponent. Please consider reading it from the beginning.
Now that we've seen how LiveComponent works with Hotwire, let's expand the capabilities of our app so we can add new items to the todo list.
In order for our app to support multiple users, we need the ability to manage multiple todo lists. Let's create a TodoListComponent that renders a title and the list of items from a TodoList model. We'll also add a form with a single text field and a submit button.
1 | <h1><%= @todo_list.name %></h1> |
2 | <ul>
|
3 | <% todo_items.each do |todo_item| %> |
4 | <li><%= todo_item %></li> |
5 | <% end %> |
6 | </ul>
|
7 | <%= form_with( |
8 | model: [@todo_list, @todo_list.todo_items.build], |
9 | rerender: :self
|
10 | ) do |f| %> |
11 | <%= f.text_field :text %> |
12 | <%= f.submit "Add todo item" %> |
13 | <% end %> |
1 | <h1><%= @todo_list.name %></h1> |
2 | <ul>
|
3 | <% todo_items.each do |todo_item| %> |
4 | <li><%= todo_item %></li> |
5 | <% end %> |
6 | </ul>
|
7 | <%= form_with( |
8 | model: [@todo_list, @todo_list.todo_items.build], |
9 | rerender: :self
|
10 | ) do |f| %> |
11 | <%= f.text_field :text %> |
12 | <%= f.submit "Add todo item" %> |
13 | <% end %> |
1 | class TodoListComponent < ApplicationComponent |
2 | include LiveComponent::Base |
3 | |
4 | renders_many :todo_items, TodoItemComponent |
5 | |
6 | def initialize(todo_list:) |
7 | @todo_list = todo_list |
8 | end
|
9 | end
|
1 | class TodoListComponent < ApplicationComponent |
2 | include LiveComponent::Base |
3 | |
4 | renders_many :todo_items, TodoItemComponent |
5 | |
6 | def initialize(todo_list:) |
7 | @todo_list = todo_list |
8 | end
|
9 | end
|
1 | class TodoListsController < ApplicationController |
2 | def show |
3 | @todo_list = TodoList |
4 | .includes(:todo_items) |
5 | .find(params[:id]) |
6 | end
|
7 | end
|
1 | class TodoListsController < ApplicationController |
2 | def show |
3 | @todo_list = TodoList |
4 | .includes(:todo_items) |
5 | .find(params[:id]) |
6 | end
|
7 | end
|
1 | class TodoList < ApplicationRecord |
2 | has_many :todo_items |
3 | end
|
1 | class TodoList < ApplicationRecord |
2 | has_many :todo_items |
3 | end
|
1 | <%= render(TodoListComponent.new(todo_list: @todo_list)) do |todo_list_component| %> |
2 | <% @todo_list.todo_items.each do |todo_item| %> |
3 | <% todo_list_component.with_todo_item(todo_item: todo_item) %> |
4 | <% end %> |
5 | <% end %> |
1 | <%= render(TodoListComponent.new(todo_list: @todo_list)) do |todo_list_component| %> |
2 | <% @todo_list.todo_items.each do |todo_item| %> |
3 | <% todo_list_component.with_todo_item(todo_item: todo_item) %> |
4 | <% end %> |
5 | <% end %> |
1 | Rails.application.routes.draw do |
2 | resources :todo_lists do |
3 | resources :todo_items |
4 | end
|
5 | end
|
1 | Rails.application.routes.draw do |
2 | resources :todo_lists do |
3 | resources :todo_items |
4 | end
|
5 | end
|
As we saw earlier, Turbo automatically handles submitting forms without refreshing the page. Since we passed the rerender argument to form_with, all the state necessary to rerender our TodoListComponent will be submitted along with the form, including the state to rerender all the TodoItemComponent slots. To add the new item, all we have to do is add another slot.
To do so, let's add a basic create action to app/controllers/todo_items_controller (note that we've excluded the update action we wrote earlier for brevity).
class TodoItemsController < ApplicationController def create @todo_list = TodoList.find(params[:todo_list_id]) @todo_item = @todo_list.todo_items.create(todo_item_params) end private def todo_item_params params.require(:todo_item).permit(:text) end end
class TodoItemsController < ApplicationController def create @todo_list = TodoList.find(params[:todo_list_id]) @todo_item = @todo_list.todo_items.create(todo_item_params) end private def todo_item_params params.require(:todo_item).permit(:text) end end
Let's create the corresponding template in app/views/todo_items/create.turbo_stream.erb:
<%= live.rerender(todo_list: @todo_list) do |todo_list_component| %> <% todo_list_component.with_todo_item(todo_item: @todo_item) %> <% end %>
<%= live.rerender(todo_list: @todo_list) do |todo_list_component| %> <% todo_list_component.with_todo_item(todo_item: @todo_item) %> <% end %>
When the submit button is clicked, the todo list is rerendered with the new item.