Skip to content

Instantly share code, notes, and snippets.

@prsaya
Created October 27, 2022 10:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save prsaya/ba03fa356a3c185839011d313c203793 to your computer and use it in GitHub Desktop.
Save prsaya/ba03fa356a3c185839011d313c203793 to your computer and use it in GitHub Desktop.

A ReactJS CRUD Table

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

index.css

: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;
}


index.js

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")
);



Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment