Search code examples
node.jsreactjsreact-contextmernreact-data-table-component

MERN - delete item/row in React Data Table Component


I have a MERN stack application that is modified from a great tutorial I completed. In the original app, transactions were rendered in a list populated from an API call to Mongo Atlas DB. I converted the list to a react-data-table-component and am now trying to figure out how to delete a table row/transaction. The original app had this as part of the transaction component with an onClick button. When I attempt to use the deleteTransaction function, I receive a "TypeError: Cannot read property '_id' of undefined". I can see that the data table renders via the object {transactions}, but cannot figure out why it does not recognize the _id.

Other info: state is managed through the React Context API, with a Router.js and Reducer.js.

TransactionTable.js

import React, { useContext, useEffect } from "react";

// Data table imports
import IconButton from "@material-ui/core/IconButton";
import DeleteIcon from "@material-ui/icons/Delete";
import Card from "@material-ui/core/Card";
import DataTable from "react-data-table-component";

// import transaction component and context provider
import { GlobalContext } from "../context/GlobalState";

// create data table component
export const TransactionTable = () => {
  const { transactions, getTransactions, deleteTransaction } = useContext(
    GlobalContext
  );

  // react-data-table-component Columns for back-end data
  const columns = [
    {
      name: "Transaction",
      selector: "text",
      sortable: true
    },
    {
      name: "Amount",
      selector: "amount",
      sortable: true,

      // conditionally render amount if positive or negative
      conditionalCellStyles: [
        {
          when: row => row.amount > 0,
          style: {
            color: "green"
          }
        },
        {
          when: row => row.amount < 0,
          style: {
            color: "red"
          }
        }
      ]
        },
        {
// where I'm attempting to pass the transactions prop and apply the deleteTransaction function 
// using the delete button that renders in each row
          cell: ({ transactions }) => (
            <IconButton
              aria-label="delete"
              color="secondary"
              onClick={() => deleteTransaction(transactions._id)}
            >  
              <DeleteIcon />
            </IconButton>
          )
        }
      ];

      useEffect(() => {
        getTransactions();
        // eslint-disable-next-line react-hooks/exhaustive-deps
      }, []);

      return (
        <div>
        <Card style={{ height: "100%" }} p={2} mx="auto">
            <DataTable
              title="Transactions"
              columns={columns}
              data={transactions}
              defaultSortField="Transactions"
              //actions={actions}
              pagination={true}
              highlightOnHover={true}
              dense={true}
            />
          </Card>
        </div>
      );
    };

./controllers/transactions.js - this is where the deleteTransaction function is

const Transaction = require('../models/Transaction');

// @desc    Get all transactions
// @route   GET /api/v1/transactions
// @access  Public
exports.getTransactions = async (req, res, next) => {
  try {
    const transactions = await Transaction.find();
    //const result = result.transaction.toString()

    return res.status(200).json({
      success: true,
      count: transactions.length,
      data: transactions
    });
  } catch (err) {
    return res.status(500).json({
      success: false,
      error: 'Server Error'
    });
  }
}

// @desc    Add transaction
// @route   POST /api/v1/transactions
// @access  Public
exports.addTransaction = async (req, res, next) => {
  try {
    const { text, amount } = req.body;

    const transaction = await Transaction.create(req.body);

    return res.status(201).json({
      success: true,
      data: transaction
    }); 
  } catch (err) {
    if(err.name === 'ValidationError') {
      const messages = Object.values(err.errors).map(val => val.message);

      return res.status(400).json({
        success: false,
        error: messages
      });
    } else {
      return res.status(500).json({
        success: false,
        error: 'Server Error'
      });
    }
  }
}

// @desc    Delete transaction
// @route   DELETE /api/v1/transactions/:id
// @access  Public
exports.deleteTransaction = async (req, res, next) => {
  try {
    const transaction = await Transaction.findById(req.params.id);

    if(!transaction) {
      return res.status(404).json({
        success: false,
        error: 'No transaction found'
      });
    }

    await transaction.remove();

    return res.status(200).json({
      success: true,
      data: {}
    });

  } catch (err) {
    return res.status(500).json({
      success: false,
      error: 'Server Error'
    });
  }
}

Solution

  • According to the docs https://www.npmjs.com/package/react-data-table-component#custom-cells, each cell is passed an object named row by convention (you can name it to whatever you want)..

    This row object should have the _id you need..

    // react-data-table-component Columns for back-end data
    const columns = [
      // ... column items,
      {
        cell: row => (
          <IconButton
            aria-label="delete"
            color="secondary"
            onClick={() => deleteTransaction(row._id)}
          >  
            <DeleteIcon />
          </IconButton>
        )
      }
    ]
    

    Each row basically represents a single transaction.