Working With Tables in React: How to Render and Edit Fetched Data

For a recent project, my team and I wanted to build a table in our front end (using React) which would hold the data we pulled from the back end (using Ruby and Sinatra to build the database and API paths).

Another post (linked at the end) was extremely helpful in getting the data into our table, but we wanted to be able to edit the table data and have those edits persist using a PATCH request. Given how state works in React and how the React components were organized, this was a task. I thought it would be worth sharing how we incorporated edit capabilities.

Basic Outline of this Post - Jump to What You Need

  • Phase 1: Rendering Data to the Table
    • how to grab data using a GET fetch request
    • how to push data into table rows
  • Phase 2: Building an Edit Feature
    • how to capture the data you want to edit
    • how to create an EditForm component; show/hide as needed
    • how to build the PATCH request and submit changes

A Simple Component Map

Here you can see how the component relationships are defined and what the primary responsibility is for each one. We'll use customers data as an example.
React Component Map

Phase 1: Rendering Data to the Table

Getting Your Data Using Fetch

Below you'll see the first step to take - getting your data using a fetch and saving it to state.

// App.js
import React, { useEffect, useState } from "react";
import Customers from "./Customers";

function App() {
// set state
  const [customers, setCustomers] = useState([]);

// first data grab
  useEffect(() => {
    fetch("http://localhost:9292/customers") // your url may look different
      .then(resp => resp.json())
      .then(data => setCustomers(data)) // set data to state
  }, []);

return (
    <div>
      {/* pass data down to the Customers component where we'll create the table*/}
      <Customers customers={customers} />
    </div>
  );
}
export default App

Pushing Data Into Your Table

Once we have the data passed down to the Customers component, we need to create a table (HTML tag is table) into which the data gets pushed. Inside, you nest a table header (thead) where the column names go (th), as well as a table body (tbody) where the rows of data will go (tr).

// Customers.js
import React from 'react'
import Customer from './Customer'

function Customers({customers}) {

  return (
        <table>
          <thead>
            <tr>
              <th>Customer ID</th>
              <th>Name</th>
              <th>Email</th>
              <th>Phone</th>
              <th>Modify Customer</th> // where you'll put the edit button
            </tr>
          </thead>
          <tbody>
            {/* iterate through the customers array and render a unique Customer component for each customer object in the array */}
            { customers.map(customer => <Customer key={customer.id} customer={customer} />) }
          </tbody>
        </table>
  )
}

export default Customers

When formatting the Customer component, it is very important that you mind what kinds of HTML tags you use. Since Customer will manifest inside of a table, your top level HTML element needs to be a table row (tr).

// Customer.js
import React from 'react'

// deconstructed props
function Customer({customer:{id, name, email, phone} }) {

  return (
        <tr key={id}>
            <td>{id}</td>
            <td>{name}</td>
            <td>{email}</td>
            <td>{phone}</td>
            <td><button>Edit</button></td>
        </tr>
  )
}
export default Customer

At this point, we successfully have data rendering in a table. Now for the fun part: creating an edit feature!

Phase 2: Building an Edit Feature

In order to edit our table data in a way that persists to the database, we have to build a few new functions across multiple components.

Our concerns include:

  • capturing the customer data we want to edit
  • creating the EditCustomer component; show/hide as needed
  • passing captured customer data to the edit form
  • building the PATCH request to submit the changes
  • auto-updating the customer data in the browser

Due to the complexity of the task, I'll start with a simple description of each. Then, I'll include fully updated code blocks for each component. It is possible some of the code could be written more efficiently, but I tried to aim more for clarity.

Capture the customer's data

We'll need input fields in which to make changes, but it will need to happen outside of the table. Input tabs cause errors inside of a table, so we need to pass the data captured in Customer up to Customers where it will then get passed back down to the EditCustomer component.

Create the EditCustomer component; conditionally render

Keeping with React best practices, EditCustomer should be a separate component. The component will hold a form, as well as the PATCH request function.

You can conditionally render this component using state. See more about this skill in a previous post of mine here.

Pass captured customer to EditCustomer

Using state defined in Customers, you can save the customer data captured, then pass it down to the edit form. This is doubly helpful when submitting the PATCH request.

Build the PATCH request to ensure persistence

The PATCH request will live in the EditCustomer component and will submit the state that holds the captured customer's data (as well as any changes we've made to it).

Automatically render the updated data

We'll need a function in the same component where customer state is defined (in App), then pass that function down as props to the needed components and dependent functions. This function will automatically render the updated data to the page.

Component Code Blocks

App

