The common operations in any information system are the create, read, update, and delete (CRUD) operations against a record in the database. In this guide, we'll take a look at how to perform the create and update parts in the context of a React.js app. The component should be able to provide a form that will deal with saving information as a new record or updating an existing record. In traditional approaches, to distinguish a create
from an update
coming from a form, the id
value has to be present in order to indicate that the form details will perform an update rather than a create. This has been a security risk, however, since the id
is sometimes exposed either as a hidden value within HTML or as part of the URL to connect to. We'll see in this guide that we can use React.js' state management approach to maintain the id
value only within the React.js logic and not in the interface itself.
Before we begin, let's make three assumptions:
id
, first_name
and last_name
.http://localhost:3000/api/v1/people/save
.http://localhost:3000/api/v1/people/fetch
.This guide will provide a simple set of instructions on how to implement the backend using Ruby on Rails in the section Rails Server Code at the end. Regardless, you can still use your own favorite framework to implement the backend that meets the assumptions above.
Create an initial component that maintains the id
, first_name
, and last_name
states reflecting the data model of a person.
1import React from 'react';
2import $ from 'jquery';
3
4export default class PersonForm extends React.Component {
5 constructor(props) {
6 super(props);
7
8 this.state = {
9 id: props.id,
10 firstName: "",
11 lastName: ""
12 }
13 }
14
15 updateFirstName(event) {
16 this.setState({
17 firstName: event.target.value
18 });
19 }
20
21 updateLastName(event) {
22 this.setState({
23 lastName: event.target.value
24 });
25 }
26
27 render() {
28 return (
29 <div>
30 First Name:
31 <input type="text" value={this.state.firstName} onChange={this.updateFirstName.bind(this)} />
32 Last Name:
33 <input type="text" value={this.state.lastName} onChange={this.updateLastName.bind(this)} />
34 <hr/>
35
36 <button>
37 Save
38 </button>
39 </div>
40 );
41 }
42}
This is a standard React.js component that has minimal logic in it, namely, the utilization of updateFirstName
and updateLastName
methods to update the state of firstName
and lastName
whenever the user changes something. Notice also that in the constructor, you get to pass an id
as part of props
. This suggests that it is possible to mount this component and pass an id
from the parent calling it, which will provide information to perform an update. Optionally, you can also not pass an id
suggesting that you're using the component to create a new record.
Create a method that will perform a POST to the API endpoint /api/v1/people/save
. The method looks like the following:
1save() {
2 var context = this;
3
4 $.ajax({
5 url: "http://localhost:3000/api/v1/people/save",
6 method: "POST",
7 data: {
8 id: context.state.id,
9 first_name: context.state.firstName,
10 last_name: context.state.lastName
11 },
12 success: function(response) {
13 alert("Successfully saved record!");
14 },
15 error: function(response) {
16 alert("Error in saving record!");
17 }
18 });
19}
Notice that you first have to create a proxy for this
so you can still refer to the instance of this component within the ajax
call, such as accessing the current state values context.state.id
, context.state.firstName
, and context.state.lastName
. This will perform a POST method against the API. If no id
was supplied then it creates a record. But if an id
was initially supplied to the component via props
, then the backend should perform an update instead.
Finally, connect the save
method to the onClick
attribute of the component's button:
1<button onClick={this.save.bind(this)}>
2 Save
3</button>
Make sure you have an application server running with the specifications mentioned earlier. This section will allow you to implement a backend server written in Ruby on Rails using the sqlite database so you won't have any dependencies besides Rails.
1$ rails new sampleapi
2$ cd sampleapi
1$ rails g model Person first_name:string last_name:string
2$ rake db:migrate
config/routes.rb
, create the API route definition.1namepsace :api do
2 namespace :v1 do
3 get "/people/fetch", to: "people#fetch"
4 post "/people/save", to: "people#save"
5 end
6end
app/controllers/api/v1/people_controller.rb
.1module Api
2 module V1
3 class PeopleController < ApplicationController
4 protect_from_forgery with: :null_session
5
6 def fetch
7 person = Person.find(params[:id])
8
9 render json: person
10 end
11
12 def save
13 person = Person.find_by_id(params[:id])
14
15 if person.present?
16 person.first_name = params[:first_name]
17 person.last_name = params[:last_name]
18 else
19 person = Person.new(first_name: params[:first_name], last_name: params[:last_name])
20 end
21
22 person.save!
23
24 render json: { message: "success", id: person.id }
25 end
26 end
27 end
28end
1$ rails server -b 127.0.0.1
The final code should look like the following:
1import React from 'react';
2import $ from 'jquery';
3
4export default class PersonForm extends React.Component {
5 constructor(props) {
6 super(props);
7
8 this.state = {
9 id: props.id,
10 firstName: "",
11 lastName: ""
12 }
13 }
14
15 updateFirstName(event) {
16 this.setState({
17 firstName: event.target.value
18 });
19 }
20
21 updateLastName(event) {
22 this.setState({
23 lastName: event.target.value
24 });
25 }
26
27 save() {
28 var context = this;
29
30 $.ajax({
31 url: "http://localhost:3000/api/v1/people/save",
32 method: "POST",
33 data: {
34 id: context.state.id,
35 first_name: context.state.firstName,
36 last_name: context.state.lastName
37 },
38 success: function(response) {
39 alert("Successfully saved record!");
40 },
41 error: function(response) {
42 alert("Error in saving record!");
43 }
44 });
45 }
46
47 render() {
48 return (
49 <div>
50 First Name:
51 <input type="text" value={this.state.firstName} onChange={this.updateFirstName.bind(this)} />
52 Last Name:
53 <input type="text" value={this.state.lastName} onChange={this.updateLastName.bind(this)} />
54 <hr/>
55
56 <button onClick={this.save.bind(this)}>
57 Save
58 </button>
59 </div>
60 );
61 }
62}
With just a few lines of code, you now have a recyclable component that deals with both the creation and updating of a record from a database. Of course, the information that is managed will largely depend on the attributes that were specified, as well as the API endpoints to connect to. However, the point of this approach is that you can take advantage of React.js' state management mechanisms to create a form with an interface that reflects an existing schema in the backend. As opposed to other approaches, this is considered more secure since the id
value is never exposed in the user interface.
As a challenge, I purposely left out the logic for fetching a record from the database. Try to see if you can write a method called fetch(id)
with logic that makes a call against /api/v1/people/fetch
and loads the values into the form by calling setState
within fetch(id)
. Ideally, this is the only addition you need as everything else in the form will follow the extracted attributes from the API.
For any questions or concerns, or if you simply want to chat about programming in general, hit me up @happyalampay!