Languages

Creating a dynamic table with textboxes in React

3 points
Asked by:
kwartz
1060

I am trying to create a dynamic table with textboxes. Each header should have a corresponding textbox. When a row is populated with data, a empty row of textboxes is created dynamically on the next line. I am doing this in React. Can you help me?

 

3 comments
Root-ssh
Can you provide your current code? Or picture in paint how this feature should looks like. Also in what technology you use JavaScript, React, Angular, Vue.js?
kwartz
it's being coded in React
kwartz
Thank you. This is a good start for me, however, I do not need the add row button, a blank row should be added immediately after the previous row is populated. So there should always be a blank row.
Add comment
3 answers
5 points
Answered by:
kwartz
1060

What about solution in this article: React - create dynamic editable table (like spreadsheet) ?

Simple dynamic editable table in React (like spreadsheet).
Simple dynamic editable table in React (like spreadsheet).
0 comments Add comment
4 points
Answered by:
kwartz
1060

What about solution in this article: React - create dynamic editable table ?

Simple dynamic editable table in React.
Simple dynamic editable table in React.
0 comments Add comment
3 points
Answered by:
kwartz
1060

Check this solution:

Dynamic table with automatically calculated summary - React.
Dynamic table with automatically calculated summary - React.

Source code:

// ONLINE-RUNNER:browser;

// Note: Uncomment import lines in your project.
// import React from 'react';
// import ReactDOM from 'react-dom';

// CELL -----------------------------------------

const itemStyle = {
  	position: 'relative',
    height: '26px'
};

const inputStyle = {
  	padding: '0',
    position: 'absolute',
    left: '2px',
    top: '2px',
    right: '2px',
    bottom: '2px',
  	fontFamily: 'Arial',
  	fontSize: '13px'
};

const Cell = React.memo(({ value, onChange }) => {
    const valueRef = React.useRef();
  	const inputRef = React.useRef();
  	React.useEffect(() => {
        var input = inputRef.current;
        if (input) {
            input.value = value ?? '';
        }
    }, [value]);
    const handleFocus = () => {
        var input = inputRef.current;
        if (input) {
            valueRef.current = input.value;
        }
    };
	const handleBlur = () => {
        if (onChange) {
            var input = inputRef.current;
            if (input && input.value !== valueRef.current) {
                onChange(input.value);
            }
        }
    };
    return (
        <div style={itemStyle}>
          <input
            ref={inputRef}
            style={inputStyle}
            type="text"
            onFocus={handleFocus}
            onBlur={handleBlur}
          />
        </div>
    );
});

// ROW ------------------------------------------

const tdStyle = {
  	padding: '1px',
  	border: '1px solid black',
};

const optionStyle = {
  	...tdStyle,
    padding: '2px 2px',
  	width: '30px'
};

const Row = React.memo(({ columns, data, onChange, onDelete }) => {
	const handleDeleteClick = () => onDelete?.();
    return (
      <tr>
        {columns.map(({path}, columnIndex) => {
            const handleChange = value => {
                if (onChange) {
                    const changedData = { ...data, [path]: value };
                    onChange(columnIndex, changedData);
                }
            };
            return (
                <td key={path} style={tdStyle}>
                  <Cell 
                    value={data[path]} 
                    onChange={handleChange} 
                  />
                </td>
            );
        })}
        <td style={optionStyle}>
          <button onClick={handleDeleteClick}>Delete</button>
        </td>
      </tr>
    );
});

// TABLE ----------------------------------------

const tableStyle = {
    border: '1px solid black',
    borderCollapse: 'collapse',
  	width: '100%'
}

