a sleepy programmer's blog

notes and stuff.

Creating a Simple Application Using React

In the previous post, we looked at the basic concepts in React. In this post, I would like to explain and demonstrate identifying and creating components with a simple example app.

We would be creating a simple React app as below: images

Here, we have 3 main sections: the summary of all the transactions, the form to enter the transaction details and the table displaying the transactions. So, we can say that we have 3 components. If the component becomes complex, we can futher break it down to multiple components. For now, this is enough for us to get started. Since all the 3 components are on the same level, we will hold them collectively held under a component(say TransactionsDetails)

So, the hierarchy of components is going to be as

  • TransactionsDetails
    • TransactionsSummary
    • TransactionForm
    • TransactionsTable
      • TransactionRow

For sake of simplicity, we will integrate Bootstrap after the entire app is up and running.

The basic HTML page is as below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html>
  <head>
    <meta charset='UTF-8' />
    <title>React Rocks!!!</title>
    <script src='build/react.js'></script>
    <script src='build/react-dom.js'></script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js'></script>
    <script src='https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js'></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
  </head>
  <body>
    <div id='container'></div>
    <script type='text/babel'>
    </script>
  </body>
</html>

First, we instruct React to render the base component(TransactionsDetails) at ‘#container’.

1
2
3
4
5
6
7
8
9
var TransactionsDetails = React.createClass({
  render: function(){
    return(
      null
    );
  }
});

ReactDOM.render( <TransactionsDetails />, document.getElementById('container'));

Then, we add the child components: TransactionsSummary, TransactionForm & TransactionsTable. We are displaying TransactionsSummary & TransactionForm adjacent to each other. Below them, TransactionsTable is displayed.

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
var TransactionsSummary = React.createClass({
  render: function(){
    return (null);
  }
});

var TransactionForm = React.createClass({
  render: function(){
    return (null);
  }
});

var TransactionsTable = React.createClass({
  render: function(){
    return (null);
  }
});

var TransactionsDetails = React.createClass({
  render: function(){
    return (
      <div className="container">
        <div className='row'>
          <div className="col-md-3">
            <TransactionsSummary />
          </div>
          <div className="col-md-3">
            <TransactionForm />
          </div>
        </div>
        <div className='row'>
          <div className="col-md-6">
            <TransactionsTable />
          </div>
        </div>
      </div>
    );
  }
});

Now, lets start adding details for each component. I prefer displaying static data. Later, when all the components are in place and the static data is displayed properly, I start making changes for dynamic data.

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
var TransactionsSummary = React.createClass({
  render: function(){
    return(
      <ul>
        <li>Transactions: 2</li>
        <li>Debits: 500</li>
        <li>Credits: 600</li>
        <li>Balance: 100</li>
        <li>Total: 1100</li>
      </ul>
    );
  }
});

var TransactionForm = React.createClass({
  render: function(){
    return (
      <form>
        Date: <input type='text' name='date' /><br />
        Reason: <input type='text' name='reason' /><br />
        Amount: <input type='integer' name='amount' /><br />
        <button type='submit'>Submit</button>
        <button type='button'>Cancel</button>
      </form>
    );
  }
});

var TransactionsTable = React.createClass({
  render: function(){
    return (
      <table>
        <thead>
          <tr>
            <th> Date </th>
            <th> Reason </th>
            <th> Amount </th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td> 31-05-2016 </td>
            <td> Salary </td>
            <td> 600 </td>
          </tr>
          <tr>
            <td> 01-06-2016 </td>
            <td> Party </td>
            <td> -500 </td>
          </tr>
        </tbody>
      </table>
    );
  }
});

