1

I am recently developing an email list using React Table. There is a BCC field that allows users to insert an email address. I have implemented App.tsx as the Root component and EmailForm.tsx as the child one.

After every action from Child (Insert characters in BCC, select the checkboxes), an update email list is sent from the Child to Parent component where all main activities will be handled.

I am facing a problem that every time I try to insert a new character in BCC input, "onChange" event just takes one character and not the whole input text area.

I followed this thread, but it did not help.

My repo: https://codesandbox.io/s/thirsty-ellis-2981iv

My Parent component: App.tsx

import React, { Component } from "react";
import { Formik } from "formik";
import './App.css';
import EmailForm from "./EmailForm";
interface IEmail {
  "title": number;
  "checkList": ICheckList[];
  "bcc": IBcc;
}
interface ICheckList {
  "isEnable": boolean;
  "email": string
}

interface IBcc {
  "isEnable": boolean;
  "email": string
}
const defaults = [
  {
    title: "title-1",
    checkList: [{
      isEnable: true,
      email: "title-1.1@mail.com"
    },
    {
      isEnable: true,
      email: "title-1.2@mail.com"
    }],
    bcc: {
      isEnable: true,
      email: ""
    }
  },
  {
    title: "title-2",
    checkList: [{
      isEnable: true,
      email: "title-2@mail.com"
    }],
    bcc: {
      isEnable: true,
      email: ""
    }
  },
  {
    title: "title-3",
    checkList: [{
      isEnable: true,
      email: "title-3@mail.com"
    }],
    bcc: {
      isEnable: true,
      email: ""
    }
  }
];

class App extends Component {
  state = {
    data: defaults,
  }

  getInitialValues = () => {
    const initialValues = {
      ...defaults
    };
    return initialValues;
  }

  handleBccInput = (index: number, event: string) => {
    console.log('handleBccInput index: ' + index + ' bccInput : ' + event)
    let data = [...this.state.data];
    //console.log('data: ' + JSON.stringify(data));
    data[index].bcc.email = event;
    console.log('data[index].bcc.email: ' + data[index].bcc.email);
    this.setState({ data });

  }
  onSubmit = () => {
    console.log('onSubmit clicked')
  }

  handleCheckboxSelected = (emailIdx: number, addressIdx: number) => {

    let data = [...this.state.data];
    data[emailIdx].checkList[addressIdx].isEnable = !data[emailIdx].checkList[addressIdx].isEnable
    this.setState({ data });

  }

  render() {
    const initialValues = this.getInitialValues();

    const renderForm = (props: any) => (
      <EmailForm
        {...props}
        data={this.state.data}
        handleBccInput={this.handleBccInput}
        handleCheckboxSelected={this.handleCheckboxSelected}
      />
    );
    return (
      <React.Fragment >
        <Formik
          // tslint:disable-next-line jsx-no-lambda
          render={props => renderForm(props)}
          initialValues={initialValues}
          onSubmit={this.onSubmit}
          validateOnBlur={true}
          validateOnChange={true} />
      </React.Fragment>
    );
  }
}

export default App;

My Child component: EmailForm.tsx:

import React, { Component } from "react";
import { Form, FormikProps } from "formik";
import { WithTranslation, withTranslation } from "react-i18next";
import ReactTable, { Column } from "react-table";
import "react-table/react-table.css";
interface IEmail {
    "title": number;
    "checkList": ICheckList[];
    "bcc": IBcc;
}

interface ICheckList {
    "isEnable": boolean;
    "email": string
}

interface IBcc {
    "isEnable": boolean;
    "email": string
}
interface IState {
    data: IEmail[],
    handleBccInput(index: any, event: string): any,
    handleCheckboxSelected(emailIndex: number, addressIndex: number): any,

}
class EmailForm extends Component<IEmail & IState & WithTranslation> {

    renderCheckbox = (title: string) => {
        return (
            <div>{title}</div>
        );
    }
    overrideValue = (index: number, override: any) => {
        //console.log('index: ' + index + ' override: ' + override)
        this.props.handleBccInput(index, override)
    }

    onCheckBoxItemSelected = (emailIndex: number, addressIndex: number) => {
        this.props.handleCheckboxSelected(emailIndex, addressIndex)
    }
    renderHeader = (title: string) => {
        return (
            <div
                style={{
                    textAlign: "center",
                }}
            >{title}</div>
        );
    }

    tableHeader = (): Array<Column<IEmail>> => {
        // Extract transalation variable from props
        return [
            {
                Header: this.renderHeader('Title'),
                id: "title",
                accessor: "title",
                width: 200,
                Cell: props => {
                    return (
                        <input value={props.value} readOnly></input>
                    )

                },
            },
            {
                Header: this.renderHeader("Check List"),
                id: "checkList",
                accessor: "checkList",
                sortable: false,
                width: 200,
                resizable: true,
                Cell: props => {
                    const cellValues = props.value
                    //console.log('cell.value : ' + JSON.stringify(cellValues))
                    return cellValues.map((item: ICheckList, index: number) => {
                        return (
                            <div>
                                <input type="checkbox" checked={item.isEnable} onChange={() => this.onCheckBoxItemSelected(props.index, index)} />
                                <a>{item.email}</a>
                            </div>
                        )
                    })

                }
            },
            {
                Header: this.renderHeader("BCC"),
                id: "bcc",
                accessor: "bcc",
                Cell: props => {
                    return (
                        <input disabled={!props.value.isEnable} value={props.value.email} onChange={e => { this.overrideValue(props.index, e.target.value) }} type="text" ></input>
                    )

                },
                width: 200,
            }

        ];
    }
    /*
    Component render function
    */
    render() {
        const {
            t,
            data,
        } = this.props
        const emptyElement = () => null;
        const tableHeader = this.tableHeader();
        return (
            <Form className="email-container-form">
                <ReactTable
                    columns={tableHeader}
                    resizable={false}
                    data={data}
                    loading={false}
                    showPagination={false}
                    NoDataComponent={emptyElement}
                    defaultPageSize={Number.MAX_SAFE_INTEGER}
                    minRows={1}
                />
            </Form>
        )
    }
}
export default withTranslation()(EmailForm);
mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Anh Hoang
  • 117
  • 4
  • 10
  • Probably the best place to start untangling this is to read https://reactjs.org/docs/state-and-lifecycle.html#do-not-modify-state-directly and stop trying to modify state directly with this: `items[index].bcc.email = event` – akds May 11 '22 at 15:45
  • hi @akds I modified the source code and applied setState(), but the problem is still remaining – Anh Hoang May 11 '22 at 18:11
  • You are essentially re-rendering your whole `ReactTable` on each change in your BCC fields. Read this https://stackoverflow.com/questions/58778631/react-input-loses-focus-on-keypress and rethink your usage of `ReactTable` You can prove it by adding simple `` next to your `ReactTable` and wiring in the same piece of the state you use in the table, say the first element of the array. Here's an example https://codesandbox.io/s/distracted-brahmagupta-tjokk4?file=/src/EmailForm.tsx – akds May 12 '22 at 10:47

0 Answers0