const Table = React.memo(({ id, columns, data, onAdd, onChange, onDelete, children }) => {
    const handleAddClick = () => {
      	onAdd?.(data.length);
    };
	return (
        <div>
          <table style={tableStyle}>
            <tbody>
              <tr>
                {columns.map(({path, name}) => (
                  <th key={path} style={tdStyle}>{name}</th>
                ))}
              </tr>
              {data.map((rowData, rowIndex) => {
                  const handleChange = (columnIndex, changedData) => {
                      onChange?.(rowIndex, columnIndex, changedData);
                  };
                  const handleDelete = () => {
                      onDelete?.(rowIndex, rowData);
                  };
                  return (
                      <Row 
                        key={rowData[id]}
                        columns={columns}
                        data={rowData}
                        onChange={handleChange}
                        onDelete={handleDelete}
                      />
                  );
              })}
              {children}
            </tbody>
          </table>
          <br />
          <div>
            <button onClick={handleAddClick}>Add row</button>
          </div>
        </div>
    );
});

// UTILS ----------------------------------------

// https://dirask.com/snippets/D7XEop

const appendItem = (updater, item) => {
    updater(array => array.concat(item));
};
  
const replaceItem = (updater, index, item) => {
    updater(array => array.map((value, i) => i === index ? item : value));
};

const deleteItem = (updater, index) => {
    updater(array => array.filter((value, i) => i !== index));
};

// Example --------------------------------------

const isRowFilled = (columns, row) => {
    return columns.reduce((result, column) => result && !!row[column.path], true);
};
  
const columns = [
    { path: 'name',   name: 'Name'   },
    { path: 'vendor', name: 'Vendor' },
    { path: 'type',   name: 'Type'   },
    { path: 'amount', name: 'Amount' }
];

let counter = 0;

const App = () => {
  	const [data, setData] = React.useState(() => ([
        { id: ++counter, name: 'John doe', vendor: 'Matrix', type: 'Service', amount: 120 }
	]));
    const totalAmount = React.useMemo(() => {
        return data.reduce((sum, item) => sum + Number(item.amount || 0), 0);
    }, [data]);
  	const handleAdd = (rowIndex) => {
      	const newRowData =  { id: ++counter };
        appendItem(setData, newRowData);
      	//TODO: AJAX request to server
      	console.log(`Added empty row!`);
    };
  	const handleChange = (rowIndex, columnIndex, changedRowData) => {  
        setData((data) => {
            const aPart = data.slice(0, rowIndex);
            const bPart = data.slice(rowIndex + 1);
            const newData = [...aPart, changedRowData, ...bPart];
            if (isRowFilled(columns, newData[newData.length - 1])) {
                newData.push({ id: ++counter });
            }
            return newData;
        });
    	const changedRowJson = JSON.stringify(changedRowData, null, 4);
      	//TODO: AJAX request to server
      	console.log(`Changed row:\n${changedRowJson}`);
    };
  	const handleDelete = (rowIndex, deletedRowData) => {
        deleteItem(setData, rowIndex);
      	//TODO: AJAX request to server
      	console.log(`Deleted row: ${rowIndex}`);
    };
    return (
        <div>
          <Table
            id="id"
            columns={columns}
            data={data}
            onAdd={handleAdd}
            onChange={handleChange}
            onDelete={handleDelete}
          >
            <tr>
              <td style={tdStyle} />
              <td style={tdStyle} />
              <td style={tdStyle} />
              <td style={tdStyle}>{totalAmount}</td>
              <td style={tdStyle} />
            </tr>
          </Table>
        </div >
    );
};

const root = document.querySelector('#root');
ReactDOM.render(<App/>, root);

 

Sources

  1. React - create dynamic editable table (like spreadsheet)
  2. React - append, prepend, remove and replace items in array with utils for useState()

4 comments
kwartz
Thank you. This is a good start for me, however, I do not need the add row button, a blank row should be added immediately after the previous row is populated. So there should always be a blank row.
Root-ssh
Try to check if data row is empty inside handleChange function using https://dirask.com/snippets/pVW8Gj and call appendItem() if needed.
kwartz
Row id is not incrementing.......... how to I fix that?
Root-ssh
Check current version.
Add comment
Donate to Dirask
Our content is created by volunteers - like Wikipedia. If you think, the things we do are good, donate us. Thanks!
Join to our subscribers to be up to date with content, news and offers.
Native Advertising
🚀
Get your tech brand or product in front of software developers.
For more information Contact us
Dirask - we help you to
solve coding problems.
Ask question.

❤️💻 🙂

Join