【问题标题】:Why does React text input lose focus after each keystroke?为什么每次击键后 React 文本输入都会失去焦点?
【发布时间】:2020-10-22 14:36:01
【问题描述】:

在使用 Material-UI v4.11.0 的 React v16.13.1 中,我有以下组件

  const GuestSignup = (props: GuestSignupProps) => {

    return <Grid container spacing={1} alignItems="flex-end">
      <Grid item xs={1}>
        <Tooltip title="Uncheck to cancel individual signup" arrow>
          <FormControl>
            <FormControlLabel
              control={<Checkbox key="ck1" onChange={props.handleSignupCheckChangeGuest(props.gs.guestSignupID === global.NULL_ID ? props.gs.index : props.gs.guestSignupID)} value={props.gs.guestSignupID} checked={props.gs.selected} />}
              label=""
            />
          </FormControl>
        </Tooltip>
      </Grid>
      <Grid item xs={2}>
        <TextField label="First Name" value={props.gs.firstName} key={props.gs.guestSignupID.toString() + 'fn'} required onChange={props.handleFirstNameChange(props.gs.guestSignupID, props.gs.index)} />
      </Grid>
      <Grid item xs={2}>
        <TextField label="Last Name" value={props.gs.lastName} key={props.gs.guestSignupID.toString() + 'ln'} required onChange={props.handleLastNameChange(props.gs.guestSignupID, props.gs.index)} />
      </Grid>
      <Grid item xs={2}>
        <GuestTypes value={props.gs.guestType.guestTypeID} key="gt" gt={props.gt} handleGuestTypeChange={props.handleGuestTypeChange(props.gs.guestSignupID, props.gs.index)} />
      </Grid>
      <Grid item xs={2}>
        <KeyboardDatePicker
          autoOk
          className={classes.guestDate}
          disablePast
          disableToolbar
          variant="inline"
          format="MM/dd/yyyy"
          key="ad"
          margin="dense"
          label="Arrival"
          required
          value={props.gs.arrivalDate === global.EMPTY_STRING ? null : new Date(props.gs.arrivalDate)}
          onChange={props.handleArrivalDateChange(props.gs.guestSignupID === global.NULL_ID ? props.gs.index : props.gs.guestSignupID, GUEST_SIGNUP)}
          KeyboardButtonProps={{
            'aria-label': 'change date',
          }}
        />
      </Grid>
      <Grid item xs={2}>
        <KeyboardDatePicker
          autoOk
          className={classes.guestDate}
          disablePast
          disableToolbar
          variant="inline"
          format="MM/dd/yyyy"
          key="dd"
          margin="dense"
          label="Departure"
          required
          value={props.gs.departureDate === global.EMPTY_STRING ? null : new Date(props.gs.departureDate)}
          onChange={props.handleDepartureDateChange(props.gs.guestSignupID === global.NULL_ID ? props.gs.index : props.gs.guestSignupID, GUEST_SIGNUP)}
          KeyboardButtonProps={{
            'aria-label': 'change date',
          }}
        />
      </Grid>
    </Grid>
  }

使用这些输入道具

interface GuestSignupProps {
  id: number;
  key: number;
  gs: GuestSignup;
  gt: Array<GuestType>;
  handleArrivalDateChange(id: number, type: string): (date: Date | null) => void;
  handleDepartureDateChange(id: number, type: string): (date: Date | null) => void;
  handleFirstNameChange(id: number, index: number): (event: React.ChangeEvent<HTMLInputElement>) => void;
  handleGuestTypeChange(id: number, index: number): (event: React.ChangeEvent<{ value: unknown }>) => void;
  handleLastNameChange(id: number, index: number): (event: React.ChangeEvent<HTMLInputElement>) => void;
  handleSignupCheckChangeGuest(id: number): (event: React.ChangeEvent<HTMLInputElement>) => void;
}

使用这种数据结构