Now that the components skeleton is in place, lets start making changes for dynamic data. Lets fix the TransactionForm first. Since, the TransactionForm is the only place in the application from where data is going to be inserted, lets add states date, reason and amount to the TransactionForm component.

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
var TransactionForm = React.createClass({
  getInitialState: function(){
    return { date: '', reason: '', amount: '' };
  },

  handleChange: function(e){
    switch(e.target.name){
      case 'date':
        this.setState({date: e.target.value});
        break;
      case 'reason':
        this.setState({reason: e.target.value});
        break;
      case 'amount':
        this.setState({amount: e.target.value});
        break;
    }
  },

  handleSubmit: function(e){
    e.preventDefault();
  },

  handleCancel: function(e){
    e.preventDefault();
    this.setState(this.getInitialState());
  },

  render: function(){
    return (
      <form onSubmit={this.handleSubmit}>
        Date: <input type='text' name='date' value={this.state.date} onChange={this.handleChange}/><br />
        Reason: <input type='text' name='reason' value={this.state.reason} onChange={this.handleChange}/><br />
        Amount: <input type='integer' name='amount' value={this.state.amount} onChange={this.handleChange}/><br />
        <button type='submit'>Submit</button>
        <button type='button' onClick={this.handleCancel}>Cancel</button>
      </form>
    );
  }
});

Now that we have added the basic states to the TransactionForm and have functions to update form states when user enters data, we need to add logic to accept the form submit functionality. Here, after accepting the form data, we need to save the record in the TransactionsDetails state since TransactionsSummary and TransactionsTable are the components that are going to refer to this state to update themselves.

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
var TransactionForm = React.createClass({
  handleSubmit: function(e){
    e.preventDefault();
    this.props.handleNewRecord(this.state);
    this.setState(this.getInitialState());
  }
});

var TransactionsDetails = React.createClass({
  getInitialState: function(){
    return { records: [] };
  },

  addRecord: function(record){
    var records = this.state.records;
    records.push(record);
    this.setState({ records: records });
  },

  render: function(){
    return (
      ...
      <TransactionForm handleNewRecord={this.addRecord}/>
      ...
    );
  }
});

Since the form is working and the entered transactions are being saved in TransactionsDetails’s state.records, we move forward by displaying the dynamically added records in the table. For this, we introduce a new component named TransactionRow.

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
var TransactionRow = React.createClass({
  render: function(){
    return (
      <tr>
        <td>{this.props.record.date}</td>
        <td>{this.props.record.reason}</td>
        <td>{this.props.record.amount}</td>
      </tr>
    );
  }
});

var TransactionsTable = React.createClass({
  render: function(){
    var rows = [];
    this.props.records.map(function(record, index){
      rows.push(<TransactionRow key={index} record={record} />);
    }.bind(this));
    return (
      <table>
        <thead>
          <tr>
            <th> Date </th>
            <th> Reason </th>
            <th> Amount </th>
          </tr>
        </thead>
        <tbody>
          { rows }
        </tbody>
      </table>
    );
  }
});

var TransactionsDetails = React.createClass({
  render: function(){
    return (
      ...
      <TransactionsTable records={this.state.records}/>
      ...
    );
  }
});

Now that we have the form and the table working, the only section remaining is the TransactionsSummary. Here, we are neither going to pass the state of TransactionsDetails but are passing the return values of functions.

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
var TransactionsSummary = React.createClass({
  render: function(){
    return(
      <ul>
        <li>Transactions: {this.props.tCount}</li>
        <li>Debits: 500</li>
        <li>Credits: 600</li>
        <li>Balance: 100</li>
        <li>Total: 1100</li>
      </ul>
    );
  }
});


var TransactionsDetails = React.createClass({
  transactionsCount: function(){
    return this.state.records.length;
  },

  render: function(){
    return (
      ...
      <TransactionsSummary tCount={this.transactionsCount()}/>
      ...
    );
  }
});

Similarly, we implement the credits, debits, balance and total transactions sumation as

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
var TransactionsSummary = React.createClass({
  render: function(){
    return(
      <ul>
        <li>Transactions: {this.props.tCount}</li>
        <li>Debits: {this.props.debits}</li>
        <li>Credits: {this.props.credits}</li>
        <li>Balance: {this.props.balance}</li>
        <li>Total: {this.props.total}</li>
      </ul>
    );
  }
});

