React输入文本字段 – 如何在触摸时聚焦?

huangapple go评论60阅读模式
英文:

React Input Text Field - How to Focus on Touch?

问题

我有以下的输入组件,我在很多地方都在重复使用它。在桌面上它工作得非常好,但在移动设备上,我似乎无法使它在按下时获得焦点。我不确定我做错了什么,而且我搜索的所有内容都显示React Native,而我没有使用React Native。我看到一些建议是添加一个按钮,但那对我来说似乎很奇怪。我应该在输入上添加一个 onClick 处理程序吗?

import React, { useRef } from 'react';

const Input = ({
  label,
  type,
  name,
  placeholder,
  autoComplete,
  required,
  className,
  disabled,
  onChange,
}) => {
  const inputRef = useRef(null);

  const handleFocus = () => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  return (
    <div className={`${!!label ? 'form-floating' : ''}`}>
      <input
        ref={inputRef}
        className={`form-control ${className ?? ''}`}
        type={type}
        name={name}
        id={name}
        placeholder={placeholder}
        required={required}
        autoComplete={autoComplete}
        disabled={disabled}
        onChange={(e) => onChange({ payload: e })}
        onClick={handleFocus}
      />
      {!!label && <label htmlFor={name}>{placeholder}</label>}
    </div>
  );
}

export default Input;

我期望光标弹出并且设备上的键盘弹出(在移动设备上),但什么都没有发生。我知道我可以设置 autoFocus,但这并没有帮助,因为它是一个可重用的组件,不能在页面上的多个实例上都设置 autoFocus。

编辑:更新为使用 onClick 而不是 onFocus。仍然不起作用。这些输入框位于一个模态框中,如果有什么不同的地方,请告诉我。以下是登录表单的代码:

import { useEffect, useState, useReducer } from 'react';
import { useLoginMutation, useGoogleLoginMutation } from './authApiSlice';
import usePersist from '../../hooks/usePersist';
import { useNavigate } from 'react-router-dom';
import { useGoogleLogin } from '@react-oauth/google';
import formReducer from '../../hooks/useReducer';
import Input from '../../components/Input';

// ...(省略了一些代码)...

const Login = () => {
  // Login hook
  const [login, { isLoading }] = useLoginMutation();
  const [googleLogin, { isLoading: isGoogleLoading }] = useGoogleLoginMutation();

  // ...(省略了一些代码)...

  const content = (
    <form>
      <div className='position-relative mb-3'>
        <Input
          type='email'
          className={`${state.validUClass && state.validUClass === false ? 'is-invalid' : ''}`}
          name='username'
          label={true}
          placeholder='Email Address'
          autoComplete='username'
          onChange={setState}
        />
        <div id='usernameFeedback' className='invalid-feedback'>
          Username must be a valid email address.
        </div>
      </div>
      <div className='input-group mb-3 has-validation'>
        <Input
          type={state.showPassword ? 'text' : 'password'}
          className={`${state.validPClass && state.validPClass === false ? 'is-invalid' : ''}`}
          name='password'
          placeholder='Password'
          label={true}
          autoComplete='password'
          onChange={setState}
          aria-describedby='eye-addon'
        />
        <div id='passwordFeedback' className='invalid-feedback'>
          Password must contain 1 uppercase, lowercase, numeric, and special character
        </div>
        <span
          className='input-group-text bi bi-eye>'
          id='eye-addon'
          onClick={() =>
            setState({
              payload: {
                target: { name: 'showPassword', value: !state.showPassword },
              },
            })
          }
        >
          <i
            className={state.showPassword ? 'bi bi-eye-slash' : 'bi bi-eye'}
          ></i>
        </span>
      </div>
      <!-- 其他表单元素 -->
    </form>
  );

  return content;
}

export default Login;

以及模态框代码:

import { useEffect, useCallback } from 'react';
import { Outlet, useNavigate, useLocation } from 'react-router-dom';

const Modal = () => {
  const location = useLocation();
  const navigate = useNavigate();

  // ...(省略了一些代码)...

  const content = (
    <div
      className='modal d-block bg-body-tertiary bg-opacity-50 py-md-5'
      tabIndex='-1'
      role='dialog'
      id='modal'
      onClick={() => navigate(-1)}
    >
      <div
        className='modal-dialog modal-fullscreen-sm-down modal-dialog-centered'
        role='document'
        onClick={(e) => e.stopPropagation()}
      >
        <div className='modal-content rounded-4 shadow'>
          <div className='modal-header p-5 pb-4 border-bottom-0'>
            <h1 className='fw-bold mb-0 fs-2 ms-auto'>{location.state.context}</h1>
            <button
              type='button'
              className='btn-close'
              aria-label='Close'
              onClick={() => navigate(-1)}
            ></button>
          </div>
          <div className='modal-body p-5 pt-0'>
            <Outlet />
          </div>
        </div>
      </div>
    </div>
  );

  return content;
}

