EN
Creating a dynamic table with textboxes in React
3
answers
3
points
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 answers
5
points
What about solution in this article: React - create dynamic editable table (like spreadsheet) ?

0 comments
Add comment
4
points
What about solution in this article: React - create dynamic editable table ?

0 comments
Add comment
3
points
Check this solution:

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
4 comments
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.
Try to check if data row is empty inside handleChange function using https://dirask.com/snippets/pVW8Gj and call appendItem() if needed.
Row id is not incrementing.......... how to I fix that?
Check current version.