переопределить свойство функции по умолчанию для дочернего элемента реакции с помощью cloneElement - PullRequest
1 голос
/ 08 июля 2020

Итак, в основном я пытаюсь создать таблицу с произвольно набранными входами (например, выбор, число, текст), которые позволяют пользователю добавлять / редактировать / удалять строки таблиц с указанными входами:

Когда вы щелкните зеленую галочку, строка таблицы должна быть добавлена ​​с данными, введенными пользователем, добавленная строка имеет кнопку редактирования (которая удалит строку из таблицы и установит входные значения для значений строк, чтобы их можно было редактировать и прочитано) и кнопку удаления.

Моя проблема в том, что я пытаюсь дать произвольные входные данные и разрешить пользователю указывать их (все они имеют функцию изменения дескриптора из интерфейса IHandleChange) в индексном файле tsx Мне все еще нужна ссылка на функции компонентов таблицы для передачи дочерним компонентам, которые пользователь выбирает для помещения в таблицу. Прямо сейчас я использую функцию по умолчанию:

const default_function = () => {}

, а затем ожидаю, что мой метод рендеринга в таблице переопределит свойства, когда он вызывает React.cloneElement с новым набором свойств из таблицы. Однако, как видно в окне компонента, мои функции добавления, редактирования, handleChanges и удаления никогда не передавались правильно моему TableBody и, следовательно, его дочерним компонентам.

Есть ли что-то серьезное, что я делаю неправильно, или я просто неправильная передача реквизита при клонировании элемента в функции Table :: render?

component inspector window

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}
         />
    );
}
/*********************************************************************************************/
...