为什么我无法在ReactJS中连续输入输入字段?

时间:2018-04-05 15:40:36

标签: javascript reactjs input user-input

我有一个如下所示的输入组件:

import React from 'react';
import { PropTypes } from 'prop-types';

const NewCartonInput =
({
  value,
  itemId,
  onChange,
}) => (
  <input
    id={itemId}
    onChange={onChange}
    type="number"
    value={value}
  />
);

NewCartonInput.propTypes = {
  value: PropTypes.number.isRequired,
  itemId: PropTypes.string.isRequired,
  onChange: PropTypes.func.isRequired,
};

export default NewCartonInput;

它位于此父组件中:

import React from 'react';
import { PropTypes } from 'prop-types';
import { sumBy, isEmpty, isEqual, toNumber } from 'lodash';
import T from 'i18n-react';
import uuidv1 from 'uuid/v1';

import { formatNumber } from 'utils/currencyAndNumberFormatting';
import Container from 'components/Container';
import Content from 'components/Content';
import Header from 'components/Header';
import SearchBox from 'components/SearchBox';
import ListWithHeader from 'components/List/ListWithHeader';
import ListItemWithNumber from 'components/List/ListItemWithNumber';
import Footer from 'components/Footer';
import FooterText from 'components/Footer/Text';
import Prompt from 'components/Prompt';
import TransferPickList from 'components/PrintTemplates/TransferPickList';
import Print from 'components/Print';
import { formatDate } from 'utils/date';
import NewCartonInput from './NewCartonInput';
import { showProgressBar } from './utils';
import './NewTransfer.scss';

export default class NewCarton extends React.Component {
  constructor(props) {
    super(props);
    this.onEditClicked = this.onEditClicked.bind(this);
    this.onClickPrint = this.onClickPrint.bind(this);
    this.onCloseCartonClicked = this.onCloseCartonClicked.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.listCartons = this.listCartons.bind(this);

    this.state = {
      editMode: false,
      showPrintDialog: false,
      disableSearch: false,
    };
  }

  componentWillMount() {
    const {
      onGetCartonItems,
      onClearNewCarton,
      transferRequestId,
      hasChanges,
      routerAction,
    } = this.props;

    if (routerAction === 'PUSH') {
      onClearNewCarton();
    }

    if ((!hasChanges || routerAction === 'PUSH') && transferRequestId) {
      onGetCartonItems(transferRequestId);
    }
  }

  shouldComponentUpdate(newProps) {
    const newItems = newProps.items;
    const oldItems = this.props.items;
    return !isEqual(newItems, oldItems);
  }

  componentWillUnmount() {
    const { onClearShipment } = this.props;
    onClearShipment();
  }

  onEditClicked() {
    this.forceUpdate();

    this.setState({
      editMode: !this.state.editMode,
    });

    this.forceUpdate();
  }

  onClickPrint() {
    this.forceUpdate();
    this.setState({
      showPrintDialog: !this.state.showPrintDialog,
    });
  }

  onCloseCartonClicked(cartonContext) {
    const { onClickClose } = this.props;
    this.setState({
      disableSearch: true,
    });
    onClickClose(cartonContext);
  }

  handleChange(e) {
    const { items, onUpdateQuantity } = this.props;
    const updateItem = items.filter((item) =>
      item.itemId === e.target.id,
    );
    const itemQuantity = toNumber(e.target.value);

    updateItem.total += itemQuantity;
    onUpdateQuantity(e.target.id, itemQuantity);
  }

  listCartons() {
    const {
      items,
      onItemDelete,
      transferRequestId,
    } = this.props;
    const list =
      items.filter((item) => !item.deleted || item.deleted === false);
    return list.map(({
      itemId,
      itemDescription,
      packed,
      total,
    }, index, filteredItems) => {
      let onDeletePress;
      if (deleteMode(filteredItems, this.state.editMode)) {
        onDeletePress = (e) => {
          e.stopPropagation();
          onItemDelete(itemId);
        };
      }

      const packedAll = packed === total && total > 0;

      let countDetailsStr;
      let countDetailsTotal;
      if (transferRequestId) {
        countDetailsStr = (
          <li styleName="newCartonOf">
            of
          </li>
        );
        countDetailsTotal = (
          <li styleName="newCartonTotal">
            {total}
          </li>
        );
      }

      return (
        <li styleName="newCartonQuantity" key={uuidv1()}>
          <ul styleName="newCartonDetails">
            <ListItemWithNumber
              header={itemId}
              section={itemDescription}
              key={itemId}
              onPress={() => {}}
              onDeletePress={onDeletePress}
              checkIcon={packedAll}
              icon={false}
            />
            <ul styleName="newCartonQtyDetails">
              <li styleName="newCartonQtyInput">
                <NewCartonInput
                  value={packed}
                  itemId={itemId}
                  onChange={this.handleChange}
                />
              </li>
              {countDetailsStr}
              {countDetailsTotal}
            </ul>
          </ul>
        </li>
      );
    });
  }

