React 新手问题:Textfield 在更新时失去焦点

React Beginner Question: Textfield Losing Focus On Update

我写了一个组件,它应该列出一堆带有相应文本字段的复选框。当您单击复选框或在字段中键入内容时,它意味着更新状态。

文本框工作正常,但是当我在字段中输入时,它更新状态正常,但是每当我点击键盘时我就会失去焦点。

我意识到这可能是因为没有设置键,所以我为所有内容都添加了键,但它仍然失去焦点。有一次我尝试在我的事件中添加 stopPropegation 因为我认为这可能会导致问题?我不确定..仍在学习中...似乎没有用所以我也删除了那部分。

似乎仍然无法弄清楚是什么导致它失去焦点...有人对这个问题有任何 advice/solves 吗?

我合并了我的代码并删除了不必要的部分以使其更易于阅读。有3个相关的JS文件..请看下面:

我仍然是 beginner/learning,所以如果您有与此代码的任何部分相关的有用建议,请随时提供。谢谢!

App.js

import React, { Component } from 'react';
import Form from './Form'

class App extends Component {
constructor() {
  super();
  this.state = {
      mediaDeliverables: [
        {label: 'badf', checked: false, quantity:''},
        {label: 'adfadf', checked: false, quantity:''},
        {label: 'adadf', checked: false, quantity:''},
        {label: 'addadf', checked: false, quantity:''},
        {label: 'adfdes', checked: false, quantity:''},
        {label: 'hghdgs', checked: false, quantity:''},
        {label: 'srtnf', checked: false, quantity:''},
        {label: 'xfthd', checked: false, quantity:''},
        {label: 'sbnhrr', checked: false, quantity:''},
        {label: 'sfghhh', checked: false, quantity:''},
        {label: 'sssddrr', checked: false, quantity:''}
      ]
  }
}

setMediaDeliverable = (value, index) => {
  let currentState = this.getStateCopy();
  currentState.mediaDeliverables[index] = value;
  this.setState(currentState);
} 

getStateCopy = () => Object.assign({}, this.state);

render() {
  return (
    <div className="App">
          <Form 
            key="mainForm"
            mediaDeliverablesOptions={this.state.mediaDeliverables}
            setMediaDeliverable={this.setMediaDeliverable}
          />  
    </div>
  );
  }
}
export default App;

Form.js

import React from 'react';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import FormControl from '@material-ui/core/FormControl';
import FormLabel from '@material-ui/core/FormLabel';
import FormGroup from '@material-ui/core/FormGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import MediaDeliverablesCheckBox from './MediaDeliverablesCheckBox';


const useStyles = makeStyles(theme => ({
  container: {
    display: 'inline-block',
    flexWrap: 'wrap',
  },
  root: {
    display: 'inline-block',
    flexWrap: 'wrap',
    maxWidth: 600,
    textAlign: 'left',
  },
  extendedIcon: {
    marginRight: theme.spacing(1),
  },
  formControl: {
    margin: theme.spacing(1),
    minWidth: 120,
    maxWidth: 300,
  },
  textField: {
    marginLeft: theme.spacing(1),
    marginRight: theme.spacing(1),
    width: 370,
  },
  dense: {
    marginTop: 19,
  },
  chips: {
    display: 'flex',
    flexWrap: 'wrap',
  },
  chip: {
    margin: 2,
  },
  noLabel: {
    marginTop: theme.spacing(3),
  },
}));

const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
  PaperProps: {
    style: {
      maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
      width: 250,
    },
  },
};

function getStyles(name, accountName, theme) {
  // console.log('>> [form.js] (getStyles) ',accountName)
  return {
    fontWeight:
      accountName.indexOf(name) === -1
        ? theme.typography.fontWeightRegular
        : theme.typography.fontWeightMedium,
  };
}