var TransactionsDetails = React.createClass({
  debits: function(){
    var sum = 0;
    var records = this.state.records.filter(function(record){ return record.amount < 0});
    if(records.length > 0){
      sum = records.reduce((function(a, b) {
        return a + parseFloat(b.amount);
      }), 0);
    }
    return -sum;
  },

  credits: function(){
    var sum = 0;
    var records = this.state.records.filter(function(record){ return record.amount >= 0});
    if(records.length > 0){
      sum = records.reduce((function(a, b) {
        return a + parseFloat(b.amount);
      }), 0);
    }
    return sum;
  },

  balance: function(){
    return this.credits() - this.debits();
  },

  totalTransactions: function(){
    return this.credits() + this.debits();
  },

  render: function(){
    return (
      ...
      <TransactionsSummary tCount={this.transactionCount()} debits={this.debits()} credits={this.credits()}
      balance={this.balance()} total={this.totalTransactions()}/>
      ...
    );
  }
});

Since we have a working example, lets integrate bootstaps for better look and feel. So, our updated code is as

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
var TransactionsSummary = React.createClass({
  render: function(){
    return(
      <ul className="list-group">
        <li className="list-group-item">
          <span className="badge">{this.props.tCount}</span>
          Count
        </li>
        <li className="list-group-item">
          <span className="badge">{this.props.debits}</span>
          Debits
        </li>
        <li className="list-group-item">
          <span className="badge">{this.props.credits}</span>
          Credits
        </li>
        <li className="list-group-item">
          <span className="badge">{this.props.balance}</span>
          Balance
        </li>
        <li className="list-group-item">
          <span className="badge">{this.props.total}</span>
          Total
        </li>
      </ul>
    );
  }
});

var TransactionForm = React.createClass({
  getInitialState: function(){
    return { date: '', reason: '', amount: '' };
  },

  handleChange: function(e){
    switch(e.target.name){
      case 'date':
        this.setState({date: e.target.value});
        break;
      case 'reason':
        this.setState({reason: e.target.value});
        break;
      case 'amount':
        this.setState({amount: e.target.value});
        break;
    }
  },

  handleSubmit: function(e){
    e.preventDefault();
    this.props.handleNewRecord(this.state);
    this.setState(this.getInitialState());
  },

  handleCancel: function(e){
    e.preventDefault();
    this.setState(this.getInitialState());
  },

  render: function(){
    return (
      <form onSubmit={this.handleSubmit} className="form-horizontal">
        <div className="form-group">
          <label for="date" className="col-sm-2 control-label">Date: </label>
          <div className="col-sm-10">
            <input type='text' name='date' className="form-control" value={this.state.date} onChange={this.handleChange}/>
          </div>
        </div>
        <div className="form-group">
          <label for="reason" className="col-sm-2 control-label">Reason: </label>
          <div className="col-sm-10">
            <input type='text' name='reason' className="form-control" value={this.state.reason} onChange={this.handleChange}/>
          </div>
        </div>
        <div className="form-group">
          <label for="amount" className="col-sm-2 control-label">Amount: </label>
          <div className="col-sm-10">
            <input type='integer' name='amount' className="form-control" value={this.state.amount} onChange={this.handleChange}/>
          </div>
        </div>
        <div className="form-group">
          <div className="col-sm-offset-2 col-sm-10">
            <button className="btn btn-primary" type='submit'>Submit</button>
            <button className="btn" type='button' onClick={this.handleCancel}>Cancel</button>
          </div>
        </div>
      </form>
    );
  }
});

var TransactionRow = React.createClass({
  render: function(){
    return (
      <tr>
        <td>{this.props.record.date}</td>
        <td>{this.props.record.reason}</td>
        <td>{this.props.record.amount}</td>
      </tr>
    );
  }
});

