This is an example of a ReactJS front end browser app. The GUI has a HTML table with data for performing Create Read Update and Delete (CRUD) operations. Each row in the table represents an item with properties - name and quantity.
Create a react project using the terminal command:
npx create-react-app items-app
This creates a project folder items-app
.
In the project's src
folder, delete all files.
Add the following two files into that folder - index.js
and index.css
.
Start the react app:
cd items-app
npm start
:root {
--dark: #787878;
--light: #eeeeee;
}
body {
font-family: Helevetica, sans-serif;
font-size: 14px;
color: var(--dark);
margin: 5px;
}
h1 {
color: var(--light);
background-color: var(--dark);
padding: 8px 0px;
width: 50%;
margin-left: 25%;
}
.centering {
text-align: center;
}
#table-wrapper {
height: 220px;
width: 50%;
margin: auto;
border-bottom: 1px solid var(--dark);
padding-bottom: 10px;
overflow-y: auto;
}
table {
width: 100%;
}
thead th {
position: sticky;
top: 0;
background-color: var(--dark);
color: var(--light);
}
td, th {
padding: 6px;
}
.tqty {
width: 20%;
}
.tradio {
width: 15%;
accent-color: var(--dark);
}
.button-grp {
margin: 10px 0px 20px 0px;
}
button {
padding: 8px 14px;
color: var(--light);
background-color: var(--dark);
border-radius: 3px;
border-style: none;
cursor: pointer;
}
button:hover {
background-color: #343434;
}
.labels {
font-weight: bold;
}
.textinput {
padding: 5px;
margin-left: 5px;
color: var(--dark);
border: 1px solid var(--light);
}
#status-label {
margin-left: 25%;
}
.status, .statusErr {
font-weight: normal;
color: var(--dark);
margin-left: 5px;
}
.statusErr {
color: red;
}
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
const INITIAL_DATA = [
{ name: "Paper", quantity: 20 },
{ name: "Pencils", quantity: 6 },
{ name: "Paper clips", quantity: 100 }
];
class StatusLabel extends React.Component {
render() {
const clazz = (this.props.status.type === "err") ? "statusErr" : "status";
return (
<div id="status-label" className="labels">
Status: <span className={clazz}>{this.props.status.msg}</span>
</div>
);
}
}
class ButtonsBar extends React.Component {
render() {
return (
<div className="centering button-grp">
<button type="button"
title="Add new row to the table from the text inputs"
onClick={this.props.addBtnClick}>Add</button>
<button type="button"
title="Update selected row"
onClick={this.props.updateBtnClick}>Update</button>
<button type="button"
title="Delete selected row"
onClick={this.props.deleteBtnClick}>Delete</button>
<span style={{ marginLeft: "5px" }}></span>
<button type="button"
title="Clear selection and text inputs"
onClick={this.props.clearBtnClick}>Clear</button>
<button type="button"
title="Navigate to first row"
onClick={this.props.navBtnClick.bind(this, "first")}>First</button>
<button type="button"
title="Navigate to last row"
onClick={this.props.navBtnClick.bind(this, "last")}>Last</button>
</div>
);
}
}
class TextInputs extends React.Component {
render() {
return (
<div className="centering">
<label className="labels">
Name: <input type="text"
className="textinput"
placeholder="Enter name (required)"
value={this.props.name}
onChange={this.props.handleNameChange} />
</label>
<span style={{ marginLeft: "30px" }}></span>
<label className="labels">
Quantity: <input type="number"
className="textinput"
placeholder="Enter quantity (required)"
value={this.props.quantity}
onChange={this.props.handleQtyChange} />
</label>
</div>
);
}
}
class DataTable extends React.Component {
render() {
const rowStyle = { color: "var(--dark)", backgroundColor: "white" };
const rowStyleSelect = { color: "#2a2a2a", backgroundColor: "var(--light)" };
const tableRows = this.props.data.map((r, i) =>
<tr key={i} style={this.props.rowIx === i ? rowStyleSelect : rowStyle}>
<td>{r.name}</td>
<td className="tqty centering">{r.quantity}</td>
<td className="tradio centering">
<input type="radio"
name="select"
onChange={() => {/* No Implementation */}}
onClick={this.props.radioClick.bind(this, i)}
checked={this.props.rowIx === i} />
</td>
</tr>
);
return (
<div id="table-wrapper">
<table>
<thead>
<tr>
<th>Name</th>
<th>Quantity</th>
<th>Select</th>
</tr>
</thead>
<tbody>
{tableRows}
</tbody>
</table>
</div>
);
}
}
const Heading = () => {
return (
<h1 className="centering">Items App</h1>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
rowIx: null,
name: "",
quantity: "",
status: { msg: "" }
}
this.addBtnClick = this.addBtnClick.bind(this);
this.updateBtnClick = this.updateBtnClick.bind(this);
this.deleteBtnClick = this.deleteBtnClick.bind(this);
this.clearBtnClick = this.clearBtnClick.bind(this);
this.navBtnClick = this.navBtnClick.bind(this);
this.radioClick = this.radioClick.bind(this);
this.handleNameChange = this.handleNameChange.bind(this);
this.handleQtyChange = this.handleQtyChange.bind(this);
}
addBtnClick() {
if (!this.state.name || !this.state.quantity) {
this.setState({
status: {
msg: "Name and quantity are required fields!",
type: "err"
}
});
return;
}
const input = {
name: this.state.name,
quantity: this.state.quantity
};
this.setState(prevState => ({
data: [ input, ...prevState.data ],
status: { msg: "New item added." },
rowIx: 0
}));
}
updateBtnClick() {
if (this.state.rowIx === null ||
this.state.data.length <= 0 ||
this.state.name !== this.state.data[this.state.rowIx].name ||
!this.state.quantity) {
this.setState({
status: {
msg: "Select a row. Enter a valid quantity and name cannot be changed!",
type: "err"
}});
return;
}
const updateData = { quantity: this.state.quantity };
this.setState(prevState => ({
data: ((arr, ix, input) => {
const item = arr.slice(ix, ix+1);
arr.splice(ix, 1, Object.assign(item[0], input));
return arr;
})(prevState.data, prevState.rowIx, updateData),
status: { msg: "Updated the item quantity." }
}));
}
deleteBtnClick() {
if (this.state.rowIx === null || this.state.data.length <= 0) {
this.setState({
status: {
msg: "Select a row to delete!",
type: "err"
}
});
return;
}
this.setState(prevState => ({
data: ((arr, ix) => {
arr.splice(ix, 1);
return arr;
})(prevState.data, prevState.rowIx),
status: { msg: "Item deleted." },
name: "",
quantity: "",
rowIx: null
}));
}
clearBtnClick() {
this.setState({
name: "",
quantity: "",
rowIx: null,
status: { msg: "" }
});
}
navBtnClick(navPos) {
this.setState(prevState => ({
rowIx: (navPos === "last") ? prevState.data.length - 1 : 0
}),
() => {
const ele = document.getElementById("table-wrapper");
const bucketHt = ele.clientHeight;
const itemsInBucket =
ele.clientHeight / (ele.scrollHeight / this.state.data.length);
const targetBucket = (this.state.rowIx + 1) / itemsInBucket;
ele.scrollTop = (bucketHt * (targetBucket - 1)) + (bucketHt / 2);
this.radioClick(this.state.rowIx);
});
}
radioClick(ix) {
this.setState(prev => ({
rowIx: ix,
name: (prev.data.length > 0) ? prev.data[ix]['name'] : "",
quantity: (prev.data.length > 0) ? prev.data[ix]['quantity'] : "",
status: {
msg: (prev.data.length > 0) ? ("Selected row " + ++ix) : ""
}
}));
}
handleNameChange(ev) {
this.setState({ name: ev.target.value });
}
handleQtyChange(ev) {
this.setState({ quantity: ev.target.value });
}
componentDidMount() {
this.setState({
data: INITIAL_DATA,
status: { msg: "Items data loaded." }
});
}
render() {
return (
<div>
<Heading />
<br />
<TextInputs
handleNameChange={this.handleNameChange}
handleQtyChange={this.handleQtyChange}
name={this.state.name}
quantity={this.state.quantity}
/>
<br />
<DataTable
data={this.state.data}
rowIx={this.state.rowIx}
radioClick={this.radioClick}
/>
<br />
<ButtonsBar
addBtnClick={this.addBtnClick}
updateBtnClick={this.updateBtnClick}
deleteBtnClick={this.deleteBtnClick}
clearBtnClick={this.clearBtnClick}
navBtnClick={this.navBtnClick}
/>
<br />
<StatusLabel status={this.state.status} />
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById("root")
);