【问题标题】:React Beginner Question: Textfield Losing Focus On UpdateReact 初学者问题:Textfield 失去对更新的关注
【发布时间】:2019-07-03 15:51:24
【问题描述】:

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

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

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

似乎仍然无法弄清楚是什么导致它失去焦点...有人对此问题有任何建议/解决方案吗?

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

我仍然是初学者/正在学习,所以如果您对此代码的任何部分有有用的建议,请随时提供。谢谢!

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>
  );
}

【问题讨论】:

    标签: javascript reactjs forms focus material-ui


    【解决方案1】:

    我看到两个主要问题:

    1. 如何定义不同的组件(嵌套组件类型)
    2. 不将 index prop 传递给期望它的组件

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

    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 表示用于渲染&lt;MediaDeliverableCheckBoxList/&gt; 元素的组件类型。每当 Form 由于 props 或 state 改变而重新渲染时,React 都会重新渲染它的孩子。如果特定子组件的组件类型相同(加上一些其他条件,例如key,如果指定也相同),那么它将更新现有的 DOM 节点。如果特定子组件的组件类型不同,则相应的 DOM 节点将被移除,并在 DOM 中添加新的节点。

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

    您可以通过将此组件类型移到 Form 函数之外,然后添加所需的任何其他道具(例如 onMediaDeliverableChange)来传达 Form 内部已知的上下文来解决此问题。您还需要将 index 作为 prop 传递给 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 可能还有其他组件也有同样的问题。

    【讨论】:

    • 嗨瑞恩,非常感谢您的解释。很有帮助。我尝试模拟您的代码结构。就像您的示例一样,我将 MediaDeliverableCheckBoxList 移到了表单函数之外,但现在它指向“values.map”中的句点并向我显示错误:“解析错误:意外的令牌,预期的','”..有原因吗为什么 React 没有正确读取这个函数?我不确定我是否理解这里为什么会出现问题。
    • 更改后我需要查看您的确切代码。我认为我的示例由于遗漏了诸如 FormGroup 之类的部分而存在一些轻微的语法问题。如果您将新代码添加到您的问题中,我可以帮助您找到语法问题。
    • 我用新的 form.js 更新了原始问题,并添加了我在代码之间得到评论的错误。让我知道你的想法...
    • 您只需在 MediaDeliverableCheckBoxList 中重新添加 FormControl、FormLabel 和 FormGroup 元素。我只是删除它们以消除我的主要观点的混乱,但在这个过程中我让它在语法上不正确——{values.map...} 部分需要在 JSX 元素内(例如&lt;FormGroup&gt;)。
    • 我成功了!我重新添加了这些元素,但唯一缺少的是参数“values”,该参数未定义,因此我将其添加到 MediaDeliverableCheckBoxList JSX 组件节点属性中,现在它似乎可以正常工作。非常感谢!!!
    【解决方案2】:

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

    【讨论】:

    • 我明白了...我为每个文本字段添加了一个键。我做错了吗?我有 "key={'tf'+props.index}" 应该给每个文本字段一个唯一的键,不是吗?
    • 每个表单元素都根据其表单键唯一标识。如果您有不同的表单,其文本字段具有相同的键,但这些表单具有不同的键,则它们将被唯一标识。不会有问题
    • 提供唯一键没有问题,但这些键不能随时间变化。一旦一个字段获得它的键,它必须在该组件的整个生命周期中保留该键
    • 我不相信我的文本字段键会随着时间而改变。我只有一个带有唯一键的表单组件。其中我只有一个“MediaDeliverablesCheckboxList”,它也有一个不变的唯一键。当该组件使用“map”遍历值时,它会为每个“MediaDeliverablesCheckBox”组件生成一个唯一键(我不相信这会改变吗?)。根据每个 MediaDeliverablesCheckBox 的索引值,每个文本字段都是唯一的。因此,除非这些键在渲染时以某种方式发生变化,否则它们似乎是独一无二且不可更改的。也许我不明白..
    • 好吧,做一件事!你在浏览器中启用了react developer tools 扩展吗?
    猜你喜欢
    • 2020-06-20
    • 1970-01-01
    • 2021-02-13
    • 2011-01-12
    • 2018-01-08
    • 2023-01-18
    • 2021-06-18
    • 2011-08-20
    相关资源
    最近更新 更多