React + Ruby on Rails = 🔥
React has taken the frontend development world by storm. It’s an excellent JavaScript library for building user interfaces. And it’s great in combination with Ruby on Rails. You can use Rails on the back end with React on the front end in various ways.
In this hands-on tutorial, we’re going to build a React app that works with a Rails 5.1 API.
You can watch a video version of this tutorial here.
To follow this tutorial, you need to be comfortable with Rails and know the basics of React.
If you don’t use Rails, you can also build the API in the language or framework of your choice, and just use this tutorial for the React part.
The tutorial covers stateless functional components, class-based components, using Create React App, use of axios for making API calls, immutability-helper and more.
Key Takeaways
- Utilize Rails 5.1 API to create a backend for a React single-page application (SPA), ensuring you have Rails version 5.1.3 or higher installed for API-only applications.
- Set up the front-end using Create React App, a tool by Facebook that simplifies React app creation without manual configuration.
- Implement CRUD (Create, Read, Update, Delete) functionalities in React, using axios for API calls and managing state updates with immutability-helper.
- Enable Cross-Origin Resource Sharing (CORS) in the Rails application to allow the React app, running on a different port, to interact with the API.
- Use functional components for displaying data and class components for handling logic and state in React, enhancing modularity and reuse.
- Handle form inputs and API interactions in React to add, update, and delete ideas, with immediate UI updates.
- Prepare the application for future enhancements like animations, sorting, and search capabilities, and consider deployment strategies for production.
What We’re Going to Build
We’re going to build an idea board as a single page app (SPA), which displays ideas in the form of square tiles.
You can add new ideas, edit them and delete them. Ideas get auto-saved when the user focuses out of the editing form.
At the end of this tutorial, we’ll have a functional CRUD app, to which we can add some enhancements, such as animations, sorting and search in a future tutorial.
You can see the full code for the app on GitHub:
Setting up the Rails API
Let’s get started by building the Rails API. We’ll use the in-built feature of Rails for building API-only apps.
Make sure you have version 5.1 or higher of the Rails gem installed.
gem install rails -v 5.1.3
At the time of writing this tutorial, 5.1.3 is the latest stable release, so that’s what we’ll use.
Then generate a new Rails API app with the --api
flag.
rails new --api ideaboard-api
cd ideaboard-api
Next, let’s create the data model. We only need one data model for ideas with two fields — a title and a body, both of type string
.
Let’s generate and run the migration:
rails generate model Idea title:string body:string
rails db:migrate
Now that we’ve created an ideas table in our database, let’s seed it with some records so that we have some ideas to display.
In the db/seeds.rb
file, add the following code:
ideas = Idea.create(
[
{
title: "A new cake recipe",
body: "Made of chocolate"
},
{
title: "A twitter client idea",
body: "Only for replying to mentions and DMs"
},
{
title: "A novel set in Italy",
body: "A mafia crime drama starring Berlusconi"
},
{
title: "Card game design",
body: "Like Uno but involves drinking"
}
])
Feel free to add your own ideas.
Then run:
rails db:seed
Next, let’s create an IdeasController with an index action in app/controllers/api/v1/ideas_controller.rb
:
module Api::V1
class IdeasController < ApplicationController
def index
@ideas = Idea.all
render json: @ideas
end
end
end
Note that the controller is under app/controllers/api/v1
because we’re versioning our API. This is a good practice to avoid breaking changes and provide some backwards compatibility with our API.
Then add ideas as a resource in config/routes.rb
:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :ideas
end
end
end
Alright, now let’s test our first API endpoint!
First, let’s start the Rails API server on port 3001:
rails s -p 3001
Then, let’s test our endpoint for getting all ideas with curl:
curl -G http://localhost:3001/api/v1/ideas
And that prints all our ideas in JSON format:
[{"id":18,"title":"Card game design","body":"Like Uno but involves drinking","created_at":"2017-09-05T15:42:36.217Z","updated_at":"2017-09-05T15:42:36.217Z"},{"id":17,"title":"A novel set in Italy","body":"A mafia crime drama starring Berlusconi","created_at":"2017-09-05T15:42:36.213Z","updated_at":"2017-09-05T15:42:36.213Z"},{"id":16,"title":"A twitter client idea","body":"Only for replying to mentions and DMs","created_at":"2017-09-05T15:42:36.209Z","updated_at":"2017-09-05T15:42:36.209Z"},{"id":15,"title":"A new cake recipe","body":"Made of chocolate","created_at":"2017-09-05T15:42:36.205Z","updated_at":"2017-09-05T15:42:36.205Z"}]
We can also test the endpoint in a browser by going to http://localhost:3001/api/v1/ideas.
Setting up Our Front-end App Using Create React App
Now that we have a basic API, let’s set up our front-end React app using Create React App. Create React App is a project by Facebook that helps you get started with a React app quickly without any configuration.
First, make sure you have Node.js and npm installed. You can download the installer from the Node.js website. Then install Create React App by running:
npm install -g create-react-app
Then, make sure you’re outside the Rails directory and run the following command:
create-react-app ideaboard
That will generate a React app called ideaboard, which we’ll now use to talk to our Rails API.
Let’s run the React app:
cd ideaboard
npm start
This will open it on http://localhost:3000.
The app has a default page with a React component called App that displays the React logo and a welcome message.
The content on the page is rendered through a React component in the src/App.js
file:
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App
Our First React Component
Our next step is to edit this file to use the API we just created and list all the ideas on the page.
Let’s start off by replacing the Welcome message with an h1
tag with the title of our app ‘Idea Board’.
Let’s also add a new component called IdeasContainer
. We need to import it and add it to the render function:
import React, { Component } from 'react'
import './App.css'
import IdeasContainer from './components/IdeasContainer'
class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
<h1>Idea Board</h1>
</div>
<IdeasContainer />
</div>
);
}
}
export default App
Let’s create this IdeasContainer
component in a new file in src/IdeasContainer.js
under a src/components
directory.
import React, { Component } from 'react'
class IdeasContainer extends Component {
render() {
return (
<div>
Ideas
</div>
)
}
}
export default IdeasContainer
Let’s also change the styles in App.css
to have a white header and black text, and also remove styles we don’t need:
.App-header {
text-align: center;
height: 150px;
padding: 20px;
}
.App-intro {
font-size: large;
}
This component needs to talk to our Rails API endpoint for getting all ideas and display them.
Fetching API Data with axios
We’ll make an Ajax call to the API in the componentDidMount()
lifecycle method of the IdeasContainer component and store the ideas in the component state.
Let’s start by initializing the state in the constructor with ideas as an empty array:
constructor(props) {
super(props)
this.state = {
ideas: []
}
}
And then we’ll update the state in componentDidMount()
.
Let’s use the axios library for making the API calls. You can also use fetch or jQuery if you prefer those.
Install axios with npm:
npm install axios --save
Then import it in IdeasContainer
:
import axios from 'axios'
And use it in componentDidMount()
:
componentDidMount() {
axios.get('http://localhost:3001/api/v1/ideas.json')
.then(response => {
console.log(response)
this.setState({ideas: response.data})
})
.catch(error => console.log(error))
}
Now if we refresh the page … it won’t work!
We’ll get a “No Access-Control-Allow-Origin header present” error, because our API is on a different port and we haven’t enabled Cross Origin Resource Sharing (CORS).
Enabling Cross Origin Resource Sharing (CORS)
So let’s first enable CORS using the rack-cors gem in our Rails app.
Add the gem to the Gemfile:
gem 'rack-cors', :require => 'rack/cors'
Install it:
bundle install
Then add the middleware configuration to config/application.rb
file:
config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:3000'
resource '*', :headers => :any, :methods => [:get, :post, :put, :delete, :options]
end
end
We restrict the origins to our front-end app at http://localhost:3000
and allow access to the standard REST API endpoint methods for all resources.
Now we need to restart the Rails server, and if we refresh the browser, we’ll no longer get the CORS error.
The page will load fine and we can see the response data logged in the console.
So now that we know we’re able to fetch ideas from our API, let’s use them in our React component.
We can change the render function to iterate through the list ideas from the state and display each of them:
render() {
return (
<div>
{this.state.ideas.map((idea) => {
return(
<div className="tile" key={idea.id} >
<h4>{idea.title}</h4>
<p>{idea.body}</p>
</div>
)
})}
</div>
);
}
That will display all the ideas on the page now.
Note the key
attribute on the tile div.
We need to include it when creating lists of elements. Keys help React identify which items have changed, are added, or are removed.
Now let’s add some styling in App.css
to make each idea look like a tile:
.tile {
height: 150px;
width: 150px;
margin: 10px;
background: lightyellow;
float: left;
font-size: 11px;
text-align: left;
}
We set the height, width, background color and make the tiles float left.
Stateless functional components
Before we proceed, let’s refactor our code so far and move the JSX for the idea tiles into a separate component called Idea
.
import React from 'react'
const Idea = ({idea}) =>
<div className="tile" key={idea.id}>
<h4>{idea.title}</h4>
<p>{idea.body}</p>
</div>
export default Idea
This is a stateless functional component (or as some call it, a “dumb” component), which means that it doesn’t handle any state. It’s a pure function that accepts some data and returns JSX.
Then inside the map function in IdeasContainer
, we can return the new Idea component:
{this.state.ideas.map((idea) => {
return (<Idea idea={idea} key={idea.id} />)
})}
Don’t forget to import Idea
as well:
import Idea from './Idea'
Great, so that’s the first part of our app complete. We have an API with an endpoint for getting ideas and a React app for displaying them as tiles on a board!
Adding a new record
Next, we’ll add a way to create new ideas.
Let’s start by adding a button to add a new idea.
Inside the render function in IdeasContainer
, add:
<button className="newIdeaButton">
New Idea
</button>
And let’s add some styling for it in App.css
:
.newIdeaButton {
background: darkblue;
color: white;
border: none;
font-size: 18px;
cursor: pointer;
margin-right: 10px;
margin-left: 10px;
padding:10px;
}
Now when we click the button, we want another tile to appear with a form to edit the idea.
Once we edit the form, we want to submit it to our API to create a new idea.
API endpoint for creating a new idea
So let’s start of by first making an API endpoint for creating new ideas in IdeasController
:
def create
@idea = Idea.create(idea_params)
render json: @idea
end
private
def idea_params
params.require(:idea).permit(:title, :body)
end
Since Rails uses strong parameters, we define the private method idea_params
to whitelist the params we need — title
and body
.
Now we have an API endpoint to which we can post idea data and create new ideas.
Back in our React app, now let’s add a click handler called addNewIdea
to the new idea button:
<button className="newIdeaButton"
onClick={this.addNewIdea} >
New Idea
</button>
Let’s define addNewIdea
as a function that uses axios to make a POST call to our new idea endpoint with a blank idea. Let’s just log the response to the console for now:
addNewIdea = () => {
axios.post(
'http://localhost:3001/api/v1/ideas',
{ idea:
{
title: '',
body: ''
}
}
)
.then(response => {
console.log(response)
})
.catch(error => console.log(error))
}
Now if we try clicking on the new idea button in the browser, we’ll see in the console that the response contains a data object with our new idea with a blank title and body.
When we refresh the page, we can see an empty tile representing our new idea.
What we really want to happen is that, when we click the new idea button, an idea is created immediately, and a form for editing that idea appears on the page.
This way, we can use the same form and logic for editing any idea later on in the tutorial.
Before we do that, let’s first order the ideas on the page in reverse chronological order so that the newest ideas appear at the top.
So let’s change the definition of @ideas
in IdeasController
to order ideas in descending order of their created_at
time:
module Api::V1
class IdeasController < ApplicationController
def index
@ideas = Idea.order("created_at DESC")
render json: @ideas
end
end
end
Alright, now the latest ideas are displayed first.
Now, let’s continue with defining addNewIdea
.
First, let’s use the response from our POST
call to update the array of ideas in the state, so that when we add a new idea it appears on the page immediately.
We could just push
the new idea to the array, since this is only an example app, but it’s good practice to use immutable data.
So let’s use immutability-helper, which is a nice package for updating data without directly mutating it.
Install it with npm:
npm install immutability-helper --save
Then import the update
function in IdeasContainer
:
import update from 'immutability-helper'
Now let’s use it inside addNewIdea
to insert our new idea at the beginning of the array of ideas:
addNewIdea = () => {
axios.post(
'http://localhost:3001/api/v1/ideas',
{ idea:
{
title: '',
body: ''
}
}
)
.then(response => {
console.log(response)
const ideas = update(this.state.ideas, {
$splice: [[0, 0, response.data]]
})
this.setState({ideas: ideas})
})
.catch(error => console.log(error))
}
We make a new copy of this.state.ideas
and use the $splice
command to insert the new idea (in response.data
) at the 0th index of this array.
Then we use this new ideas array to update the state using setState
.
Now if we try the app in the browser and click the new idea button, a new empty tile appears immediately.
Now we can proceed with editing this idea.
First, we need a new state property editingIdeaId
, which keeps track of which idea is being currently edited.
By default, we’re not editing any idea, so let’s initialize editingIdeaId
in the state with a null value:
this.state = {
ideas: [],
editingIdeaId: null
}
Now when we add a new idea, in addition to adding it to state.ideas
, we also want to set its id as the value of state.editingIdeaId
. So let’s modify the setState
call in addNewIdea
to include also set editingIdeaId
:
this.setState({
ideas: ideas,
editingIdeaId: response.data.id
})
So this indicates that we’ve just added a new idea and we want to edit it immediately.
The complete addNewIdea
function now looks like this:
addNewIdea = () => {
axios.post(
'http://localhost:3001/api/v1/ideas',
{ idea:
{
title: '',
body: ''
}
}
)
.then(response => {
const ideas = update(this.state.ideas, {
$splice: [[0, 0, response.data]]
})
this.setState({
ideas: ideas,
editingIdeaId: response.data.id
})
})
.catch(error => console.log(error))
}
A Form component
Now we can use state.editingIdeaId
in the render function, so that instead of displaying just a normal idea tile, we can display a form.
Inside the map
function, let’s change the return value to a conditional statement, which renders an IdeaForm
component if an idea’s id matches state.editingIdeaId
, otherwise rendering an Idea
component:
{this.state.ideas.map((idea) => {
if(this.state.editingIdeaId === idea.id) {
return(<IdeaForm idea={idea} key={idea.id} />)
} else {
return (<Idea idea={idea} key={idea.id} />)
}
})}
Let’s import the IdeaForm
component in IdeasContainer
:
import IdeaForm from './IdeaForm'
And let’s define it in IdeaForm.js
. We’ll start with a simple class component, which renders a form with two input fields for the idea title and body:
import React, { Component } from 'react'
import axios from 'axios'
class IdeaForm extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
return (
<div className="tile">
<form>
<input className='input' type="text"
name="title" placeholder='Enter a Title' />
<textarea className='input' name="body"
placeholder='Describe your idea'></textarea>
</form>
</div>
);
}
}
export default IdeaForm
Let’s add a bit of CSS in App.css
to style the form:
.input {
border: 0;
background: none;
outline: none;
margin-top:10px;
width: 140px;
font-size: 11px;
}
.input:focus {
border: solid 1px lightgrey;
}
textarea {
resize: none;
height: 90px;
font-size: 11px;
}
Now when we click on the new idea button, a new tile appears with a form in it:
Now let’s make this form functional!
We need to hook up the form input fields to the state.
First, let’s initialize the IdeaForm
component state values from the idea
prop that it receives from IdeasContainer
:
class IdeaForm extends Component {
constructor(props) {
super(props)
this.state = {
title: this.props.idea.title,
body: this.props.idea.body
}
}
Then set the form field values to their corresponding state values and set an onChange
handler:
<form>
<input className='input' type="text"
name="title" placeholder='Enter a Title'
value={this.state.title} onChange={this.handleInput} />
<textarea className='input' name="body"
placeholder='Describe your idea'
value={this.state.body} onChange={this.handleInput}>
</textarea>
</form>
We’ll define handleInput
such that, when we type in either of the input fields, the corresponding state value and then the value of the field gets updated:
handleInput = (e) => {
this.setState({[e.target.name]: e.target.value})
}
Tracking state changes in React Developer Tools
Let’s see these state changes in action with the React Developer Tools browser extension. You can get it for Chrome here and for Firefox here.
Once you have it installed, refresh the app page and open the developer console. You should see a new React tab.
When you click on it, you’ll see our app components tree on the left and all the props and state associated with each component on the right.
Now we’re updating the form fields, but we’re still not saving the edited idea. So the next thing needed is that, when we blur out of a form field, we want to submit the form and update the idea.
API endpoint for updating ideas
First, we need to define an API endpoint for updating ideas. So let’s add an update
action in IdeasController
:
def update
@idea = Idea.find(params[:id])
@idea.update_attributes(idea_params)
render json: @idea
end
Back in IdeaForm.js
, we’ll set an onBlur
handler called handleBlur
to the form:
<form onBlur={this.handleBlur} >
We’ll define handleBlur
to make a PUT
call to our API endpoint for updating ideas with idea data from the state. For now, let’s just log the response to the console and see if our call works:
handleBlur = () => {
const idea = {
title: this.state.title,
body: this.state.body
}
axios.put(
`http://localhost:3001/api/v1/ideas/${this.props.idea.id}`,
{
idea: idea
})
.then(response => {
console.log(response)
})
.catch(error => console.log(error))
}
We also need to import axios in this file to be able to use it:
import axios from 'axios'
Now if we click on the new idea button, edit its title and blur out of that field, we’ll see our API response logged in the console, with the new edited idea data.
The same thing happens if we edit the body and blur out of that field.
So our onBlur
handler works and we can edit our new idea, but we also need to send the edited idea data back up to IdeasContainer
so that it can update its own state too.
Otherwise, state.ideas
won’t have the updated value of the idea we just edited.
We’ll use a method called updateIdea
, which we’ll pass as a prop from IdeasContainer
to IdeaForm
. We’ll call updateIdea
with the response data from our API call:
handleBlur = () => {
const idea = {
title: this.state.title,
body: this.state.body
}
axios.put(
`http://localhost:3001/api/v1/ideas/${this.props.idea.id}`,
{
idea: idea
})
.then(response => {
console.log(response)
this.props.updateIdea(response.data)
})
.catch(error => console.log(error))
}
Now in IdeasContainer
, let’s send an updateIdea
function as a prop to IdeaForm:
<IdeaForm idea={idea} key={idea.id}
updateIdea={this.updateIdea} />
Let’s define the function to do an immutable update of the idea in state.ideas
:
updateIdea = (idea) => {
const ideaIndex = this.state.ideas.findIndex(x => x.id === idea.id)
const ideas = update(this.state.ideas, {
[ideaIndex]: { $set: idea }
})
this.setState({ideas: ideas})
}
First, we find the index of the edited idea in the array, and then use the $set
command to replace the old value with the new one. Finally, we call setState to update state.ideas
.
We can see this in action in the browser with the React Developer Tools tab open.
Displaying a success notification
Now we can add a new idea and edit it, but the user gets no visual feedback or confirmation when the idea is saved. So let’s add a notification message to tell the user when an idea has been successfully saved.
Let’s add a span next to the new idea button to display a notification from a value in state:
<span className="notification">
{this.state.notification}
</span>
Let’s initialize state.notification
as an empty string:
constructor(props) {
super(props)
this.state = {
ideas: [],
editingIdeaId: null,
notification: ''
}
}
Now every time an idea gets updated, we’ll update state.notification
with a success notification we want to show to the user.
So in the setState
call in updateIdea
, in addition to updating ideas
, let’s also update notification
:
this.setState({
ideas: ideas,
notification: 'All changes saved'
})
Now when we edit an idea and blur out of the input field, the idea gets saved and we see the success notification.
We also want to reset the notification as soon as the user makes a change that hasn’t been saved yet.
So in the handleInput
function of the IdeaForm
component, let’s call a function called resetNotification
to reset the notification message:
handleInput = (e) => {
this.props.resetNotification()
this.setState({[e.target.name]: e.target.value})
}
Now, inside the render
function of IdeasContainer
, let’s also pass resetNotification
as a prop to IdeaForm
:
<IdeaForm idea={idea} key={idea.id}
updateIdea={this.updateIdea}
resetNotification={this.resetNotification} />
Let’s define resetNotification
as:
resetNotification = () => {
this.setState({notification: ''})
}
Now after a success notification appears, if we edit the idea again, the notification disappears.
Editing an existing idea
Next, let’s add the ability to edit an existing idea. When we click on an idea tile, we want to change the tile so that it replaces the Idea
component with an IdeaForm
component to edit that idea.
Then we can edit the idea and it will get saved on blur.
In order to add this feature, we need to add a click handler on our idea tiles.
So first we need to convert our Idea
component from a functional component into a class component and then we can set define a click handler function handleClick
for the title and body.
import React, { Component } from 'react'
class Idea extends Component {
handleClick = () => {
this.props.onClick(this.props.idea.id)
}
render () {
return(
<div className="tile">
<h4 onClick={this.handleClick}>
{this.props.idea.title}
</h4>
<p onClick={this.handleClick}>
{this.props.idea.body}
</p>
</div>
)
}
}
export default Idea
Note that we have to add this.props.
to use the props value, because unlike in the functional component, we are no longer destructuring the props object.
handleClick
calls this.props.onClick
with the idea id.
Now, inside the render
function of IdeasContainer
, let’s also pass onClick
as a prop to Idea
:
return (<Idea idea={idea} key={idea.id} onClick={this.enableEditing} />)
We’ll define enableEditing
to set the value of state.editingIdeaId
to the clicked idea’s id:
enableEditing = (id) => {
this.setState({editingIdeaId: id})
}
Now when we click on a tile, it instantly becomes editable!
When we click on a tile, once the form appears, let’s also set the cursor focus to the title input field.
We can do that by adding a ref on the title input field in IdeaForm
:
<input className='input' type="text"
name="title" placeholder='Enter a Title'
value={this.state.title} onChange={this.handleInput}
ref={this.props.titleRef} />
We need to pass the ref as a prop, because we want to use it in the parent component IdeasContainer
, where we can define the ref as a callback function:
<IdeaForm idea={idea} key={idea.id}
updateIdea={this.updateIdea}
titleRef= {input => this.title = input}
resetNotification={this.resetNotification} />
Now we can use this ref in enableEditing
to set the focus in the title input field:
enableEditing = (id) => {
this.setState({editingIdeaId: id},
() => { this.title.focus() })
}
Notice that we didn’t call this.title.focus()
as a separate function after calling setState
. Instead, we passed it to setState
inside a callback as a second argument.
We did this because setState
doesn’t always immediately update the component. By passing our focus call in a callback, we make sure that it gets called only after the component has been updated.
Now if we try the app in a browser, when we click on an idea tile, it becomes editable with a form and the cursor gets focused inside its title input field.
So now we can add and edit ideas.
Deleting an idea
Finally, we want to be able to delete ideas.
When we hover over an idea tile, we want a delete button (in the form of a red cross) to appear in the top right corner. Clicking that cross should delete the idea and remove the tile from the board.
So let’s start by adding some markup and CSS to display the delete button on hover.
In the Idea
component, add a span with a class deleteButton
and the text ‘x’:
<div className="tile">
<span className="deleteButton">
x
</span>
Then let’s add some CSS in App.css
to hide this span by default and make it visible when we hover over a tile:
.deleteButton {
visibility: hidden;
float: right;
margin: 5px;
font-size: 14px;
cursor: pointer;
color: red;
}
.tile:hover .deleteButton {
visibility: visible;
}
Next, let’s add a click handler handleDelete
to this delete button, which then deletes the idea:
<span className="deleteButton" onClick={this.handleDelete}>
x
</span>
Similar to handleClick
, we’ll define handleDelete
as an arrow function that calls another function this.props.onDelete
with the tile’s idea id:
handleDelete = () => {
this.props.onDelete(this.props.idea.id)
}
Let’s pass onDelete
as a prop from IdeasContainer
:
<Idea idea={idea} key={idea.id}
onClick={this.enableEditing}
onDelete={this.deleteIdea} />
We’ll define deleteIdea
in a moment, but first let’s add an API endpoint for deleting ideas in IdeasController
:
def destroy
@idea = Idea.find(params[:id])
if @idea.destroy
head :no_content, status: :ok
else
render json: @idea.errors, status: :unprocessable_entity
end
end
Now let’s define deleteIdea
in IdeasContainer
as a function that makes a DELETE
call to our API with the idea id and, on success, updates state.ideas
:
deleteIdea = (id) => {
axios.delete(`http://localhost:3001/api/v1/ideas/${id}`)
.then(response => {
const ideaIndex = this.state.ideas.findIndex(x => x.id === id)
const ideas = update(this.state.ideas, { $splice: [[ideaIndex, 1]]})
this.setState({ideas: ideas})
})
.catch(error => console.log(error))
}
Once again, we look up the index of the deleted idea, use update
with the $splice
command to create a new array of ideas, and then update state.ideas
with that.
Now we can try it in the browser. When we hover over an idea tile, the red delete button appears. Clicking on it deletes the idea and removes the tile from the board.
Hurray, we now have a functional app with all the basic CRUD functionality!
Wrap Up
In this tutorial, we built a complete CRUD app using a Rails 5.1 API and a front-end React app.
Our API has three endpoints, one each for creating, updating and deleting ideas.
We used Create React App to make our React app. This made setup completely painless and easy. We could dive straight into building our app instead of configuring anything.
We used axios for making Ajax calls to the API and immutability-helper to make data updates.
In a future tutorial, we can look at how to deploy this app to a production server and also add some animations and transitions to spice up the UI. For example, we could fade in new idea tiles and fade out deleted tiles, fade in and out notification messages.
You can watch a video version of this tutorial here.
You can see the full code for the app on GitHub:
Frequently Asked Questions (FAQs) on React and Rails 5.1 Integration
How Do I Set Up a Ruby on Rails Project with a React Frontend?
Setting up a Ruby on Rails project with a React frontend involves several steps. First, you need to create a new Rails application using the command “rails new myapp –webpack=react”. This command sets up a new Rails application with Webpack and React. After creating the application, navigate into the application directory using the command “cd myapp”. Then, you can start the Rails server using the command “rails s”. You can now access your application at “http://localhost:3000“.
How Do I Connect a Ruby on Rails App with React in a Monolith?
Connecting a Ruby on Rails app with React in a monolith involves using the react-rails gem. First, add the gem to your Gemfile using the line “gem ‘react-rails'”. Then, run “bundle install” to install the gem. After installing the gem, run “rails generate react:install” to set up the necessary files. You can now use React components in your Rails views.
What are the Benefits of Integrating React with Ruby on Rails?
Integrating React with Ruby on Rails offers several benefits. React allows for the creation of interactive user interfaces with a high degree of flexibility and efficiency. It also enables the development of single-page applications, which can provide a smoother user experience. On the other hand, Rails provides a robust and mature framework for server-side development, including features like database abstraction, routing, and session management.
How Do I Use the react-rails Gem?
The react-rails gem provides a bridge between Rails and React. After installing the gem, you can use the provided helper method “react_component” to render React components in your Rails views. The method takes the name of the component and an optional hash of props as arguments.
How Do I Handle State Management in a React and Rails Application?
State management in a React and Rails application can be handled using various libraries like Redux or MobX. These libraries provide a way to manage and share state across components. Alternatively, you can also use React’s built-in state management features.
How Do I Deploy a React and Rails Application?
Deploying a React and Rails application can be done using various platforms like Heroku, AWS, or Google Cloud. The exact steps depend on the platform, but generally involve pushing your code to a repository, setting up a server, and configuring the deployment settings.
How Do I Test a React and Rails Application?
Testing a React and Rails application involves writing unit tests for individual components and integration tests for the entire application. You can use libraries like Jest for React components and RSpec for Rails.
How Do I Handle Routing in a React and Rails Application?
Routing in a React and Rails application can be handled using the built-in Rails routing system for server-side routes and libraries like React Router for client-side routes.
How Do I Handle Authentication in a React and Rails Application?
Authentication in a React and Rails application can be handled using various libraries like Devise for Rails and react-router for React. These libraries provide features like user registration, login, and session management.
How Do I Optimize Performance in a React and Rails Application?
Performance in a React and Rails application can be optimized using various techniques like code splitting, lazy loading, and server-side rendering. These techniques can help reduce the initial load time and improve the overall user experience.
Hi, I'm the founder of Learnetto.com, where I teach React.js to Ruby on Rails developers.