首页 > 解决方案 > 上下文中的 useState 挂钩重置未聚焦的输入框

问题描述

我的项目采用了一个显示名称,我希望将其保存在上下文中以供将来的组件使用以及发布到数据库时使用。所以,我有一个在上下文中设置名称的 onChange 函数,但是当它设置名称时,它会从输入框中消除焦点。这使得您一次只能输入一个字母的显示名称。状态正在更新,并且有一个 useEffect 将其添加到本地存储。我已经删除了该代码,它似乎不会影响这是否有效。

输入框不止一个,所以自动对焦属性不起作用。我曾尝试使用 .focus() 方法,但由于 useState 的 Set 部分不会立即发生,因此没有奏效。我尝试通过在 onChange 函数中设置值而不更改问题来使其成为受控输入。类似问题的其他答案在其代码中存在其他问题,导致其无法正常工作。

零件:

import React, { useContext } from 'react';
import { ParticipantContext } from '../../../contexts/ParticipantContext';

const Component = () => {
  const { participant, SetParticipantName } = useContext(ParticipantContext);

  const DisplayNameChange = (e) => {
    SetParticipantName(e.target.value);
  }

  return (
    <div className='inputBoxParent'>
      <input 
        type="text" 
        placeholder="Display Name" 
        className='inputBox'
        onChange={DisplayNameChange}
        defaultValue={participant.name || ''} />
    </div>
  )
}

export default Component;

语境:

import React, { createContext, useState, useEffect } from 'react';

export const ParticipantContext = createContext();

const ParticipantContextProvider = (props) => {
  const [participant, SetParticipant] = useState(() => {
    return GetLocalData('participant', 
      {
        name: '',
        avatar: {
          name: 'square',
          imgURL: 'square.png'
        }
    });
  });

  const SetParticipantName = (name) => {
    SetParticipant({ ...participant, name });
  }

  useEffect(() => {
    if (participant.name) {
      localStorage.setItem('participant', JSON.stringify(participant))
    }
  }, [participant])

  return ( 
    <ParticipantContext.Provider value={{ participant, SetParticipant, SetParticipantName }}>
      { props.children }
    </ParticipantContext.Provider>
  );
}

export default ParticipantContextProvider;

组件的父级:

import React from 'react'
import ParticipantContextProvider from './ParticipantContext';
import Component from '../components/Component';

const ParentOfComponent = () => {
  return (
    <ParticipantContextProvider>
      <Component />
    </ParticipantContextProvider>
  );
}

export default ParentOfComponent;

这是我的第一篇文章,所以如果您需要有关该问题的其他信息,请告诉我。提前感谢您提供的任何帮助。

标签: reactjsinputuse-stateuse-context

解决方案


What is most likely happening here is that the context change is triggering an unmount and remount of your input component.

A few ideas off the top of my head:

  1. Try passing props directly through the context provider:
// this
<ParticipantContext.Provider
  value={{ participant, SetParticipant, SetParticipantName }}
  {...props}
/>

// instead of this
<ParticipantContext.Provider
  value={{ participant, SetParticipant, SetParticipantName }}
>
  { props.children }
</ParticipantContext.Provider>

I'm not sure this will make any difference—I'd have to think about it—but it's possible that the way you have it (with { props.children } as a child of the context provider) is causing unnecessary re-renders.

If that doesn't fix it, I have a few other ideas:

  1. Update context on blur instead of on change. This would avoid the context triggering a unmount/remount issue, but might be problematic if your field gets auto-filled by a user's browser.

  2. Another possibility to consider would be whether you could keep it in component state until unmount, and set context via an effect cleanup:

const [name, setName] = useState('');
useEffect(() => () => SetParticipant({ ...participant, name }), [])
<input value={name} onChange={(e) => setName(e.target.value)} />
  1. You might also consider setting up a hook that reads/writes to storage instead of using context:
const useDisplayName = () => {
  const [participant, setParticipant] = useState(JSON.parse(localStorage.getItem('participant') || {}));
  const updateName = newName => localStorage.setItem('participant', {...participant, name} );
  return [name, updateName];
}

Then your input component (and others) could get and set the name without context:

const [name, setName] = useDisplayName();
<input value={name} onChange={(e) => setName(e.target.value)} />

推荐阅读