Dynamically add a column to react table on button click

6.9k views Asked by At

I am trying to add a column to the react table using an add column button, but I am a little confused on how to implement this. I want to be able to add a column to the corresponding side that the button is clicked. Do I need to create a custom function and add it to my columns array or is there an easier way to implement this?

X-Matrix

Here is the code.

/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable guard-for-in */
/* eslint-disable no-restricted-syntax */
/* eslint-disable react/jsx-props-no-spreading */
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { useTable } from 'react-table';
import data from '../Data/data.json';
import './Table.css';

const Styles = styled.div`

  table {
    border-spacing: 0;
    border: 1px solid black;
    width: 970px;

    tr {
      :last-child {
        td {
          border-bottom: 0;
          height: 500px;
        }
      }
    }

    th,

    td {
      margin: 0;
      padding: 0.5rem;
      border-bottom: 1px solid black;
      border-right: 1px solid black;

      :last-child {
        border-right: 0;
      }
    }
  }
`;

const HarveyBall = (initialValue) => {
  const [value, setValue] = useState(initialValue);
  
  const onClick = () => {
    if(value === 'Empty'){
      setValue('Quarter');
    }
    if(value === 'Quarter'){
      setValue('Half');
    }
    if(value === 'Half'){
      setValue('Three-Quarter');
    }
    if(value === 'Three-Quarter'){
      setValue('Full');
    }
    if(value === 'Full'){
      setValue('Empty');
    }
  };

  if(value === "Empty"){
    return (
      <div type="button" label="Empty" className="harvey none" onClick={onClick} />
    );
  }

  if(value === "Quarter"){
    return (
      <div type="button" label="Quarter" className="harvey quarters quarter" onClick={onClick} />
    );
  }

  if(value === "Half"){
    return (
      <div type="button" label="Half" className="harvey quarters half" onClick={onClick} />
    );
  }

  if(value === "Three-Quarter"){
    return (
      <div type="button" label="Three-Quarter" className="harvey quarters three-quarters" onClick={onClick} />
    );
  }

  if(value === "Full"){
    return (
      <div type="button" label="Full" className="harvey quarters full" onClick={onClick} />
    );
  }
  return null;
};

const defaultPropGetter = () => ({});

const EditableCell = ({
  value: initialValue,
  row: { index },
  column: { id },
  updateMyData, 
}) => {

  const [value, setValue] = React.useState(initialValue);

  const onChange = e => {
    setValue(e.target.value);
  };

  const onBlur = () => {
    updateMyData(index, id, value);
  };

  React.useEffect(() => {
    setValue(initialValue);
  }, [initialValue]);

  return id === "strategicInitiative" || index === 5 ? <textarea value={value} onChange={onChange} onBlur={onBlur} style={{ width: '100%', focus: 'none', outline: 'none', border: 'none',resize: 'none', expand: {height: '1em', width: '50%', padding: '3px'}}}/> : HarveyBall(initialValue);
 
};

const defaultColumn = {
  Cell: EditableCell,
};

function Table({ columns, getHeaderProps = defaultPropGetter, updateMyData, }) {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
  } = useTable({
    columns,
    data,
    updateMyData,
    defaultColumn,
  });

  return (
    <table {...getTableProps()}>
      <thead>
        {headerGroups.map(headerGroup => (
          <tr {...headerGroup.getHeaderGroupProps()}>
            {headerGroup.headers.map(column => column.hideHeader === false ? null : (
              <th
              {...column.getHeaderProps([
                {
                  className: column.className,
                  style: column.style,
                },
                getHeaderProps(column),
              ])}
            >
              {column.render('Header')}
            </th>
             ))}
          </tr>
        ))}
      </thead>
      <tbody {...getTableBodyProps()}>
        {rows.map((row) => {
          prepareRow(row);
          return (
            <tr {...row.getRowProps()}>
              {row.cells.map(cell => {
                return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>;
              })}
            </tr>
          );
        })}
      </tbody>
    </table>
  );
}

export default function MatrixTable() {

  const addTasks = () => {
    const taskLength = [];
    for(let i = 0; i < data.length; i += 1){
      for(const [key] of Object.entries(data[i])){
        if(key.includes("T")){
          taskLength.push(key[1]);
        }
      }
    }

    const newTaskLength = (parseInt(taskLength[taskLength.length - 1], 10) + 1) ;

    for(let i = 0; i < data.length; i += 1){
      data[i][`T${newTaskLength}`] = "";
    }
  };

  const taskButton = () => (
    <>
      <div>Tasks</div>
      <button onClick={addColumns} type='button'>Add Column</button>
    </>
  );

  const goalButton = () => (
    <>
      <div>Goals</div>
      <button type='button'>Add Column</button>
    </>
  );

  const columns = React.useMemo(
    () => [
      {
        Header: () => taskButton(),
        accessor: 'tasks',
        style: {
          width: '255px',
          height: '49px',
          background: '#fdf5ed',
          fontSize: '16px',
          color: '#f2994a',
          textAlign: 'center',
          lineHeight: '49px',
        },
        columns: [
          {
            hideHeader: false,
            accessor: 'T1'
          },
          {
            hideHeader: false,
            accessor: 'T2'
          },
          {
            hideHeader: false,
            accessor: 'T3'
          },
          {
            hideHeader: false,
            accessor: 'T4'
          },
          {
            hideHeader: false,
            accessor: 'T5'
          },
        ]
          },
          {
            Header: "Strategic Inititiatives",
            accessor: "strategicInitiative ",
            style: {
              color: '#323b3e',
              width: '460px',
              height: '49px',
              background: '#f2f2f2',
              textAlign: 'center',
              lineHeight: '49px',
            },
            columns: [
              {
                hideHeader: false,
                accessor: 'strategicInitiative'
              }
            ]
          },
          {
            Header: goalButton(),
            accessor: 'goals',
            style: {
              color: '#56ccf2',
              width: '255px',
              height: '49px',
              background: '#f8fcfe',
              textAlign: 'center',
              lineHeight: '49px',
            },
            columns: [
              {
                hideHeader: false,
                accessor: 'G1'
              },
              {
                hideHeader: false,
                accessor: 'G2'
              },
              {
                hideHeader: false,
                accessor: 'G3'
              },
              {
                hideHeader: false,
                accessor: 'G4'
              },
              {
                hideHeader: false,
                accessor: 'G5'
              } 
            ]
          },  
        ],
    []
  );

  const addColumns = () => {
    for(let i = 0; i < columns.length; i += 1) {
      if(columns[i].accessor === "tasks"){
        console.log(columns[i]);
        columns[i].columns.push({
          hideHeader: false,
          accessor: 'T6'
        });
      }
    }
  };

  addColumns();

  const [, setData] = useState(data);

  useEffect(() => {
    setData(data);
  }, [data]);

  const updateMyData = (rowIndex, columnId, value) => {
  
    setData(old =>
      old.map((row, index) => {
        if (index === rowIndex) {
          return {
            ...old[rowIndex],
            [columnId]: value,
          };
        }
        return row;
      })
    );
  };

  return (
    <Styles>
      <Table columns={columns} data={data} updateMyData={updateMyData}/>
    </Styles>
  );
}
2

There are 2 answers

0
Rohit Garg On

Yeah, changing columns array(not by mutation, but by setting columns prop to new required array) is the best way to go.

1
thatslikeyouropinionman On

I would imagine that modifying the array columns which stores the data which the table uses to render, would be the correct solution. Once columns is updated, your table should re-render which will then include the updated column.