export default Modal;

希望这可以帮助你解决问题。如果你需要更多的帮助,请随时提问。

英文:

I have the following input component that I'm reusing all over the place. It works absolutely great on desktop, but on mobile, I can't seem to get it to focus on press. I'm not sure what I'm doing wrong and everything I search is showing React Native which I'm not using. I saw some suggestions around adding a button, but that just seems weird to me. Should I add an onClick handler to the input?

import React, { useRef } from &#39;react&#39;
const Input = ({
label,
type,
name,
placeholder,
autoComplete,
required,
className,
disabled,
onChange,
}) =&gt; {
const inputRef = useRef(null)
const handleFocus = () =&gt; {
if (inputRef.current) {
inputRef.current.focus()
}
}
return (
&lt;div className={`${!!label ? &#39;form-floating&#39; : &#39;&#39;}`}&gt;
&lt;input
ref={inputRef}
className={`form-control ${className ?? &#39;&#39;}`}
type={type}
name={name}
id={name}
placeholder={placeholder}
required={required}
autoComplete={autoComplete}
disabled={disabled}
onChange={(e) =&gt; onChange({ payload: e })}
onClick={handleFocus}
/&gt;
{!!label &amp;&amp; &lt;label htmlFor={name}&gt;{placeholder}&lt;/label&gt;}     
&lt;/div&gt;
)
}
export default Input

I expected the cursor to pop up and the keyboard of the device to popup (on mobile), but nothing happened. I know I can set autoFocus, but that doesn't help since it's a re-useable component and can't have autoFocus on multiple instances on a page.

EDIT: Updating to use onClick instead of onFocus. Still does not work. The Inputs are in a modal if that makes a difference. Here's the Login form code