export interface GuestSignup {
  guestSignupID: number;
  index: number;
  signupID: number;
  firstName: string;
  lastName: string;
  gender: string;
  guestType: GuestType;
  arrivalDate: string;
  departureDate: string;
  cancelDate: string;
  notes: string;
  cancel: boolean;
  selected: boolean;
}

它作为多个实例的数组存储在状态中

  const [guestSignups, setGuestSignups] = React.useState<Array<GuestSignup>>([]);

并在页面中有多个 GuestSignup 实例的情况下呈现。

  {guestSignups.length > 0 &&
    <React.Fragment>
      {
        guestSignups.map((gs: GuestSignup, index: number) => (
          <GuestSignup id={gs.guestSignupID} key={gs.guestSignupID > 0 ? gs.guestSignupID : index} gs={gs} gt={guestTypes} handleArrivalDateChange={handleArrivalDateChange} handleDepartureDateChange={handleDepartureDateChange} handleFirstNameChange={handleFirstNameChange} handleGuestTypeChange={handleGuestTypeChange} handleLastNameChange={handleLastNameChange} handleSignupCheckChangeGuest={handleSignupCheckChangeGuest} />
        ))
      }
    </React.Fragment>
  }

名字和姓氏都有处理程序,每次按键都会在 onChange 中调用

  const handleFirstNameChange = (id: number, index: number) => (event: React.ChangeEvent<HTMLInputElement>) => {

    // Used either GuestSignupID for existing guests or array index for new guests
    const newGuestSignup = produce(guestSignups,
      draft => {
        const i = draft.findIndex(gs => { return gs.guestSignupID > 0 ? gs.guestSignupID === id : gs.index === index });
        draft[i].firstName = event.target.value;
      }
    );

    setGuestSignups([ ...newGuestSignup ]);

  }

问题在于,每次击键后,名字和姓氏文本输入都会失去焦点。他们在兄弟姐妹中拥有唯一的钥匙。似乎整个访客注册集在处理程序之后重新呈现,因为如果您点击 TAB,最顶部的访客注册中的第一个复选框将获得焦点。

让文本输入元素在每次击键后保持焦点的正确方法是什么?

【问题讨论】:

  • 不建议您将key 用于GuestSignup。由于key 帮助 React 理解重绘,您使用它的方式可能导致这些重绘,这会使输入失去焦点(因为它们将是每次重绘的新输入)跨度>
  • 为什么你的TextField 元素有一个key?只有在循环渲染多个相同类型的组件时才需要键。
  • 请在code sandbox 中重现您的问题。这可能是与this question 类似的根本原因,但如果没有完整的复制则无法确定。
  • 由于所有元素都在 GRID 单元格中,从 React 的角度来看,它们是否被视为兄弟姐妹?它们真的必须是元素兄弟,还是 React 只考虑与渲染相关的相邻元素的兄弟? React 文档中的简单 UL 是直截了当的。上面的结构呢?试图了解什么需要keyl
  • @ChrisP 您只需要在渲染元素数组时使用键(例如,在 .map 调用中渲染的元素,例如您的 GuestSignup 元素)。当元素被显式布局时,键不是必需的(尽管它们也不会伤害任何东西,只要键不改变给定元素)。

标签: javascript reactjs typescript material-ui


【解决方案1】:

几个月前我也遇到了同样的问题。您需要从所有 TextField 标记中删除 key 属性。如果你添加 key 属性,React 将在 key 发生变化时重新渲染组件并且你的输入将失去焦点。你可以从here阅读更多关于 key 的信息。

【讨论】:

  • 这个答案缺少推理。为什么要删除所有TextField 标签中的所有key 属性?为什么拥有这些键会导致所描述的行为?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-02-18
  • 2017-08-06
  • 1970-01-01
  • 1970-01-01
  • 2017-07-23
  • 2022-01-16
  • 2020-01-21
相关资源
最近更新 更多