import React, { useEffect, useState } from "react";
import Customers from "./Customers";

function App() {
// set state
  const [customers, setCustomers] = useState([]);

// first data grab
  useEffect(() => {
    fetch("http://localhost:9292/customers")
      .then((resp) => resp.json())
      .then((data) => {
        setCustomers(data)
      });
  }, []);

// update customers on page after edit
  function onUpdateCustomer(updatedCustomer) {
    const updatedCustomers = customers.map(
      customer => {
        if (customer.id === updatedCustomer.id) {
          return updatedCustomer
        } else {return customer}
      }
    )
    setCustomers(updatedCustomers)
  }

  return (
    <div>
      <Customers
        customers={customers}
        onUpdateCustomer={onUpdateCustomer}
      />
    </div>
  );
}
export default App;

Customers

import React, {useState} from 'react'
import Customer from './Customer'
import EditCustomer from './EditCustomer'

function Customers({customers, onUpdateCustomer}) {
// state for conditional render of edit form
  const [isEditing, setIsEditing] = useState(false);
// state for edit form inputs
  const [editForm, setEditForm] = useState({
    id: "",
    name: "",
    email: "",
    phone: ""
  })

// when PATCH request happens; auto-hides the form, pushes changes to display
  function handleCustomerUpdate(updatedCustomer) {
      setIsEditing(false);
      onUpdateCustomer(updatedCustomer);
    }

// capture user input in edit form inputs
  function handleChange(e) {
    setEditForm({
    ...editForm,
    [e.target.name]: e.target.value
    })
  }

// needed logic for conditional rendering of the form - shows the customer you want when you want them, and hides it when you don't
  function changeEditState(customer) {
    if (customer.id === editForm.id) {
      setIsEditing(isEditing => !isEditing) // hides the form
    } else if (isEditing === false) {
      setIsEditing(isEditing => !isEditing) // shows the form
    }
  }

// capture the customer you wish to edit, set to state
  function captureEdit(clickedCustomer) {
    let filtered = customers.filter(customer => customer.id === clickedCustomer.id)
    setEditForm(filtered[0])
  }

  return (
      <div>
        {isEditing?
          (<EditCustomer
            editForm={editForm}
            handleChange={handleChange}
            handleCustomerUpdate={handleCustomerUpdate}
          />) : null}
        <table>
          <thead>
            <tr>
              <th>Customer ID</th>
              <th>Name</th>
              <th>Email</th>
              <th>Phone</th>
              <th>Modify Customer</th>
            </tr>
          </thead>
          <tbody>
              { customers.map(customer =>
                <Customer
                  key={customer.id}
                  customer={customer}
                  captureEdit={captureEdit}
                  changeEditState={changeEditState}
                />) }
          </tbody>
        </table>
      </div>
   )
}
export default Customers

Customer

import React from 'react'

function Customer({customer, customer:{id, name, email, phone}, captureEdit, changeEditState}) {

    return (
        <tr key={id}>
            <td>{id}</td>
            <td>{name}</td>
            <td>{email}</td>
            <td>{phone}</td>
            <td>
                <button
                  onClick={() => {
                    captureEdit(customer);
                    changeEditState(customer)
                  }}
                >
                  Edit
                </button>
            </td>
        </tr>
    )
}
export default Customer

EditCustomer

import React from 'react'

function EditCustomer({ editForm, handleCustomerUpdate, handleChange }) {
    let {id, name, email, phone} = editForm

// PATCH request; calls handleCustomerUpdate to push changes to the page
    function handleEditForm(e) {
        e.preventDefault();
        fetch(`http://localhost:9292/customers/${id}`, {
            method: "PATCH",
            headers: {
                "Content-Type" : "application/json"
            },
            body: JSON.stringify(editForm),
        })
            .then(resp => resp.json())
            .then(updatedCustomer => {
                handleCustomerUpdate(updatedCustomer)})
    }

    return (
        <div>
            <h4>Edit Customer</h4>
            <form onSubmit={handleEditForm}>
                <input type="text" name="name" value={name} onChange={handleChange}/>
                <input type="text" name="email" value={email} onChange={handleChange}/>
                <input type="text" name="phone" value={phone} onChange={handleChange}/>
                <button type="submit">Submit Changes</button>
            </form>
        </div>
    )
}
export default EditCustomer

Conclusion

And there we have it! You can now push data into a table when using React, as well as edit the data in the page.

Reader feedback: Was there a better way? Did I put state in the wrong place? Have some advice to pass down to an early career engineer? Share out in the discussion below!

Did you find this tutorial helpful? Like and follow for more great posts to come!

Resources

28