export default function Form(props) {

  const mediaDeliverablesOptions = props.mediaDeliverablesOptions;
  const classes = useStyles();
  const theme = useTheme();

  const CheckboxGroup = ({ values, label, onChange }) => (
  <FormControl component="fieldset">
    <FormLabel component="legend">{label}</FormLabel>
    <FormGroup>
      {values.map((value, index) => (
        <FormControlLabel
          key={index}
          control={
            <Checkbox
              checked={value.checked}
              onChange={onChange(index)}
            />
          }
          label={value.label}
        />
      ))}
    </FormGroup>
  </FormControl>
);

  const MediaDeliverableCheckBoxList = ({values, label}) => (
    <FormControl component="fieldset">
    <FormLabel component="legend">{label}</FormLabel>
    <FormGroup>
    {values.map((value, index) => (
        <MediaDeliverablesCheckBox
        key={index}
        mediaDeliverablesOptions={value}
        onMediaDeliverableChange={onMediaDeliverableChange(index)}
      />
      ))}
    </FormGroup>
  </FormControl>
    );



  const onCheckBoxChange = index => ({ target: { checked } }) => {
    const newValues = [...values];
    const value = values[index];
    newValues[index] = { ...value, checked };
    props.setDesignOrDigital(newValues);
  };

  const onMediaDeliverableChange = index => (deliverableData, e) => {
    props.setMediaDeliverable(deliverableData, index);
  }

  return (
    <div className={classes.root}>
      <MediaDeliverableCheckBoxList
        label="Please Choose Deliverables:"
        values={mediaDeliverablesOptions}
        key="media-deliverable-checkbox-list"
      />
    </div>
  );
}

MediaDeliverablesCheckbox.js

import React from 'react';
import Checkbox from '@material-ui/core/Checkbox';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import FormControl from '@material-ui/core/FormControl';
import FormLabel from '@material-ui/core/FormLabel';
import FormGroup from '@material-ui/core/FormGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import TextField from '@material-ui/core/TextField';

export default function MediaDeliverablesCheckBox(props) {

  let deliverableData = Object.assign({}, props.mediaDeliverablesOptions);

  const onCheckBoxChange = (e) => {
     deliverableData.checked = e.target.checked;
     props.onMediaDeliverableChange(deliverableData, e);
  }

  const onQuantityChange = (e) => {
     deliverableData.quantity = e.target.value;
     props.onMediaDeliverableChange(deliverableData, e);
  }

  const CheckboxGroup = ({ value, label }) => (

  <FormControl component="fieldset">
    <FormGroup>
        <FormControlLabel
          control={
            <Checkbox
              key={props.index}
              checked={value.checked}
              onChange={onCheckBoxChange}
            />
          }
          label={label}
        />
    </FormGroup>
  </FormControl>
  );

return(
    <div className="MediaDeliverablesCheckBox">
      <CheckboxGroup
            key={props.index}
            label={props.mediaDeliverablesOptions.label}
            value={props.mediaDeliverablesOptions}
          />
      <TextField
        key={'tf'+props.index}
        id={'quantity-'+props.index}
        label="Quantity"
        placeholder="How many do you need?"
        multiline
        variant="outlined"
        value={props.mediaDeliverablesOptions.quantity}
        onChange={onQuantityChange}
        fullWidth
      />
    </div>
  );
}

根据 Ryan C 推荐的编辑更新 Form.js

import React from 'react';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import FormControl from '@material-ui/core/FormControl';
import FormLabel from '@material-ui/core/FormLabel';
import FormGroup from '@material-ui/core/FormGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import MediaDeliverablesCheckBox from './MediaDeliverablesCheckBox';


const useStyles = makeStyles(theme => ({
  container: {
    display: 'inline-block',
    flexWrap: 'wrap',
  },
  root: {
    display: 'inline-block',
    flexWrap: 'wrap',
    maxWidth: 600,
    textAlign: 'left',
  },
  extendedIcon: {
    marginRight: theme.spacing(1),
  },
  formControl: {
    margin: theme.spacing(1),
    minWidth: 120,
    maxWidth: 300,
  },
  textField: {
    marginLeft: theme.spacing(1),
    marginRight: theme.spacing(1),
    width: 370,
  },
  dense: {
    marginTop: 19,
  },
  chips: {
    display: 'flex',
    flexWrap: 'wrap',
  },
  chip: {
    margin: 2,
  },
  noLabel: {
    marginTop: theme.spacing(3),
  },
}));

const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
  PaperProps: {
    style: {
      maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
      width: 250,
    },
  },
};

function getStyles(name, accountName, theme) {
  return {
    fontWeight:
      accountName.indexOf(name) === -1
        ? theme.typography.fontWeightRegular
        : theme.typography.fontWeightMedium,
  };
}


// Failed to compile
// ./src/Form.js
//   Line 86:  Parsing error: Unexpected token, expected ","

