Итак, в основном я пытаюсь создать таблицу с произвольно набранными входами (например, выбор, число, текст), которые позволяют пользователю добавлять / редактировать / удалять строки таблиц с указанными входами:
Когда вы щелкните зеленую галочку, строка таблицы должна быть добавлена с данными, введенными пользователем, добавленная строка имеет кнопку редактирования (которая удалит строку из таблицы и установит входные значения для значений строк, чтобы их можно было редактировать и прочитано) и кнопку удаления.
Моя проблема в том, что я пытаюсь дать произвольные входные данные и разрешить пользователю указывать их (все они имеют функцию изменения дескриптора из интерфейса IHandleChange) в индексном файле tsx Мне все еще нужна ссылка на функции компонентов таблицы для передачи дочерним компонентам, которые пользователь выбирает для помещения в таблицу. Прямо сейчас я использую функцию по умолчанию:
const default_function = () => {}
, а затем ожидаю, что мой метод рендеринга в таблице переопределит свойства, когда он вызывает React.cloneElement с новым набором свойств из таблицы. Однако, как видно в окне компонента, мои функции добавления, редактирования, handleChanges и удаления никогда не передавались правильно моему TableBody и, следовательно, его дочерним компонентам.
Есть ли что-то серьезное, что я делаю неправильно, или я просто неправильная передача реквизита при клонировании элемента в функции Table :: render?
index.tsx:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import "bootstrap/dist/css/bootstrap.min.css"
//import * as FormInputs from './FormInputs';
import {Table, HeaderRow, TableBody, InputRow} from './SmartTable';
import * as FormInputs from './FormInputs';
const defaultFunc = () => {}
ReactDOM.render(
<Table rowList={[]}>
<HeaderRow tableHeaders={[{title:"select"},
{title:"text"},
{title:"date"},
{title:"number"}]}/>
<TableBody
rowList={[]}
edit={defaultFunc}
remove={defaultFunc}
add={defaultFunc}
handleChange={defaultFunc}
>
<InputRow add={defaultFunc} handleChange={defaultFunc}>
<FormInputs.SelectInput selectOptions={{name: "select", value: ""}} options={[{name:"", value:""}, {name: "one", value: "1"}, {name: "two", value: "2"}]} className="form-control"/>
<FormInputs.TextInput options={{name: "text_input", value: ""}} placeholder="enter text here" className="form-control"/>
<FormInputs.DateInput options={{name: "date_input", value: ""}} className="form-control" min="2019-09-02" max={new Date().toISOString().split('T')[0]}/>
<FormInputs.NumberInput options={{name:"number", value: ""}} className="form-control" min="-10" max="10" step="2"/>
</InputRow>
</TableBody>
</Table>, document.getElementById('root'));
Table.tsx
/*********************************************************************************************/
interface IFilterable {
title: string;
filterable?: boolean;
//this will need a filter type field in the future
}
interface IHeaderRowProps {
tableHeaders: IFilterable[];
}
export const HeaderRow: React.FC<IHeaderRowProps> = (props: IHeaderRowProps) => {
return (
<thead>
<tr>
{props.tableHeaders.map(header => header.filterable
? <th key={header.title}>{header.title}<FilterButton/></th>
: <th key={header.title}>{header.title}</th>)}
</tr>
</thead>
);
}
/*********************************************************************************************/
/*********************************************************************************************/
interface IInputRowProps extends FormInputs.ChangeHandled {
add(): void;
}
export class InputRow extends React.Component<IInputRowProps> {
private childRefs: any[] = [];
constructor(props: any) {
super(props);
if (this.props.children) {
[this.props.children].forEach((child) => {
this.childRefs.push(React.createRef());
})
}
}
handleAddRow = (event: any) => {
this.props.add();
}
handleChange = (event: any) => {
if (this.props.handleChange) {
this.props.handleChange(event);
}
}
render () {
if (this.props.children !== null && typeof this.props.children !== 'undefined') {
return (
<tr>
{React.Children.map(this.props.children, (child: any, index) => <td>{React.cloneElement(child, {handleChange: this.handleChange, ref: this.childRefs[index]})}</td>)}
<td>
<button type="button" className="btn btn-success" onClick={this.handleAddRow}>
<FontAwesomeIcon icon={faCheck}/>
</button>
</td>
</tr>
);
} else return null;
}
}
/*********************************************************************************************/
/*********************************************************************************************/
interface ITableDataProps extends FormInputs.IFormOptions {
text: string;
}
export const TableData: React.FC<ITableDataProps> = (props: ITableDataProps) => {
return (
<td>
{props.text}
{<FormInputs.HiddenInput name={props.name} value={props.value}/>}
</td>
);
}
/*********************************************************************************************/
/*********************************************************************************************/
interface ITableRowProps {
values: string[];
edit(values: string[]): void;
remove(values: string[]): void;
}
export class TableRow extends React.Component<ITableRowProps> {
handleEdit = () => {
this.props.edit(this.props.values);
}
handleRemove = () => {
this.props.remove(this.props.values);
}
render () {
return (
<tr>
{this.props.values.map((val, index) => <TableData key={uid(index) + "-d"} text={val} name={`td${index}`} value={val}/>)}
<td>
<div className="btn-group">
<button type="button" className="btn btn-warning" onClick={this.handleEdit}><FontAwesomeIcon icon={faEdit}/></button>
<button type="button" className="btn btn-danger" onClick={this.handleRemove}><FontAwesomeIcon icon={faTimes}/></button>
</div>
</td>
</tr>
);
}
}
/*********************************************************************************************/
/*********************************************************************************************/
interface ITableBodyProps {
rowList: string[][];
edit(values: string[]): void;
remove(values: string[]): void;
add(): void;
handleChange(event: any): void;
}
export class TableBody extends React.Component<ITableBodyProps> {
render () {
return (
<tbody>
{React.cloneElement(this.props.children as React.ReactElement<any>, {add: this.props.add, handleChange: this.props.handleChange})}
{this.props.rowList.map((row, index) => <TableRow key={uid(index) + "-r"} values={row} edit={this.props.edit} remove={this.props.remove}/>)}
</tbody>
);
}
}
/*********************************************************************************************/
/*********************************************************************************************/
interface ITableProps {
rowList?: string[][];
}
interface ITableState {
rowList: string[][];
[key: string]: any;
}
export class Table extends React.Component<ITableProps, ITableState> {
constructor (props: any) {
super(props);
this.state = {
rowList: typeof this.props.rowList === 'undefined' ? [] : this.props.rowList
};
}
handleInputRowChange = (event: any) => {
this.setState({
[event.target.name]: event.target.value
});
}
handleRemoveRow = (values: string[]) => {
this.setState({
rowList: this.state.rowList.slice().filter(r => r !== values)
});
}
handleEditRow = (values: string[]) => {
let i = 0;
let obj: any = {};
Object.keys(this.state).forEach((key) => {
if (key !== 'rowList') {
obj[key] = values[i++];
}
});
this.setState(obj);
this.handleRemoveRow(values);
}
handleAddRow = () => {
let values: string[] = [];
let obj: any = {};
Object.keys(this.state).forEach((key) => {
if (key !== 'rowList') {
values.push(this.state[key]);
obj[key] = "";
}
});
var newList = this.state.rowList.slice().concat([values]);
this.setState({
rowList: newList,
...obj
});
}
render () {
let children = React.Children.toArray(this.props.children);
return (
<table className="table">
{React.cloneElement(children[0] as React.ReactElement<any>)}
{React.cloneElement(children[1] as React.ReactElement<any>, {rowList: this.props.rowList, edit: this.handleEditRow, remove: this.handleRemoveRow, add: this.handleAddRow, handleChange: this.handleInputRowChange})}
</table>
);
}
}
/*********************************************************************************************/
FormInputs .tsx (на всякий случай нужно запустить):
import * as React from 'react';
import "bootstrap/dist/css/bootstrap.min.css"
interface IStyleOptions {
className?: string;
}
export interface IFormOptions {
name?: string;
value?: string;
}
export interface ChangeHandled {
handleChange?(event: any): void;
}
/*********************************************************************************************/
interface ISelectInputProps extends IStyleOptions, ChangeHandled {
options: IFormOptions[];
selectOptions: IFormOptions;
}
export class SelectInput extends React.Component<ISelectInputProps> {
render () {
return (
<select className={this.props.className}
name={this.props.selectOptions.name}
value={typeof this.props.selectOptions.value === 'undefined' ? '' : this.props.selectOptions.value}
onChange={this.props.handleChange}>
{this.props.options.map(opt => <option key={opt.name} value={opt.value}>{opt.name}</option>)}
</select>
);
}
}
/*********************************************************************************************/
/*********************************************************************************************/
interface ITextInputProps extends IStyleOptions, ChangeHandled {
options: IFormOptions;
placeholder?: string;
}
export const TextInput: React.FC<ITextInputProps> = (props) => {
return (
<input
type="text"
className={props.className}
name={props.options.name}
value={typeof props.options.value === 'undefined' ? '' : props.options.value}
placeholder={props.placeholder}
onChange={props.handleChange}
/>
);
}
/*********************************************************************************************/
/*********************************************************************************************/
interface IDateInputProps extends IStyleOptions, ChangeHandled {
options: IFormOptions;
min?: string;
max?: string;
}
export const DateInput: React.FC<IDateInputProps> = (props) => {
return (
<input
type="date"
className={props.className}
name={props.options.name}
value={typeof props.options.value === 'undefined' ? '' : props.options.value}
defaultValue={props.options.value}
min={props.min}
max={props.max}
onChange={props.handleChange}
/>
);
}
/*********************************************************************************************/
/*********************************************************************************************/
export const DateTimeInput: React.FC<IDateInputProps> = (props) => {
return (
<input
type="datetime-local"
className={props.className}
name={props.options.name}
value={typeof props.options.value === 'undefined' ? '' : props.options.value}
defaultValue={props.options.value}
min={props.min}
max={props.max}
onChange={props.handleChange}
/>
);
}
/*********************************************************************************************/
/*********************************************************************************************/
interface ICheckboxInputProps extends IStyleOptions, ChangeHandled {
options: IFormOptions;
disabled?: boolean;
}
export const CheckboxInput: React.FC<ICheckboxInputProps> = (props) => {
return (
<input
type="checkbox"
className={props.className}
name={props.options.name}
value={typeof props.options.value === 'undefined' ? '' : props.options.value}
disabled={props.disabled}
onChange={props.handleChange}
/>
);
}
/*********************************************************************************************/
/*********************************************************************************************/
interface INumberInputProps extends IStyleOptions, ChangeHandled {
options: IFormOptions;
min?: string;
max?: string;
step?: string;
}
export const NumberInput: React.FC<INumberInputProps> = (props) => {
return (
<input
type="number"
className={props.className}
name={props.options.name}
value={typeof props.options.value === 'undefined' ? '' : props.options.value}
defaultValue={props.options.value}
min={props.min}
max={props.max}
step={props.step}
onChange={props.handleChange}
/>
);
}
/*********************************************************************************************/
/*********************************************************************************************/
export const HiddenInput: React.FC<IFormOptions> = (props) => {
return (
<input
type="hidden"
name={props.name}
value={props.value}
/>
);
}
/*********************************************************************************************/