  render() {
    const {
      onSearch,
      items,
      destinationId,
      shipmentId,
      sourceId,
      onClickSave,
      onClickClose,
      shouldNotBlock,
      loading,
      transferRequestId,
      destId,
      hasChanges,
      printRequestedItems,
      currentLocale,
      disableSearch,
    } = this.props;

    const canEditItems = !transferRequestId && !isEmpty(items);
    const backTitle = destId ? T.translate('views.transfers.title.transfers')
      : T.translate('views.transfers.title.transferCartons');
    const itemsPacked = sumBy(
      items, item => item.packed,
    );
    const totalItems = sumBy(
      items, item => item.total,
    );
    const destinationHeader = {
      label: T.translate('views.transfers.labels.destination'),
      value: destinationId,
    };

    let itemsHeader;
    if (transferRequestId) {
      itemsHeader = {
        label: T.translate('views.transfers.labels.items'),
        value: `${formatNumber(itemsPacked)}/${formatNumber(totalItems)}`,
      };
    } else {
      itemsHeader = {
        label: T.translate('views.transfers.labels.items'),
        value: `${formatNumber(itemsPacked)}`,
      };
    }
    const cartonContext = {
      shipmentId,
      transferRequestId,
      sourceId,
      destinationId,
      items: items.filter((item) => !item.deleted || item.deleted === false),
      itemsPacked,
      totalItems,
    };

    let shouldDisableSave = loading || !hasChanges || isEmpty(items);
    let headerAction = () => onClickSave(cartonContext);
    let headerTitle = T.translate('views.transfers.buttons.save');
    const printbtn = T.translate('views.transfers.buttons.print');
    const editbtn = T.translate('views.transfers.buttons.edit');
    const closeCartonbtn = T.translate('views.transfers.buttons.closeCarton');
    let footer;
    if (this.state.editMode) {
      headerAction = this.onEditClicked;
      headerTitle = T.translate('views.transfers.buttons.done');
      shouldDisableSave = loading;
      footer = (
        <FooterText textAlign="center">
          {T.translate('views.transfers.text.instructions')}
        </FooterText>
      );
    } else {
      footer = (
        <Footer
          links={{
            [`${editbtn}`]: {
              onClick: this.onEditClicked,
              hidden: status === 'PACKED',
            },
            [`${printbtn}`]: {
              onClick: () => this.onClickPrint(),
              disabled: loading,
              hidden: !printRequestedItems,
            },
            [`${closeCartonbtn}`]: {
              onClick: () => onClickClose(cartonContext),
              disabled: shouldDisableSave,
            },
          }}
        />
      );
    }

    let printDialog;
    const PickTemplate = (<TransferPickList
      printingDate={formatDate(new Date(), currentLocale)}
      destinationId={destinationId}
      items={cartonContext.items}
    />);

    if (this.state.showPrintDialog) {
      printDialog = (
        <div styleName="printDialog">
          <Print
            templateRef="TransferPickList"
            templates={[PickTemplate]}
            nativePrint
            onClose={this.onClickPrint}
          />
        </div>
      );
    } else {
      printDialog = null;
    }
    return (
      <Container>
        <Header
          title={T.translate('views.transfers.title.newCarton')}
          backTitle={backTitle}
          rightLink={{
            action: headerAction,
            title: headerTitle,
            disabled: shouldDisableSave,
          }}
        />
        <Content>
          {showProgressBar(loading)}
          <Prompt
            onlyOnBack
            shouldNotBlock={shouldNotBlock}
            confirmNavigation={T.translate(
              'views.common.messages.confirmNavigation')}
          />
          <SearchBox
            onSearch={!disableSearch ? onSearch : () => {}}
            placeholder={
              T.translate('shared.components.searchBox.placeholderItemNumber')
            }
            searchButtonText={
              T.translate('shared.components.searchBox.searchButtonText')
            }
            inputDisabled={disableSearch}
          />
          <ListWithHeader
            headerLeft={destinationHeader}
            headerRight={itemsHeader}
          >
            {this.listCartons()}
          </ListWithHeader>
          {printDialog}
        </Content>
        {footer}
      </Container>
    );
  }
}

function deleteMode(filteredItems, editMode) {
  return editMode
    && filteredItems
    && filteredItems.length > 0;
}

NewCarton.propTypes = {
  onClearShipment: PropTypes.func.isRequired,
  onItemDelete: PropTypes.func,
  onSearch: PropTypes.func.isRequired,
  items: PropTypes.arrayOf(PropTypes.shape({
    itemId: PropTypes.string.isRequired,
    itemDescription: PropTypes.string.isRequired,
    packed: PropTypes.number.isRequired,
    total: PropTypes.number.isRequired,
  })),
  onGetCartonItems: PropTypes.func.isRequired,
  onClearNewCarton: PropTypes.func.isRequired,
  hasChanges: PropTypes.bool.isRequired,
  routerAction: PropTypes.string.isRequired,
  transferRequestId: PropTypes.string,
  destId: PropTypes.string,
  sourceId: PropTypes.string.isRequired,
  destinationId: PropTypes.string.isRequired,
  shipmentId: PropTypes.string,
  onClickSave: PropTypes.func.isRequired,
  onClickClose: PropTypes.func.isRequired,
  shouldNotBlock: PropTypes.func.isRequired,
  loading: PropTypes.bool.isRequired,
  printRequestedItems: PropTypes.bool.isRequired,
  currentLocale: PropTypes.string.isRequired,
  disableSearch: PropTypes.bool,
  onUpdateQuantity: PropTypes.func.isRequired,
};

NewCarton.defaultProps = {
  items: [],
  shipmentId: undefined,
  onItemDelete: () => {},
  transferRequestId: undefined,
  destId: undefined,
  disableSearch: false,
};

当我在输入字段中输入一次值时,父组件会重新渲染,从而无法连续输入输入字段。我想它可能需要对shouldComponentUpdate()做些什么,但不确定究竟是什么。我有什么想法可以阻止这个吗?也许我还需要将shouldComponentUpdate添加到子组件中?

1 个答案:

答案 0 :(得分:0)

shouldComponentUpdate组件可能会取消更新过程。这应该用于决定是否继续。所以我不认为问题在那里,但它可能在你的onEditClicked()函数中,你执行此操作.forceUpdate()。也许设置一个受控输入,其值等于本地状态。