You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React表单输入组件:聚焦时标签未同步添加active类的问题

问题分析

你当前实现的核心问题在于:const active = document.activeElement === internalRef.current 是组件渲染时的一次性计算值,但这个值的变化不会触发组件重新渲染。只有当组件的 props(比如输入内容变化导致props.value更新)或 state 改变时,组件才会重新计算该变量,所以标签的active类只会在输入内容时更新,无法和输入框的:focus状态同步。

解决方法

用 React 的状态来跟踪输入框的聚焦状态,通过onFocus和onBlur事件更新状态,状态变化会自动触发组件重渲染,从而让标签的active类和输入框的聚焦状态完全同步。

修改后的完整代码
import React, { forwardRef, ReactElement, ReactNode, HTMLProps, useImperativeHandle, useState } from 'react'
import styles from './index.module.css'
import classNames from 'classnames'
import { IconFa } from '../icon-fa'
import { useStableId } from '../use-stable-id'

export interface Props extends HTMLProps<HTMLInputElement> {
  // An icon component to show on the left.
  icon?: ReactNode
  // A help text tooltip to show on the right.
  help?: string
  // A clear text tooltip to show on the right.
  clear?: string
  // Pass an onHelp click handler to show a help button.
  onHelp?: () => void
  // Pass an onClear click handler to show a clear button.
  onClear?: () => void
  errorMessage?: ReactNode
  label?: string
  hideLabel?: boolean
}

export const FormInput = forwardRef<HTMLInputElement, Props>(function FormInput(props, ref): ReactElement {
  const { id, icon, help, hideLabel, errorMessage, onHelp, onClear, ...rest } = props
  const internalRef = React.useRef<HTMLInputElement>(null)
  useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(ref, () => internalRef.current)
  const stableId = useStableId()
  const inputId = id ? id : stableId
  // 用状态跟踪输入框聚焦状态
  const [isFocused, setIsFocused] = useState(false)

  const inputClassName = classNames(
    styles.input,
    icon && styles.hasLeftIcon,
    (help || onHelp || onClear) && styles.hasRightIcon
  )

  const labelClassName = classNames(
    styles.label,
    Boolean(props.value) && styles.hasValue,
    props['aria-invalid'] && styles.hasError,
    // 使用聚焦状态控制active类
    isFocused && styles.active,
    props.className
  )

  return (
    <div className={labelClassName}>
      {!hideLabel && (
        <label className={styles.labelText} htmlFor={inputId}>
          {props.label}
          {props.required && '*'}
        </label>
      )}
      <div className="relative">
        {icon && <div className={styles.leftIcon}>{icon}</div>}
        <input
          {...rest}
          id={inputId}
          ref={internalRef}
          aria-invalid={props['aria-invalid']}
          aria-label={props.hideLabel ? props.label : undefined}
          className={inputClassName}
          // 添加聚焦/失焦事件更新状态
          onFocus={() => setIsFocused(true)}
          onBlur={() => setIsFocused(false)}
        />
        {onClear && <ClearIcon {...props} />}
        {!onClear && (help || onHelp) && <HelpIcon {...props} />}
      </div>
      {props['aria-invalid'] && <span className={styles.error}>{errorMessage}</span>}
    </div>
  )
})

function HelpIcon(props: Props) {
  if (props.onHelp) {
    return (
      <button type="button" className={styles.rightIcon} aria-label={props.help} onClick={props.onHelp}>
        <IconFa icon={['far', 'info-circle']} title={props.help} />
      </button>
    )
  }

  return (
    <div className={styles.rightIcon} title={props.help}>
      <IconFa icon={['far', 'info-circle']} />
    </div>
  )
}

function ClearIcon(props: Props) {
  return (
    <button type="button" className={styles.rightIcon} aria-label={props.clear} onClick={props.onClear}>
      <IconFa icon={['far', 'times']} title={props.clear} />
    </button>
  )
}
关键修改点
  • 引入useState创建isFocused状态变量,专门跟踪输入框的聚焦状态
  • 给<input>元素添加onFocus和onBlur事件,实时更新聚焦状态
  • 将标签类名中的active判断逻辑,从document.activeElement替换为isFocused状态

修改后,输入框聚焦时会立即触发状态更新,组件重渲染并给标签添加active类,和输入框的:focus样式完全同步。

内容的提问来源于stack exchange,提问作者Ludwig

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 07:55:29