React-Typescript edit list

Viewed 664

I am using React-TypeScript to create a list of employees, edit and delete them. However my edit button does not do anything on click. Any idea on how I can fix this problem?

I have used the same code for the "Delete" function and works perfectly. Following are my two react-typescript files which I have my codes written in and I also have an app.tsx file which all of these are being imported in it.

CreateEmployee.tsx

import React, { useState, FC, ChangeEvent } from "react";
import { Link } from "react-router-dom";
import { IEmployee } from "../../Interfaces";
import EmployeeList from "./EmployeeList";

export const CreateEmployee: FC = () => {
  const [employeeName, setEmployeeName] = useState<string>("");
  const [employeeId, setEmployeeId] = useState<any>("");
  const [employeeList, setEmployeeList] = useState<IEmployee[]>([]);

//   get the data from the input and add it to the state
  const handleChange = (event: ChangeEvent<HTMLInputElement>): void => {
    if (event.target.name === "employeename") {
      setEmployeeName(event.target.value);
    } else {
      setEmployeeId(event.target.value);
    }
  };

// add id and name to the employee list
  const addEmployee = (): void => {
    const newEmployee = {
      employeeId: employeeId,
      employeeName: employeeName,
    };

 setEmployeeList([...employeeList, newEmployee]);
    setEmployeeName("");
    setEmployeeId("");
  };

 // delete employee from the list
  const deleteEmployee = (employeeNameToDelete: string): void => {
    setEmployeeList(
      employeeList.filter(
        (employee) => employee.employeeName !== employeeNameToDelete
      )
    );
  };

// edit employee name and id in the list
  const editEmployee = (
    employeeNameToEdit: string,
    employeeIdToEdit: string
  ): void => {
    setEmployeeList(
      employeeList.map((employee) => {
        if (employee.employeeName === employeeNameToEdit) {
          employee.employeeId = employeeIdToEdit;
        }
        return employee;
      })
    );
  };

 return (


<div>
    <h1>Create Employee</h1>
      <div>
    <label>Employee Id:</label>
    <input
      type="text"
      name="employeeid"
      value={employeeId}
      onChange={(e) => setEmployeeId(e.target.value)}
    />
    <label>Employee Name:</label>
    <input
      type="text"
      name="employeename"
      value={employeeName}
      onChange={(e) => setEmployeeName(e.target.value)}
    />
    <button type="submit" onClick={addEmployee}>
      Submit
    </button>


</div>
  <div>
    <Link to="/employeelist">Employee List</Link>
    <Link to="/engagementList">Engagement List</Link>
  </div>
  <div className="employee-list">
    <h1>Employee List</h1>
    {employeeList.map((employee: IEmployee, key: number) => {
      return (
            <EmployeeList
              key={key}
              employee={employee}
              deleteEmployee={deleteEmployee}
              editEmployee={editEmployee}
            />
          );
        })}
      </div>
    </div>
  );
};

EmployeeList.tsx

import React from "react";
import { IEmployee } from "../../Interfaces";
import { CreateEmployee } from "../employeeComponents/CreateEmployee";
interface Props {
  employee: IEmployee;
  deleteEmployee: (employeeNameToDelete: string) => void;
  editEmployee: (employeeNameToEdit: string, employeeIdToEdit: string) => void;
}
export const EmployeeList = ({ employee, deleteEmployee, editEmployee }: Props) => {
  return (
    <div>
      <div className="employee-list">
        <div className="content">
          <span>{employee.employeeId}</span>
          <span>{employee.employeeName}</span>
        </div>
        <button
          onClick={() => {
            deleteEmployee(employee.employeeName);
          }}
        >
          Delete
        </button>

        <button
          onClick={() => {
            editEmployee(employee.employeeName, employee.employeeId);
          }}
        >
          Edit
        </button>
      </div>
    </div>
  );
};

export default EmployeeList;
3 Answers

You are using the current state's value employeeList within its setter. Instead of passing an object to useState, you can pass a function instead that takes the old state as a parameter. For example:

setEmployeeList((oldEmployeeList) => 
      (oldEmployeeList.map((employee) => {
        if (employee.employeeName === employeeNameToEdit) {
          employee.employeeId = employeeIdToEdit;
        }
        return employee;
      }))
    );

This is an issue of state mutation, you are mutating the employee object you are editing instead of returning a new employee object reference. Since the specific employee object reference doesn't change React is likely bailing on rerendering them since it uses shallow reference equality checks during Reconciliation.

const editEmployee = (
  employeeNameToEdit: string,
  employeeIdToEdit: string
): void => {
  setEmployeeList(employeeList => employeeList.map((employee) => {
    if (employee.employeeName === employeeNameToEdit) {
      return {                        // <-- return new object reference
        ...employee,                  // <-- shallow copy previous state
        employeeId: employeeIdToEdit, // <-- set property value
      }
    }
    return employee; // <-- else return previous state
  }));
};

The problem was not that you were mutating the state, your editEmployee function was using setEmployeeList correctly -- map returns a new array, and React was detecting this change and re-rendering the component. You can verify that by inserting console.log('rendering'); into your CreateEmployee function.

The problem was that editEmployee was just creating an exact copy of the old employeeList.

What you probably want for it to do is to populate the text fields with the edited employee id and name, and then update your employee list when the submit button is clicked.

const editEmployee = (
    employeeNameToEdit: string,
    employeeIdToEdit: string
  ): void => {
    // This will put these two items into the text fields for editing
    setEmployeeId(employeeIdToEdit);
    setEmployeeName(employeeNameToEdit);
  };

and change addEmployee to something like this (you may rename it to addOrUpdateEmployee):

const addEmployee = (): void => {
    const newEmployee = {
      employeeId: employeeId,
      employeeName: employeeName,
    };

    // Consider it's a new employee if the employee with this employeeId
    // does not exist in the list of employees. 
    // Otherwise, it's an edited employee
    let employeeIndex;
    if (
      employeeList.some((employee, index) => {
        employeeIndex = index;
        return employee.employeeId === employeeId;
      })
    ) {
      // This is not state mutation ...
      employeeList[employeeIndex] = newEmployee;
      // ...because we set it to a copy of the mutated array
      setEmployeeList([...employeeList]);
    } else {
      setEmployeeList([...employeeList, newEmployee]);
    }

    setEmployeeName('');
    setEmployeeId('');
  };

The logic to decide if it's a new or edited employee is entirely mine, you should use whatever is appropriate for your app.

Related