1
resposta

Como colocar o foco do teclado no último input modificado após a re-renderização do componente?

Tenho um componente com dois inputs. Quando o valor de um desses é alterado, o estado do componente pai é atualizado forçando a re-renderização dos filhos e, com isso, o foco do teclado sai do input que estava recebendo texto. Quero que o foco mantenha-se sobre o mesmo input.

Tentei colocar a propriedade autoFocus em cada um, porém após a renderização o foco recai sempre sobre o

Sugestões de como resolver?

Componente

const TodoItem = ({ tagsList, todo, todoIndex, addNewTodo, idBuilder }) => {
  // Propriedades do objeto todo
  const { title, body, tag, status, isValid } = todo;

  const isInitialMount = useRef(true);

  const [todoItemState, dispatchTodoItem] = useReducer(todoItemReducer, {
    title: title,
    body: body,
    tag: tag,
    status: status,
    isValid: isValid
  });

  const {tag: todoTag, status: todoStatus} = todoItemState;

  useEffect(() => {
    if (isInitialMount.current) {
      isInitialMount.current = false;
    } else {
      addNewTodo(todoIndex, todoItemState);
    }
  });

  function inputChangeHandler(event, type) {
    dispatchTodoItem({type: type, val: event.target.value});
  }

  function statusChangeHandler() {
    dispatchTodoItem({type: TODO_STATUS});
  }

  function handleButtonType() {
    if (todoItemState.status === false && todoItemState.isValid === false) {
      return 'Cancel'
    }
    if (todoItemState.status === false && todoItemState.isValid === true) {
      return 'Feito'
    }
    return 'Edit';
  }

  return (
    <TodoItemStyled>
      // ============VEJA AQUI=============
      <InputTitle
        defaultValue={title} 
        disabled={todoItemState.status} 
        onChange={(e) => inputChangeHandler(e, TODO_TITLE)}
        autoFocus
      />
      <InputTextArea 
        defaultValue={body} 
        disabled={todoItemState.status}
        onChange={(e) => inputChangeHandler(e, TODO_BODY)}
        autoFocus
      />
     // =========================
      {
        !todoItemState.status 
        &&
        <select onChange={(e) => inputChangeHandler(e, TODO_TAG)} value={todoTag}>
          {tagsList.map(tag => {
            const {tagName} = tag;
            return (
              <option key={idBuilder()}>{tagName}</option>
            )
          })}
        </select>
      }
      <button 
        onClick={(e) => {
          e.preventDefault();
          statusChangeHandler();
        }}
      >
        {handleButtonType()}
      </button>
    </TodoItemStyled>
  );
}

export default TodoItem;
1 resposta

Olá, Leonardo! Tudo bem?

Você pode usar o useRef para guardar as referências aos inputs, e um estado para guardar a referência do input ativo. Com isso, você precisaria de um useEffect que a cada alteração dos estados modificados pelos inputs, faça o ref.current.focus() no input ativo e usar o onFocus para disparar a mudança de estado do seu input ativo.

Então, os pontos seriam:

  • Os useRef para cada input;
  • Um estado activeInput que guarda o ref do input ativo;
  • Um useEffect que fique assitindo as alterações que causam as renderizações e execute o focus() no activeInput;
  • No JSX, usar o onFocus para alterar o ref do activeInput (talvez isso gere um loop, então teria que criar uma função que verificasse se o activeInput já é esse ref, se for, não fazer nada).

Espero que ajude!