import { useEffect, useState, useReducer } from &#39;react&#39;
import { useLoginMutation, useGoogleLoginMutation } from &#39;./authApiSlice&#39;
import usePersist from &#39;../../hooks/usePersist&#39;
import { useNavigate } from &#39;react-router-dom&#39;
import { useGoogleLogin } from &#39;@react-oauth/google&#39;
import formReducer from &#39;../../hooks/useReducer&#39;
import Input from &#39;../../components/Input&#39;
const PWD_REGEX =
/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&amp;*-]).{8,15}$/
const USER_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
const Login = () =&gt; {
// Login hook
const [login, { isLoading }] = useLoginMutation()
const [googleLogin, { isLoading: isGoogleLoading }] = useGoogleLoginMutation()
const navigate = useNavigate()
const [state, setState] = useReducer(formReducer, {})
const [persist, setPersist] = usePersist()
const [errMsg, setErrMsg] = useState(&#39;&#39;)
useEffect(() =&gt; {
setState({
payload: {
target: {
name: &#39;validUsername&#39;,
value: USER_REGEX.test(state.username),
},
},
})
}, [state.username])
useEffect(() =&gt; {
setState({
payload: {
target: {
name: &#39;validPassword&#39;,
value: PWD_REGEX.test(state.password),
},
},
})
}, [state.password])
// Login function
const onLoginClicked = async () =&gt; {
if (!state.validUsername) {
setState({ payload: { target: { name: &#39;validUClass&#39;, value: false } } })
} else if (!state.validPassword) {
setState({ payload: { target: { name: &#39;validPClass&#39;, value: false } } })
} else {
try {
await login({ username: state.username, password: state.password })
navigate(&#39;/&#39;)
} catch (err) {
if (!err.status) {
setErrMsg(&#39;No Server Response&#39;)
} else {
setErrMsg(err.message)
}
}
}
}
const onGoogleLoginClicked = useGoogleLogin({
onSuccess: async ({ code }) =&gt; {
try {
await googleLogin({ code })
navigate(&#39;/&#39;)
} catch (err) {
console.log(err.message)
}
},
flow: &#39;auth-code&#39;,
})
const content = (
&lt;form&gt;
&lt;div className=&#39;position-relative mb-3&#39;&gt;
&lt;Input
type=&#39;email&#39;
className={`${
state.validUClass &amp;&amp; state.validUClass === false ? &#39;is-invalid&#39; : &#39;&#39;
}`}
name=&#39;username&#39;
label={true}
placeholder=&#39;Email Address&#39;
autoComplete=&#39;username&#39;
onChange={setState}
/&gt;
&lt;div id=&#39;usernameFeedback&#39; className=&#39;invalid-feedback&#39;&gt;
Username must be a valid email address.
&lt;/div&gt;
&lt;/div&gt;
&lt;div className=&#39;input-group mb-3 has-validation&#39;&gt;
&lt;Input
type={state.showPassword ? &#39;text&#39; : &#39;password&#39;}
className={`${
state.validPClass &amp;&amp; state.validPClass === false ? &#39;is-invalid&#39; : &#39;&#39;
}`}
name=&#39;password&#39;
placeholder=&#39;Password&#39;
label={true}
autoComplete=&#39;password&#39;
onChange={setState}
aria-describedby=&#39;eye-addon&#39;
/&gt;
&lt;div id=&#39;passwordFeedback&#39; className=&#39;invalid-feedback&#39;&gt;
Password must contain 1 uppercase, lowercase, numeric, and special
character
&lt;/div&gt;
&lt;span
className=&#39;input-group-text bi bi-eye&gt;&#39;
id=&#39;eye-addon&#39;
onClick={() =&gt;
setState({
payload: {
target: { name: &#39;showPassword&#39;, value: !state.showPassword },
},
})
}
&gt;
&lt;i
className={state.showPassword ? &#39;bi bi-eye-slash&#39; : &#39;bi bi-eye&#39;}
&gt;&lt;/i&gt;
&lt;/span&gt;
&lt;/div&gt;
&lt;div className=&#39;pb-2&#39;&gt;
&lt;input
type=&#39;checkbox&#39;
className=&#39;form-check-input&#39;
id=&#39;persist&#39;
onChange={() =&gt; setPersist((prev) =&gt; !prev)}
checked={persist}
/&gt;
&lt;label htmlFor=&#39;persist&#39; className=&#39;ps-2&#39;&gt;
Trust This Device
&lt;/label&gt;
&lt;/div&gt;
&lt;button
className=&#39;w-100 mb-2 btn btn-lg rounded-3 btn-primary&#39;
type=&#39;submit&#39;
disabled={state?.submitting}
onClick={(e) =&gt; {
e.preventDefault()
onLoginClicked()
}}
&gt;
{(!isLoading || !isGoogleLoading) &amp;&amp; !state.submitting
? &#39;Submit&#39;
: &#39;Submitting&#39;}
&lt;/button&gt;
{errMsg &amp;&amp; &lt;div className=&#39;register-error&#39;&gt;{errMsg}&lt;/div&gt;}
&lt;div className=&#39;text-body-secondary text-center&#39;&gt;
By clicking Submit, you agree to the terms of use.
&lt;/div&gt;
&lt;hr className=&#39;my-4&#39; /&gt;
&lt;h2 className=&#39;fs-5 fw-bold mb-3 text-center&#39;&gt;Or use a third-party&lt;/h2&gt;
&lt;button
className=&#39;w-100 py-2 mb-2 btn btn-outline-primary rounded-3&#39;
type=&#39;button&#39;
onClick={onGoogleLoginClicked}
&gt;
&lt;i className=&#39;bi bi-google&#39;&gt;&lt;/i&gt; Login with Google
&lt;/button&gt;
&lt;button
className=&#39;w-100 py-2 mb-2 btn btn-outline-primary rounded-3&#39;
type=&#39;button&#39;
&gt;
&lt;i className=&#39;bi bi-facebook&#39;&gt;&lt;/i&gt; Login with Facebook
&lt;/button&gt;
&lt;button
className=&#39;w-100 py-2 mb-2 btn btn-outline-primary rounded-3&#39;
type=&#39;button&#39;
&gt;
&lt;i className=&#39;bi bi-twitch&#39;&gt;&lt;/i&gt; Login with Twitch
&lt;/button&gt;
&lt;/form&gt;
)
return content
}
export default Login

And the modal code

import { useEffect, useCallback } from &#39;react&#39;
import { Outlet, useNavigate, useLocation } from &#39;react-router-dom&#39;
const Modal = () =&gt; {
const location = useLocation()
const navigate = useNavigate()
// Closes modal when Escape is pressed
const handleEscKey = useCallback(
(event) =&gt; {
if (event.key === &#39;Escape&#39;) {
navigate(-1)
}
},
[navigate]
)
// Listens for Key to close modal
useEffect(() =&gt; {
document.addEventListener(&#39;keyup&#39;, handleEscKey, false)
return () =&gt; {
document.removeEventListener(&#39;keyup&#39;, handleEscKey, false)
}
}, [handleEscKey])
const content = (
// Modal background
&lt;div
className=&#39;modal d-block bg-body-tertiary bg-opacity-50 py-md-5&#39;
tabIndex=&#39;-1&#39;
role=&#39;dialog&#39;
id=&#39;modal&#39;
onClick={() =&gt; navigate(-1)}
&gt;
&lt;div // Modal body
className=&#39;modal-dialog modal-fullscreen-sm-down modal-dialog-centered&#39;
role=&#39;document&#39;
onClick={(e) =&gt; e.stopPropagation()}
&gt;
&lt;div className=&#39;modal-content rounded-4 shadow&#39;&gt;
&lt;div className=&#39;modal-header p-5 pb-4 border-bottom-0&#39;&gt;
&lt;h1 className=&#39;fw-bold mb-0 fs-2 ms-auto&#39;&gt;
{location.state.context}
&lt;/h1&gt;
&lt;button
type=&#39;button&#39;
className=&#39;btn-close&#39;
aria-label=&#39;Close&#39;
onClick={() =&gt; navigate(-1)}
&gt;&lt;/button&gt;
&lt;/div&gt;
&lt;div className=&#39;modal-body p-5 pt-0&#39;&gt;
&lt;Outlet /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
)
return content
}
export default Modal

答案1

得分: 1

为了在移动设备上使输入字段可聚焦,您可以使用 onClick 事件处理程序,而不是 onFocus。这将允许用户点击字段以获得焦点,从而在智能手机上自动打开键盘。

更新

在输入字段的 onClick 处理程序中添加了 e.stopPropagation()。这将防止模态框接收点击事件,并允许输入字段获得焦点。

以下是示例

import React, { useRef } from 'react';

const Input = ({
  label,
  type,
  name,
  placeholder,
  autoComplete,
  required,
  className,
  disabled,
  onChange,
}) => {
  const inputRef = useRef(null);

  const handleFocus = () => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  return (
    <div className={`${!!label ? 'form-floating' : ''}`}>
      <input
        ref={inputRef}
        className={`form-control ${className ?? ''}`}
        type={type}
        name={name}
        id={name}
        placeholder={placeholder}
        required={required}
        autoComplete={autoComplete}
        disabled={disabled}
        onChange={(e) => onChange({ payload: e })}
        onClick={(e) => {
          e.stopPropagation(); // 阻止事件传播在这里
          handleFocus();
        }}
      />
      {!!label && <label htmlFor={name}>{placeholder}</label>}
    </div>
  );
};

export default Input;

请注意,这是代码的翻译部分。

英文:

To make the input field focusable on mobile devices, you can use the onClick event handler rather than onFocus. This will allow users to tap on the field to give focus which will automatically open the keyboard on the smartphones.

Update:

Added e.stopPropagation() to the onClick handler of the input field. This will prevent the modal from receiving the click event and allow the input field to receive focus instead.

Here is the example:

import React, { useRef } from &#39;react&#39;;
const Input = ({
label,
type,
name,
placeholder,
autoComplete,
required,
className,
disabled,
onChange,
}) =&gt; {
const inputRef = useRef(null);
const handleFocus = () =&gt; {
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
&lt;div className={`${!!label ? &#39;form-floating&#39; : &#39;&#39;}`}&gt;
&lt;input
ref={inputRef}
className={`form-control ${className ?? &#39;&#39;}`}
type={type}
name={name}
id={name}
placeholder={placeholder}
required={required}
autoComplete={autoComplete}
disabled={disabled}
onChange={(e) =&gt; onChange({ payload: e })}
onClick={(e) =&gt; {
e.stopPropagation(); // Stop event propagation here
handleFocus();
}}
/&gt;
{!!label &amp;&amp; &lt;label htmlFor={name}&gt;{placeholder}&lt;/label&gt;}
&lt;/div&gt;
);
};
export default Input;

huangapple
  • 本文由 发表于 2023年7月13日 21:59:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76680225.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定