var TransactionsTable = React.createClass({
  render: function(){
    var rows = [];
    this.props.records.map(function(record, index){
      rows.push(<TransactionRow key={index} record={record} />);
    }.bind(this));
    return (
      <table className="table table-striped table-hover table-bordered">
        <thead>
          <tr>
            <th> Date </th>
            <th> Reason </th>
            <th> Amount </th>
          </tr>
        </thead>
        <tbody>
          { rows }
        </tbody>
      </table>
    );
  }
});

var TransactionsDetails = React.createClass({
  getInitialState: function(){
    return { records: [] };
  },

  addRecord: function(record){
    var records = this.state.records;
    records.push(record);
    this.setState({ records: records });
  },

  transactionCount: function(){
    return this.state.records.length;
  },

  debits: function(){
    var sum = 0;
    var records = this.state.records.filter(function(record){ return record.amount < 0});
    if(records.length > 0){
      sum = records.reduce((function(a, b) {
        return a + parseFloat(b.amount);
      }), 0);
    }
    return -sum;
  },

  credits: function(){
    var sum = 0;
    var records = this.state.records.filter(function(record){ return record.amount >= 0});
    if(records.length > 0){
      sum = records.reduce((function(a, b) {
        return a + parseFloat(b.amount);
      }), 0);
    }
    return sum;
  },

  balance: function(){
    return this.credits() - this.debits();
  },

  totalTransactions: function(){
    return this.credits() + this.debits();
  },

  render: function(){
    return (
      <div className="container">
        <div className='row'>
          <div className="col-md-offset-2 col-md-4">
            <div className="well">
              <TransactionsSummary tCount={this.transactionCount()} debits={this.debits()} credits={this.credits()}
              balance={this.balance()} total={this.totalTransactions()}/>
            </div>
          </div>
          <div className="col-md-4">
            <div className="well">
              <TransactionForm handleNewRecord={this.addRecord}/>
            </div>
          </div>
        </div>
        <div className='row'>
          <div className="col-md-offset-2 col-md-8">
            <div className="well">
              <TransactionsTable records={this.state.records}/>
            </div>
          </div>
        </div>
      </div>
    );
  }
});

Now that we have a working example that looks good, lets try to add the edit and delete options to alter the transactions. For this, we would be displaying the ‘Edit’ & ‘Delete’ buttons for each TransactionRow. When clicked on ‘Delete’ it should remove the record from table. When clicked ‘Edit’ it should load the transaction in TransactionForm for editing.

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
var TransactionRow = React.createClass({
  handleDelete: function(e){
    e.preventDefault();
    this.props.handleDeleteRecord(this.props.record);
  },

  render: function(){
    return (
      <tr>
        <td>{this.props.record.date}</td>
        <td>{this.props.record.reason}</td>
        <td>{this.props.record.amount}</td>
        <td>
          <button className="btn btn-primary" >Edit</button>
          <button className="btn btn-danger" onClick={this.handleDelete}>Delete</button>
        </td>
      </tr>
    );
  }
});

var TransactionsTable = React.createClass({
  render: function(){
    var rows = [];
    this.props.records.map(function(record, index){
      rows.push(<TransactionRow key={index} record={record} handleDeleteRecord={this.props.handleDeleteRecord}/>);
    }.bind(this));
    return (
      ...
    );
  }
});

var TransactionsDetails = React.createClass({
  getInitialState: function(){
    return { records: [
      { date: '1-6-2016', reason: 'Salary', amount: 1000},
      { date: '2-6-2015', reason: 'EMI', amount: -400}
    ] };
  },

  deleteRecord: function(record){
    var records = this.state.records;
    var index  = records.indexOf(record);
    records.splice(index, 1);
    this.setState({ records: records });
  },

  render: function(){
    return (
      <TransactionsTable records={this.state.records} handleDeleteRecord={this.deleteRecord}/>
    );
  }
});