//   84 | 
//   85 | const MediaDeliverableCheckBoxList = ({values, label, onMediaDeliverableChange}) => (
// > 86 |     {values.map((value, index) => (
//      |            ^
//   87 |         <MediaDeliverablesCheckBox
//   88 |         key={index}
//   89 |         index={index}
// This error occurred during the build time and cannot be dismissed.

const MediaDeliverableCheckBoxList = ({values, label, onMediaDeliverableChange}) => (
    {values.map((value, index) => (
        <MediaDeliverablesCheckBox
        key={index}
        index={index}
        mediaDeliverablesOptions={value}
        onMediaDeliverableChange={onMediaDeliverableChange(index)}
      />
      ))}
);

export default function Form(props) {

  const mediaDeliverablesOptions = props.mediaDeliverablesOptions;
  const classes = useStyles();
  const theme = useTheme();

  const CheckboxGroup = ({ values, label, onChange }) => (
  <FormControl component="fieldset">
    <FormLabel component="legend">{label}</FormLabel>
    <FormGroup>
      {values.map((value, index) => (
        <FormControlLabel
          key={index}
          control={
            <Checkbox
              checked={value.checked}
              onChange={onChange(index)}
            />
          }
          label={value.label}
        />
      ))}
    </FormGroup>
  </FormControl>
);


  const onCheckBoxChange = index => ({ target: { checked } }) => {
    const newValues = [...values];
    const value = values[index];
    newValues[index] = { ...value, checked };
    props.setDesignOrDigital(newValues);
  };

  const onMediaDeliverableChange = index => (deliverableData, e) => {
    props.setMediaDeliverable(deliverableData, index);
  }

  return (
    <div className={classes.root}>
      <MediaDeliverableCheckBoxList
        onMediaDeliverableChange={onMediaDeliverableChange}
      />
    </div>
  );
}

此问题完全是因为您在 TextField 中的 key。您必须确保密钥在每次更新时都保持不变。否则你会面临当前的问题。

我看到两个主要问题:

  1. 您如何定义不同的组件(嵌套组件类型)
  2. 没有将 index 属性传递给需要它的组件

你有以下结构(省略了与我的观点没有直接关系的细节):

export default function Form(props) {

  const onMediaDeliverableChange = index => (deliverableData, e) => {
    props.setMediaDeliverable(deliverableData, index);
  }

  const MediaDeliverableCheckBoxList = ({values, label}) => (
    <FormGroup>
    {values.map((value, index) => (
        <MediaDeliverablesCheckBox key={index} onMediaDeliverableChange={onMediaDeliverableChange(index)}/>
      ))}
    </FormGroup>
    );

  return (
      <MediaDeliverableCheckBoxList/>
  );
}

函数MediaDeliverableCheckBoxList表示用于呈现<MediaDeliverableCheckBoxList/>元素的组件类型。每当 Form 由于 props 或状态改变而 re-rendered 时,React 将 re-render 它的 children。如果特定 child 的组件类型相同(加上一些其他条件,例如 key 如果指定相同),那么它将更新现有的 DOM 节点。如果特定 child 的组件类型是 不同的 ,则相应的 DOM 节点将被 删除 和新的添加到 DOM.

通过在 Form 函数中 定义 MediaDeliverableCheckBoxList 组件类型 ,您将导致该组件类型在每次渲染时都不同。这将导致所有 DOM 节点被替换,而不仅仅是更新,这将导致焦点在先前具有焦点的 DOM 节点被移除时消失。它还会导致性能大大降低。

您可以通过将此组件类型移到 Form 函数之外,然后添加所需的任何其他道具(例如 onMediaDeliverableChange)来解决此问题,以传达 [=14] 内部已知的上下文=].您还需要将索引作为道具传递给 MediaDeliverablesCheckBox,因为它正在使用它。

const MediaDeliverableCheckBoxList = ({values, label, onMediaDeliverableChange}) => (
    <FormGroup>
    {values.map((value, index) => (
        <MediaDeliverablesCheckBox key={index} index={index} onMediaDeliverableChange={onMediaDeliverableChange(index)}/>
      ))}
    </FormGroup>
);


export default function Form(props) {

  const onMediaDeliverableChange = index => (deliverableData, e) => {
    props.setMediaDeliverable(deliverableData, index);
  }


  return (
      <MediaDeliverableCheckBoxList onMediaDeliverableChange={onMediaDeliverableChange}/>
  );
}

您对 CheckboxGroup 和其他组件可能也有同样的问题。