Author avatar

Raphael Alampay

Load an Existing Database Record to a React.js Form

Raphael Alampay

  • Oct 10, 2020
  • 8 Min read
  • 90 Views
  • Oct 10, 2020
  • 8 Min read
  • 90 Views
Web Development
Front End Web Development
Client-side Frameworks
React

Introduction

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:

  1. We'll be persisting an object that models a person with an id, first_name and last_name.
  2. React.js interacts with a backend API running in http://localhost:3000/api/v1/people/save.
  3. To fetch data for an existing person, we can access an API via a GET request in the endpoint 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.

Creating the Form Component

Create an initial component that maintains the id, first_name, and last_name states reflecting the data model of a person.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import React from 'react';
import $ from 'jquery';

export default class PersonForm extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      id: props.id,
      firstName: "",
      lastName: ""
    }
  }

  updateFirstName(event) {
    this.setState({
      firstName: event.target.value 
    });
  }

  updateLastName(event) {
    this.setState({
      lastName: event.target.value 
    });
  }

  render() {
    return (
      <div>
        First Name:
        <input type="text" value={this.state.firstName} onChange={this.updateFirstName.bind(this)} />
        Last Name:
        <input type="text" value={this.state.lastName} onChange={this.updateLastName.bind(this)} />
        <hr/>

        <button>
          Save
        </button>
      </div>
    );
  }
}
javascript

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.

Interacting with the API

Create a method that will perform a POST to the API endpoint /api/v1/people/save. The method looks like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
save() {
  var context = this;

  $.ajax({
    url: "http://localhost:3000/api/v1/people/save",
    method: "POST",
    data: {
      id: context.state.id,
      first_name: context.state.firstName,
      last_name: context.state.lastName
    },
    success: function(response) {
      alert("Successfully saved record!");
    },
    error: function(response) {
      alert("Error in saving record!");
    }
  });
}
javascript

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
2
3
<button onClick={this.save.bind(this)}>
  Save
</button>
jsx

Rails Server Code

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. Create a new project.
1
2
$ rails new sampleapi
$ cd sampleapi
bash
  1. Create a model for person and load it to the database.
1
2
$ rails g model Person first_name:string last_name:string
$ rake db:migrate
bash
  1. In the file config/routes.rb, create the API route definition.
1
2
3
4
5
6
namepsace :api do
  namespace :v1 do
    get "/people/fetch", to: "people#fetch"
    post "/people/save", to: "people#save"
  end
end
ruby
  1. Create a controller for the API in app/controllers/api/v1/people_controller.rb.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
module Api
  module V1
    class PeopleController < ApplicationController
      protect_from_forgery with: :null_session

      def fetch
        person = Person.find(params[:id])

        render json: person
      end

      def save
        person = Person.find_by_id(params[:id])

        if person.present?
          person.first_name = params[:first_name]
          person.last_name  = params[:last_name]
        else
          person = Person.new(first_name: params[:first_name], last_name: params[:last_name])
        end

        person.save!

        render json: { message: "success", id: person.id }
      end
    end
  end
end
ruby
  1. Run the server and bind it to localhost.
1
$ rails server -b 127.0.0.1
bash

Overall Code

The final code should look like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import React from 'react';
import $ from 'jquery';

export default class PersonForm extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      id: props.id,
      firstName: "",
      lastName: ""
    }
  }

  updateFirstName(event) {
    this.setState({
      firstName: event.target.value 
    });
  }

  updateLastName(event) {
    this.setState({
      lastName: event.target.value 
    });
  }

  save() {
    var context = this;

    $.ajax({
      url: "http://localhost:3000/api/v1/people/save",
      method: "POST",
      data: {
        id: context.state.id,
        first_name: context.state.firstName,
        last_name: context.state.lastName
      },
      success: function(response) {
        alert("Successfully saved record!");
      },
      error: function(response) {
        alert("Error in saving record!");
      }
    });
  }

  render() {
    return (
      <div>
        First Name:
        <input type="text" value={this.state.firstName} onChange={this.updateFirstName.bind(this)} />
        Last Name:
        <input type="text" value={this.state.lastName} onChange={this.updateLastName.bind(this)} />
        <hr/>

        <button onClick={this.save.bind(this)}>
          Save
        </button>
      </div>
    );
  }
}
javascript

Conclusion

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!

2