Adding the functionality to edit the transaction record is going to be tricky because we need to inform another sibling component (TransactionForm) that it needs to load data for a selected record. We need to identify whether a record being editted in the form is new or existing. Hence, we add a new state editing to TransactionForm which will be true if a record is being edited. Also, we add a state recordToEdit to TransactionsDetails. It points to record to be editted when ‘Edit’ is clicked.

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
var TransactionForm = React.createClass({
  getInitialState: function(){
    return { record: { date: '', reason: '', amount: '' }, editing: false };
  },

  componentWillReceiveProps: function(nextProps) {
    this.setState({ record: nextProps.recordToEdit, editing: true });
  },
});

var TransactionRow = React.createClass({
  handleEdit: function(e){
    e.preventDefault();
    this.props.handleEditRecord(this.props.record);
  },

  render: function(){
    return (
      ...
      <button className="btn btn-primary" onClick={this.handleEdit} >Edit</button>
      ...
    )
  };
});

var TransactionsTable = React.createClass({
  render: function(){
    var rows = [];
    this.props.records.map(function(record, index){
      rows.push(<TransactionRow key={index} record={record} handleDeleteRecord={this.props.handleDeleteRecord}
      handleEditRecord={this.props.handleEditRecord}/>);
    }.bind(this));
    return (
    ...
    );
  }
});

var TransactionsDetails = React.createClass({
  getInitialState: function(){
    return {
      records: [ { date: '1-6-2016', reason: 'Salary', amount: 1000}, { date: '2-6-2015', reason: 'EMI', amount: -400} ],
      recordToEdit: { date: '', reason: '', amount: '' }
    };
  },

  editRecord: function(record){
    this.setState({ recordToEdit: record });
  },

  render: function(){
    return (
      ...
      <TransactionForm handleNewRecord={this.addRecord} recordToEdit={this.state.recordToEdit}/>
      <TransactionsTable records={this.state.records} handleDeleteRecord={this.deleteRecord}
        handleEditRecord={this.editRecord}/>
      ...
    );
  }
});

The function componentWillReceiveProps is a lifecycle function provided by React. It is called when the component is being rerendered. It has the updated props that are passed as arguments to the function and can be accessed before the component is rerendered.

Now, the last functionality that we need to add is updating a TransactionsTable. We have already implemented to load a transaction in the form so that it can be updated.

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
var TransactionForm = React.createClass({
  handleChange: function(e){
    var record = this.state.record;
    switch(e.target.name){
      case 'date':
        record.date = e.target.value;
        break;
      case 'reason':
        record.reason = e.target.value;
        break;
      case 'amount':
        record.amount = e.target.value;
        break;
    }
    this.setState({ record: record });
  },

  handleSubmit: function(e){
    e.preventDefault();
    if(this.state.editing){
      this.props.handleUpdateRecord(this.state.record);
    } else {
      this.props.handleNewRecord(this.state.record);
    }
    this.setState(this.getInitialState());
  },

  render: function(){
    return (
      <form onSubmit={this.handleSubmit} className="form-horizontal">
        <h4>{this.state.editing ? 'Edit Record' : 'New Record'}</h4>
        ...
    );
  }
});

var TransactionsDetails = React.createClass({
  updateRecord: function(record){
    var records = this.state.records;
    var index = records.indexOf(record);
    records[index] = record;
    this.setState({ records: records, recordToEdit: {} });
  },

  render: function(){
    return (
      <TransactionForm handleNewRecord={this.addRecord} recordToEdit={this.state.recordToEdit}
        handleUpdateRecord={this.updateRecord}/>
    );
  }
});

Finally, we have a simple working application created using React. I know its not perfect and am definitely sure that there are some issues with it. But, our aim was to identitying, creating and linking the components. I hope the post achieved the aim. The source code can be found here.

Hope it was interesting. Critics are welcome.

Comments