Hello everyone. Today, we will be building a todo app to showcase how to use React with Rails 6. This tutorial will be as in-depth as possible and will focus on performing CRUD operations. Our Todo app will support seeing a list of your todo, creating a today, making a todo as done, and deleting a todo. We will be leveraging react to build a reactive app and use material UI for the design. Prerequisites Rails 6Nodejs - version 8.16.0 and abovePostgreSQL - version 12.2Yarn - version 1.21.1 Step 1 - Creating the Rails app Let's start off by creating the rails app and specifying postgres as our database. $ rails new todo-app =postgresql -d The above command creates the todo-app using postgres as its DB, sets up webpack, install yarn dependencies etc. Once installation is complete, run the command below to create your database. $ rails db:create into your app and start up your server. cd $ todo-app cd $ rails s We should have our app up and running. Step 2 - Install React Stop the rails server and run the command below to install and setup React. $ rails webpacker:install:react This should add react and react-dom to your `package.json` file and setup a `hello.jsx` file in the javascript pack. Step 3 - Generate a root route and use react for the view We are going to generate a controller called `Todo` with an index route that we will eventually change to the root route. $ rails g controller Todos index Next we update the `routes.rb` file to make the index route our root route root # config/routes.rb "todos#index" Next, we need to create an entry point to your React environment by adding one of the javascript packs to your application layout. Let's rename the to . app/javascript/packs/hello_react.jsx app/javascript/packs/index.jsx $ mv app/javascript/packs/hello_react.jsx app/javascript/packs/index.jsx Update the javascript pack tag to point to the file. application.html.erb index.jsx <%= %> < / / / > -! app views layouts application.html.erb - javascript_pack_tag 'index' Delete all the content in your and start up your server again. We should have the react app content rendered on our root route. app/views/todos/index.html.erb Note: I am using because I have another app running on port . If you want to change your port, you just need to update line 13 of your file. localhost:3001 3000 config/puma.rb Congratulations. You have a react app rendering the view of our rails app. Step 4 - Generate and Migrate the Todo model Next we need to create a model for Todo. Run the code below to do that: $ rails g model Todo title:string completed:boolean This should generate a model file and a migration file. Next, we need to edit the migration file to set the attribute to false as a default. completed create_table t.string t.boolean , t.timestamps # db/migrate/migration_file_name.rb < ActiveRecord::Migration[6.0] class CreateTodos def change :todos do |t| :title :completed default: false end end end Next we run migration command to create the table in our db. $ rails db:migrate Step 5 - Add seed data to our database Let's add some seed that we can use for our index page before we add the ability to create a todo. Open up your seed file and add the following code: .times Todo.create!({ , }) puts .times Todo.create!({ , }) puts # db/seeds 5 do |index| title: "Todo " #{index + } 1 completed: false end "5 uncompleted todos created" 5 do |index| title: "Todo " #{index + } 1 completed: true end "5 completed todos created" Next, we ran the rails seeds command to add the data to our database. $ rails db:seed Step 6 - Build out our index page First let's add bootstrap to our app for styling. $ yarn add bootstrap jquery popper.js Next, we update the routes file and add a controller action to return our todo list. Note: Every React route must point to a controller action that has an empty view file else when you refresh the page, it will get a missing route error. You need to remember this is you decide to add other routes. We won't be doing that in this article. Rails.application.routes.draw root get # config/routes.rb do "todos#index" "todos/all_todos" end completed = Todo.where( ) uncompleted = Todo.where( ) render { completed, uncompleted } # app/controllers/todo_controller def all_todos completed: true completed: false json: completed: uncompleted: end As you can see above, I added an route and a corresponding controller action. all_todos Next, we create a component folder in the javascript folder and add a file where we will be performing all our actions. Add the following code to it: Home.jsx React, { useState, useEffect } ; Loader ; Pending ; Completed ; const Home = { const [todos, setTodos] = useState({}); const [loading, setLoading] = useState( ); useEffect( { const url = ; fetch(url) . (response => { (response.ok) { response.json(); } Error( ); }) . (response => { setTodos(response); setLoading( ); }) . ( .log( )); }, []); ( <div className= > <div className= > <div className= > <h1 className= >Todo</h1> <p className= > A curated list recipes the best homemade meal delicacies. </p> <hr className= /> { loading ? <Loader /> : ( <div> <Pending pending={todos.pending} /> <hr className= /> <Completed completed={todos.completed} /> </div> ) } </div> </div> </div> ) } Home; # components/Home.jsx import from 'react' import from './Loader' import from './Pending' import from './Completed' => () true => () "/todos/all_todos" then if return throw new "Network response was not ok." then false catch => () console 'An error occurred while fetching the todos' return "vw-100 vh-100 primary-color d-flex justify-content-center" "jumbotron jumbotron-fluid bg-transparent" "container secondary-color" "display-4" "lead" of for and "my-4" "my-4" export default Lets create a loader, pending and completed components # components/Loader.jsx React ; Loader = ( <div className="spinner-border" role="status"> <span className="sr-only">Loading...</span> </div> ) Loader; import from 'react' const => () < = > div className "d-flex justify-content-center" </ > div export default # components/Completed.jsx React ; Completed = { ( <h4>Completed</h4> <div class="form-check" key={i}> <input class="form-check-input" type="checkbox" checked={todo.completed} value="" id={`checkbox${todo.id}`} disabled /> <label class="form-check-label" for={`checkbox${todo.id}`}> {todo.title} </label> </div> ) })} </div> import from 'react' const ( ) => { completed } return < > div {completed.map((todo, i) => { return ( ) }; export default Completed; # components/Pending.jsx React ; Pending = { ( <h4>Pending</h4> <div className="form-check editing"> <input className="form-check-input" disabled type="checkbox" defaultChecked={todo.completed} /> <input type="text" className="form-control-plaintext" value={todo.title} /> </div> ) })} </div> ) } export default Pending; import from 'react' const ( ) => { pending } return < > div {pending.map((todo, i) => { return ( Next, we update our file to this: index.jsx React ReactDOM ; $ ; Popper ; ; Home ; .addEventListener( , () => { ReactDOM.render( import from 'react' import from 'react-dom' import 'bootstrap/dist/css/bootstrap.min.css' import from 'jquery' import from 'popper.js' import 'bootstrap/dist/js/bootstrap.bundle.min' import from '../components/Home' document 'DOMContentLoaded' , document.body.appendChild(document.createElement('div')), ) }) < /> Home And our app should be looking like what we have below. It shows a list of pending todos above and completed todos below. Step 7 - Editing a todo item We are going to be splitting the edit into 2. First will be editing the todoItem title and the second is to mark it as completed. Let's hop on the first option. I am going to be moving the html that is returned in the loop to a component of its own so we can better manage its state. We are also going to be making an update to the file, todos_controller etc. Pending.jsx routes.rb Let's update our . This below should be our updated file. routes.rb Rails.application.routes.draw root get put # config/routes.rb do "todos#index" "todos/all_todos" "todos/update" end Next, an update to our controller to add the update action. I also updated the action to return an ordered pending items. all_todos completed = Todo.where( ) pending = Todo.where( ).order( ) render { completed, pending } todo = Todo.find(params[ ]) todo.update_attributes!(todo_params) render { } render { } private params. ( ).permit( , , ) < ApplicationController class TodosController def index end def all_todos completed: true completed: false :id json: completed: pending: end def update :id if json: message: "Todo Item updated successfully" else json: message: "An error occured" end end def todo_params require :todo :id :title :completed end end Next, we need to update our and move the return contents to its own file. Let's create a and add this content. Pending.jsx PendingItems.jsx React, { useState } ; PendingItems = { [editing, setEditing] = useState( ); [pendingTodo, setPendingTodo] = useState(todo); handleClick = { setEditing( ); } handleChange = { setPendingTodo({ ...pendingTodo, : event.target.value }) } handleKeyDown = { (event.key === ) { setEditing( ); handleSubmit(pendingTodo); } } editing ? ( <input className="form-check-input" disabled type="checkbox" defaultChecked={pendingTodo.completed} id={`checkbox${pendingTodo.id}`} /> <input type="text" className="form-control-plaintext" id="staticEmail2" value={pendingTodo.title} onChange={handleChange} onKeyDown={handleKeyDown} autoFocus/> </div> <div className="form-check"> <input className="form-check-input" type="checkbox" defaultChecked={pendingTodo.completed} id={`checkbox${pendingTodo.id}`} /> <label className="form-check-label" htmlFor={`checkbox${pendingTodo.id}`} onClick={handleClick} > {pendingTodo.title} </label> </div> ) }; export default PendingItems; import from 'react' const ( ) => { todo, handleSubmit } const false const const => () true const ( ) => event title const ( ) => event if 'Enter' false return < = > div className "form-check editing" ) : ( This component accepts 2 props; the todoItem and the handleSubmit method. We also created methods to handle click events, change events and keydown events. is used to switch to editing mode so you can edit the todo item. updates the todo state and helps us submit the form. handleClick handleChange handleKeyDown In the return statement, you will notice that we are switching which content is displayed based on if we are in the editing mode. When editing, an input field is shown with the checkbox disabled. Otherwise, an enabled checkbox with a label is displayed. Next, we update our file to reflect this new component we created and pass a method to it. We also need to create the to make an api call to update the todoItem. Pending.jsx handleSubmit handleSubmit React ; PendingItems ; Pending = { handleSubmit = { url = ; token = .querySelector( ).content; fetch(url, { : , : { : token, : }, : .stringify(body) }) .then( { (response.ok) { response.json(); } ( ); }) .then( { .log(response); .location.reload( ); }) .catch( .log( )); } ( <h4>Pending</h4> <PendingItems key={i} todo={todo} handleSubmit={handleSubmit} /> ) })} </div> import from 'react' import from './PendingItems' const ( ) => { pending } const ( ) => body const "/todos/update" const document 'meta[name="csrf-token"]' method "PUT" headers "X-CSRF-Token" "Content-Type" "application/json" body JSON => response if return throw new Error "Network response was not ok." => response console window false => () console 'An error occurred while adding the todo item' return < > div {pending.map((todo, i) => { return ( ) } export default Pending; If you noticed, we are passing a token to the request. Quoting from a source, below is an explanation of why we need to pass that: To protect against Cross-Site Request Forgery (CSRF) attacks, Rails attaches a CSRF security token to the HTML document. This token is required whenever a non-GET request is made. With the token constant in the preceding code, your application verifies the token on the server and throws an exception if the security token doesn’t match what is expected. I made some css update to the edit mode. You can add this styles to your file: todos.scss { : flex; : center; .form-check-input { : ; } } { : auto; } .form-check .editing display align-items margin-top 0 label cursor I tried editing the first today and it works. Voila!! Let's quickly work on setting a todo item to completed. Update your file to what we have below. I changed the method to . I added a method that makes the api call to mark as complete. And I also removed the in the checkbox input. We don't need them. PendingItem.jsx handleChange handleTitleChange handleCompletedChange id React, { useState } ; PendingItems = { [editing, setEditing] = useState( ); [pendingTodo, setPendingTodo] = useState(todo); handleClick = { setEditing( ); } handleTitleChange = { setPendingTodo({ ...pendingTodo, : event.target.value }) } handleCompletedChange = { handleSubmit({ ...pendingTodo, : event.target.checked }) } handleKeyDown = { (event.key === ) { setEditing( ); handleSubmit(pendingTodo); } } editing ? ( <input className="form-check-input" disabled type="checkbox" defaultChecked={pendingTodo.completed} /> <input type="text" className="form-control-plaintext" id="staticEmail2" value={pendingTodo.title} onChange={handleTitleChange} onKeyDown={handleKeyDown} autoFocus/> </div> <div className="form-check"> <input className="form-check-input" type="checkbox" defaultChecked={pendingTodo.completed} id={`checkbox${pendingTodo.id}`} onChange={handleCompletedChange} /> <label className="form-check-label" htmlFor={`checkbox${pendingTodo.id}`} onClick={handleClick} > {pendingTodo.title} </label> </div> ) } export default PendingItems; import from 'react' const ( ) => { todo, handleSubmit } const false const const => () true const ( ) => event title const ( ) => event completed const ( ) => event if 'Enter' false return < = > div className "form-check editing" ) : ( Refresh the page and mark an item as complete to test it out. I marked the second item as completed. I don't want the article to be too long so I stop here. I will add the link to the repo below so you can checkout how I implemented the addTodo feature but I think you should be able to do that from the example above. Remember, you React route must match a controller action with an empty view file else, when you refresh the page, you get an unknown route error. Our final app looks like this with the add todo feature. Feel free to refactor the react app as you see fit. I just wanted to focus on integrating with React with Rails and might have missed some best practices implementation styles. Let me know what you think of the article in the comment below. Until next week. Links and Resources Github repo How To Set Up a Ruby on Rails Project with a React Frontend