Using React to manage your views in Rails 6 - Todo App
Tags: rails, react, rails6
Published: 2020-07-18 09:08:32

---
title: Using React to manage your views in Rails 6 - Todo App
published: true
description: 
tags: #rails #react #rails6
//cover_image: https://direct_url_to_image.jpg
---
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 6
* Nodejs - version 8.16.0 and above
* PostgreSQL - version 12.2
* Yarn - 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 -d=postgresql
```

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
```

`cd` into your app and start up your server.

```
$ cd todo-app
$ rails s
```

We should have our app up and running.
![Rails default welcome page](https://dev-to-uploads.s3.amazonaws.com/i/npyc7hc9nprlb7engeuj.png)

## 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
```

![Generate Todos controller](https://dev-to-uploads.s3.amazonaws.com/i/bru26cjl4jxjme67ymrc.png)

Next we update the `routes.rb` file to make the index route our root route

```ruby
# config/routes.rb

root "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 `app/javascript/packs/hello_react.jsx` to `app/javascript/packs/index.jsx`

```
$ mv app/javascript/packs/hello_react.jsx app/javascript/packs/index.jsx
```

Update the `application.html.erb` javascript pack tag to point to the index.jsx file

```erb
<-! app/views/layouts/application.html.erb ->

<%= javascript_pack_tag 'index' %>
```

Delete all the content in your `app/views/todos/index.html.erb` and start up your server again. We should have the react app content rendered on our root route.

![Root route content - Hello React](https://dev-to-uploads.s3.amazonaws.com/i/h10kilvojb3maqn8wlmx.png)

Note: I am using `localhost:3001` because I have another app running on port `3000`. If you want to change your port, you just need to update line 13 of your `config/puma.rb` file.

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 `completed` attribute to false as a default. 

```ruby
# db/migrate/migration_file_name.rb

class CreateTodos < ActiveRecord::Migration[6.0]
  def change
    create_table :todos do |t|
      t.string :title
      t.boolean :completed, default: false

      t.timestamps
    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:

```ruby
# db/seeds
5.times do |index|
  Todo.create!({ title: "Todo #{index + 1}", completed: false})
end

puts "5 uncompleted todos created"

5.times do |index|
  Todo.create!({ title: "Todo #{index + 1}", completed: true})
end

puts "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.

```ruby
# config/routes.rb

Rails.application.routes.draw do
  root "todos#index"
  get "todos/all_todos"
end
```

```ruby
# app/controllers/todo_controller

def all_todos
    completed = Todo.where(completed: true)
    uncompleted = Todo.where(completed: false)
    render json: { completed: completed, uncompleted: uncompleted }
  end
```
As you can see above, i added a `all_todos` route and a corresponding controller action.

Next, we create a component folder in the javascript folder and add a `Home.jsx` file where we will be performing all our actions. Add the following code to it:

```javascript
# components/Home.jsx
import React, { useState, useEffect } from 'react';
import Loader from './Loader';
import Pending from './Pending';
import Completed from './Completed';

const Home  = () => {
  const [todos, setTodos] = useState({});
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const url = "/todos/all_todos";
    fetch(url)
      .then(response => {
        if (response.ok) {
          return response.json();
        }
        throw new Error("Network response was not ok.");
      })
      .then(response => {
        setTodos(response);
        setLoading(false);
      })
      .catch(() => console.log('An error occurred while fetching the todos'));
  }, []);

  return (
    <div className="vw-100 vh-100 primary-color d-flex justify-content-center">
      <div className="jumbotron jumbotron-fluid bg-transparent">
        <div className="container secondary-color">
          <h1 className="display-4">Todo</h1>
          <p className="lead">
            A curated list of recipes for the best homemade meal and delicacies.
          </p>
          <hr className="my-4" />
          {
            loading ? <Loader /> : (
              <div>
                <Pending pending={todos.pending} />
                <hr className="my-4" />
                <Completed completed={todos.completed} />
              </div>
            )
          }
        </div>
      </div>
    </div>
  )
}

export default Home;
```

Lets create the loader, pending and completed components

```javascript
# components/Loader.jsx

import React from 'react';

const Loader = () => (
  <div className="d-flex justify-content-center">
    <div className="spinner-border" role="status">
      <span className="sr-only">Loading...</span>
    </div>
  </div>
)

export default Loader;
```

```javascript
# components/Pending.jsx

import React from 'react';

const Pending = ({ pending }) => {
  return (
    <div>
      <h4>Pending</h4>
      {pending.map((todo, i) => {
        return (
          <div class="form-check" key={i}>
            <input class="form-check-input" type="checkbox" checked={todo.completed} value="" id={`checkbox${todo.id}`} />
            <label class="form-check-label" for={`checkbox${todo.id}`}>
              {todo.title}
            </label>
          </div>
        )
      })}
    </div>
  )
}

export default Pending;
```

```javascript
# components/Completed.jsx

import React from 'react';

const Completed = ({ completed }) => {
  return (
    <div>
      <h4>Completed</h4>
      {completed.map((todo, i) => {
        return (
          <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>
  )
}

export default Completed;
```
Next, we update our `index.jsx` file to this:

```javascript

import React from 'react'
import ReactDOM from 'react-dom'
import 'bootstrap/dist/css/bootstrap.min.css';
import $ from 'jquery';
import Popper from 'popper.js';
import 'bootstrap/dist/js/bootstrap.bundle.min';
import Home from '../components/Home';

document.addEventListener('DOMContentLoaded', () => {
  ReactDOM.render(
    <Home />,
    document.body.appendChild(document.createElement('div')),
  )
})

```
And our app should be looking like what we have below. It shows a list of pending todos above and completed todos below.
 
![Todo list](https://dev-to-uploads.s3.amazonaws.com/i/61na0lhfulh0qmlnwnyw.png)

## 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 `Pending.jsx` 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 `routes.rb` file, todos_controller etc.

Let's update our `routes.rb`. This below should be our updated file.

```ruby
# config/routes.rb

Rails.application.routes.draw do
  root "todos#index"
  get "todos/all_todos"
  put "todos/update"
end
```

Next, an update to our controller to add the update action. I also updated the `all_todos` action to return an ordered pending items.

```ruby
class TodosController < ApplicationController
  def index
  end

  def all_todos
    completed = Todo.where(completed: true)
    pending = Todo.where(completed: false).order(:id)
    render json: { completed: completed, pending: pending }
  end

  def update
    todo = Todo.find(params[:id])
    if todo.update_attributes!(todo_params)
      render json: { message: "Todo Item updated successfully" }
    else
      render json: { message: "An error occured" }
    end
  end

  private

  def todo_params
    params.require(:todo).permit(:id, :title, :completed)
  end
end
```

Next, we need to update our `Pending.jsx` and move the return contents to its own file. Let's create a `PendingItem.jsx` and add this content.

```javascript
import React, { useState } from 'react';

const PendingItems = ({ todo, handleSubmit }) => {
  const [editing, setEditing] = useState(false);
  const [pendingTodo, setPendingTodo] = useState(todo);

  const handleClick = () => {
    setEditing(true);
  }

  const handleChange = (event) => {
    setPendingTodo({
      ...pendingTodo,
      title: event.target.value
    })
  }

  const handleKeyDown = (event) => {
    if (event.key === 'Enter') {
      setEditing(false);
      handleSubmit(pendingTodo);
    }
  }
  
  return editing ? (
    <div className="form-check 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;
```
This component accepts 2 props; the todoItem and the handleSubmit method. We also created methods to handle click events, change events and keydown events. `handleClick` is used to switch to editing mode so you can edit the todo item. `handleChange` updates the todo state and `handleKeyDown` helps us submit the form. 

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 `Pending.jsx` file to reflect this new component we created and pass a `handleSubmit` method to it. We also need to create the `handleSubmit` to make an api call to update the todoItem.

```javascript
import React from 'react';
import PendingItems from './PendingItems';

const Pending = ({ pending }) => {
  const handleSubmit = (body) => {
    const url = "/todos/update";
    const token = document.querySelector('meta[name="csrf-token"]').content;
    
    fetch(url, {
      method: "PUT",
      headers: {
        "X-CSRF-Token": token,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body)
    })
      .then(response => {
        if (response.ok) {
          return response.json();
        }
        throw new Error("Network response was not ok.");
      })
      .then(response => {
        console.log(response);
        window.location.reload(false);
      })
      .catch(() => console.log('An error occurred while adding the todo item'));
  }
  return (
    <div>
      <h4>Pending</h4>
      {pending.map((todo, i) => {
        return (
          <PendingItems key={i} todo={todo} handleSubmit={handleSubmit} />
        )
      })}
    </div>
  )
}

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 `todos.scss` file:

```css
.form-check.editing {
  display: flex;
  align-items: center;

  .form-check-input {
    margin-top: 0;
  }
}

label {
  cursor: auto;
}
```

I tried editing the first today and it works. Voila!!

![Edit first todo item](https://dev-to-uploads.s3.amazonaws.com/i/0ugqtml0pxx93nusobbc.png)

Lets quickly work on setting a todo item to completed. Update your `PendingItem.jsx` file to what we have below. I changed the `handleChange` method to `handleTitleChange`. I added a `handleCompletedChange` method that makes the api call to mark as complete. And I also removed the `id` in the checkbox input. We don't need them.

```javascript
import React, { useState } from 'react';

const PendingItems = ({ todo, handleSubmit }) => {
  const [editing, setEditing] = useState(false);
  const [pendingTodo, setPendingTodo] = useState(todo);

  const handleClick = () => {
    setEditing(true);
  }

  const handleTitleChange = (event) => {
    setPendingTodo({
      ...pendingTodo,
      title: event.target.value
    })
  }

  const handleCompletedChange = (event) => {
    handleSubmit({
      ...pendingTodo,
      completed: event.target.checked
    })
  }

  const handleKeyDown = (event) => {
    if (event.key === 'Enter') {
      setEditing(false);
      handleSubmit(pendingTodo);
    }
  }
  
  return editing ? (
    <div className="form-check 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;
```
Refresh the page and mark an item as complete to test it out. I marked the second item as completed.

![Completed second Items](https://dev-to-uploads.s3.amazonaws.com/i/wrwx606rh4g9n3ia66cn.png)

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.

![Add todo button](https://dev-to-uploads.s3.amazonaws.com/i/esxwq23njql00rlzkp4i.png)

![Add todo input field](https://dev-to-uploads.s3.amazonaws.com/i/73uvgo0v9vwhinlomgdv.png)

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](https://github.com/Nkemjiks/todo-react-rails6)
* [How To Set Up a Ruby on Rails Project with a React Frontend](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-ruby-on-rails-project-with-a-react-frontend)