38
Tables are critical for displaying data in web apps. They are often complemented by forms on a different page or on a modal. However, at times, creating a form is hectic when you just need to edit just a column. Hence the need to create editable tables to reduce the hassle of having to build a form for the table. An advantage of this approach is that you also get to improve your app's user experience (UX) by minimizing the interfaces a user has to interact with. You will also be able to load data dynamically populating table rows, ensuring your tables can always have updated data.
In this guide, you will learn to build a simple inventory table with the Unit Price column editable to enable updating the price of a product. The table will source its data from a Mock API, which you can create using this guide: Creating a Mock API in React.
This guide assumes that you are familiar with HTML(table
element), React Hooks, Creating a Mock API, and making API calls using fetch.
Set up the Mock API as explained in my previous guide, Creating a Mock API in React.
When done, replace the content of the db.json with the code below:
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
{ "inventory": [ { "id": 1, "product_name": "Weetabix", "product_category": "Cereal", "unit_price": "501", }, { "id": 2, "product_name": "Colgate Toothpaste", "product_category": "Toiletries", "unit_price": "119", }, { "id": 3, "product_name": "Imperial Leather Soap", "product_category": "Toiletries", "unit_price": "235", }, { "id": 4, "product_name": "Sunlight Detergent", "product_category": "Toiletries", "unit_price": "401", } ] }
To start up your API, run the command below:
1
json-server --watch src/db.json
You should see be able to see your API running with an endpoint, http:/localhost:3000/inventory
.
Use Create-React-App, a scaffold that lets you create React apps with no build configurations.
Ensure you have create-react-app
installed on your machine. If not, you can install it by running the following:
1
npm install -g create-react-app
Once it is installed, to create the app, run the following:
1
npx create-react-app simple-inventory-table
To start your app, run the command:
1
yarn start
Open the App.js file and replace the default content with 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
import React from 'react'; function App() { return ( <div className="container"> <h1>Simple Inventory Table</h1> <table> <thead> <tr> <th>Product Name</th> <th>Product Category</th> <th>Unit Price</th> <th>Action</th> </tr> </thead> <tbody> <tr> <td/> <td/> <td/> <td/> </tr> </tbody> </table> </div> ); } export default App;
Above, you have defined a div with classname container to house the table. You have defined the title of the page.
To populate data, make a GET request to the Mock API URL you created.
Define the API_HOST of your Mock API and the endpoint. This is done outside the App
function:
1 2 3 4 5 6 7 8
import React from 'react'; const API_HOST = "http://localhost:3000"; const INVENTORY_API_URL = `${API_HOST}/inventory`; function App() { ... }
Use React Hooks to define state and lifecycles in the app. Start by defining a state variable to store the data from the Mock API. The default value is set to an empty array.
1 2 3 4 5 6 7 8 9 10
import React, {useState} from 'react'; const API_HOST = "http://localhost:3000"; const INVENTORY_API_URL = `${API_HOST}/inventory`; function App() { // data state variable defaulted to an empty array const [data, setData] = useState([]); ... }
Using fetch, define a function to make a GET request to your Mock API. The setData
function is used to update the data state variable on getting a successful response from the Mock API.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
import React, {useState} from 'react'; const API_HOST = "http://localhost:3000"; const INVENTORY_API_URL = `${API_HOST}/inventory`; function App() { const [data, setData] = useState([]); // GET request function to your Mock API const fetchInventory = () => { fetch(`${INVENTORY_API_URL}`) .then(res => res.json()) .then(json => setData(json)); } ... }
Call the function on component mount to get the inventory data to populate the table.
To dynamically create the rows, iterate through the data and use the .map()
function.
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
import React, {useEffect, useState} from 'react'; const API_HOST = "http://localhost:3000"; const INVENTORY_API_URL = `${API_HOST}/inventory`; function App() { const [data, setData] = useState([]); // GET request function to your Mock API const fetchInventory = () => { fetch(`${INVENTORY_API_URL}`) .then(res => res.json()) .then(json => setData(json)); } // Calling the function on component mount useEffect(() => { fetchInventory(); }, []); return ( <div className="container"> <h1>Simple Inventory Table</h1> <table> <thead> <tr> <th>Product Name</th> <th>Product Category</th> <th>Unit Price</th> <th>Action</th> </tr> </thead> <tbody> { data.map((item) => ( <tr key={item.id}> <td>{item.product_name}</td> <td>{item.product_category}</td> <td>{item.unit_price}</td> <td/> </tr> )) } </tbody> </table> </div> ); } export default App;
Define a state variable inEditMode
to track the edit status. This is an object with two properties:
status
: A Boolean that shows whether the table is in edit mode or not.rowKey
: The ID of the product being edited and indicates which row on the table is being edited.Define a state variable unitPrice
to hold the unit price of the row being edited.
Define a function onEdit
that takes in an object as an argument. The object has two properties:
id
: The ID of the product of the row being edited.currentUnitPrice
: The current unit price of the product(row being edited), which is used to set the unitPrice state variable that initializes the input visible on edit mode.
Define a function onCancel
, which resets the inEditMode
and unitPrice
state variables.
Define a function updateInventory
that takes in an object as an argument. The object has two properties:
id
: The ID of the product of the row being edited.newUnitPrice
: The updated unit price of the product(row being edited).
The function sends a PATCH request to our Mock API, updating it with the new unit price.
On successful update, the inEditMode
and unitPrice
state variables are reset and the updated list of inventory fetched.Define a function onSave
that takes in an object as an argument. The object has two properties:
id
: The ID of the product of the row being edited.newUnitPrice
: The updated unit price of the product(row being edited).
The function calls the updateInventory
function to update the unit price.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
import React, {useEffect, useState} from 'react'; const API_HOST = "http://localhost:3000"; const INVENTORY_API_URL = `${API_HOST}/inventory`; function App() { ... const [inEditMode, setInEditMode] = useState({ status: false, rowKey: null }); const [unitPrice, setUnitPrice] = useState(null); /** * * @param id - The id of the product * @param currentUnitPrice - The current unit price of the product */ const onEdit = ({id, currentUnitPrice}) => { setInEditMode({ status: true, rowKey: id }) setUnitPrice(currentUnitPrice); } /** * * @param id * @param newUnitPrice */ const updateInventory = ({id, newUnitPrice}) => { fetch(`${INVENTORY_API_URL}/${id}`, { method: "PATCH", body: JSON.stringify({ unit_price: newUnitPrice }), headers: { "Content-type": "application/json; charset=UTF-8" } }) .then(response => response.json()) .then(json => { // reset inEditMode and unit price state values onCancel(); // fetch the updated data fetchInventory(); }) } /** * * @param id -The id of the product * @param newUnitPrice - The new unit price of the product */ const onSave = ({id, newUnitPrice}) => { updateInventory({id, newUnitPrice}); } const onCancel = () => { // reset the inEditMode state value setInEditMode({ status: false, rowKey: null }) // reset the unit price state value setUnitPrice(null); } ... } export default App;
The final code should look like this:
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
import React, {useEffect, useState} from 'react'; const API_HOST = "http://localhost:3000"; const INVENTORY_API_URL = `${API_HOST}/inventory`; function App() { const [data, setData] = useState([]); const fetchInventory = () => { fetch(`${INVENTORY_API_URL}`) .then(res => res.json()) .then(json => setData(json)); } useEffect(() => { fetchInventory(); }, []); const [inEditMode, setInEditMode] = useState({ status: false, rowKey: null }); const [unitPrice, setUnitPrice] = useState(null); /** * * @param id - The id of the product * @param currentUnitPrice - The current unit price of the product */ const onEdit = ({id, currentUnitPrice}) => { setInEditMode({ status: true, rowKey: id }) setUnitPrice(currentUnitPrice); } /** * * @param id * @param newUnitPrice */ const updateInventory = ({id, newUnitPrice}) => { fetch(`${INVENTORY_API_URL}/${id}`, { method: "PATCH", body: JSON.stringify({ unit_price: newUnitPrice }), headers: { "Content-type": "application/json; charset=UTF-8" } }) .then(response => response.json()) .then(json => { // reset inEditMode and unit price state values onCancel(); // fetch the updated data fetchInventory(); }) } /** * * @param id -The id of the product * @param newUnitPrice - The new unit price of the product */ const onSave = ({id, newUnitPrice}) => { updateInventory({id, newUnitPrice}); } const onCancel = () => { // reset the inEditMode state value setInEditMode({ status: false, rowKey: null }) // reset the unit price state value setUnitPrice(null); } return ( <div className="container"> <h1>Simple Inventory Table</h1> <table> <thead> <tr> <th>Product Name</th> <th>Product Category</th> <th>Unit Price</th> <th>Action</th> </tr> </thead> <tbody> { data.map((item) => ( <tr key={item.id}> <td>{item.product_name}</td> <td>{item.product_category}</td> <td> { inEditMode.status && inEditMode.rowKey === item.id ? ( <input value={unitPrice} onChange={(event) => setUnitPrice(event.target.value)} /> ) : ( item.unit_price ) } </td> <td> { inEditMode.status && inEditMode.rowKey === item.id ? ( <React.Fragment> <button className={"btn-success"} onClick={() => onSave({id: item.id, newUnitPrice: unitPrice})} > Save </button> <button className={"btn-secondary"} style={{marginLeft: 8}} onClick={() => onCancel()} > Cancel </button> </React.Fragment> ) : ( <button className={"btn-primary"} onClick={() => onEdit({id: item.id, currentUnitPrice: item.unit_price})} > Edit </button> ) } </td> </tr> )) } </tbody> </table> </div> ); } export default App;
In the table, you added a ternary operator on the Unit Price
and Action
columns.
In the Unit Price
column, when in edit mode, an input is visible to enable editing of the unit price. Otherwise, the unit price value is visible.
There you have it. A table sourcing data from a Mock API that you used to dynamically create the rows. You also learned how to make a column editable. As a supplement to a previous guide, you made a PATCH request to the Mock API previously created. To build on the knowledge gained in this guide, further explore how to extend your Mock APIs using json-server.
38