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

如何通过keyEvents限制输入框数值范围并实现循环,解决时间选择器键盘输入越界问题

解决方案

你需要给输入框绑定onKeyDown事件拦截默认的上下键行为,同时补全输入校验规则即可,修改后的完整代码如下:

import { useState } from 'react';
import styled from 'styled-components';
import { IoIosArrowUp, IoIosArrowDown } from 'react-icons/io';

function TimePickerComponent({ hours, setHours, minutes, setMinutes }) {

  // INTERVAL STATES ARROWS
  const [showIntervalFirstHoursArrow, setShowIntervalFirstHoursArrow] = useState(false);
  const [showIntervalFirstMinutesArrow, setShowIntervalFirstMinutesArrow] = useState(false);

  // INCREMENTS/DECREMENTS HOURS INTERVALS
  const incrementFirstHour = () => {
    setHours((prevHours) => {
      if (!prevHours || +prevHours >= 24) {
        return '00';
      } else {
        return String(+prevHours + 1).padStart(2, '0');
      }
    });
  };

  const decrementFirstHours = () => {
    setHours((prevHours) => {
      if (!prevHours) {
        return '00';
      } else if (+prevHours <= 0) {
        return '24';
      } else {
        return String(+prevHours - 1).padStart(2, '0');
      }
    });
  };

  // INCREMENTS/DECREMENTS MINUTES INTERVALS
  const incrementFirstMinutes = () => {
    setMinutes((prevMinutes) => {
      if (!prevMinutes || +prevMinutes >= 59) {
        return '00';
      } else {
        return String(+prevMinutes + 1).padStart(2, '0');
      }
    });
  };
  const decrementFirstMinutes = () => {
    setMinutes((prevMinutes) => {
      if (!prevMinutes) {
        return '00';
      } else if (+prevMinutes <= 0) {
        return '59';
      } else {
        return String(+prevMinutes - 1).padStart(2, '0');
      }
    });
  };

  return (
    <Inputs>
      <Container>
        <Selection
          onMouseEnter={() => setShowIntervalFirstHoursArrow(true)}
          onMouseLeave={() => setShowIntervalFirstHoursArrow(false)}
        >
          {showIntervalFirstHoursArrow && (
            <p className="icon" onClick={incrementFirstHour}>
              <IoIosArrowUp />
            </p>
          )}
          <SquareInput
            type="number"
            onChange={(e) => {
              const val = e.target.value
              if (val.length > 2) return
              const numVal = +val
              if (numVal > 24 || numVal < 0) return
              setHours(val)
            }}
            onKeyDown={(e) => {
              if (e.key === 'ArrowUp') {
                e.preventDefault()
                incrementFirstHour()
              } else if (e.key === 'ArrowDown') {
                e.preventDefault()
                decrementFirstHours()
              }
            }}
            onBlur={() => {
              setHours(prev => {
                if (!prev) return '00'
                let num = +prev
                num = ((num % 25) + 25) % 25
                return String(num).padStart(2, '0')
              })
            }}
            value={hours}
          />
          {showIntervalFirstHoursArrow && (
            <p className="icon" onClick={decrementFirstHours}>
              <IoIosArrowDown />
            </p>
          )}
        </Selection>
        <p>:</p>
        <Selection
          onMouseEnter={() => setShowIntervalFirstMinutesArrow(true)}
          onMouseLeave={() => setShowIntervalFirstMinutesArrow(false)}
        >
          {showIntervalFirstMinutesArrow && (
            <p className="icon" onClick={incrementFirstMinutes}>
              <IoIosArrowUp />
            </p>
          )}
          <SquareInput
            min="0"
            type="number"
            onChange={(e) => {
              const val = e.target.value
              if (val.length > 2) return
              const numVal = +val
              if (numVal >= 60 || numVal < 0) return
              setMinutes(val)
            }}
            onKeyDown={(e) => {
              if (e.key === 'ArrowUp') {
                e.preventDefault()
                incrementFirstMinutes()
              } else if (e.key === 'ArrowDown') {
                e.preventDefault()
                decrementFirstMinutes()
              }
            }}
            onBlur={() => {
              setMinutes(prev => {
                if (!prev) return '00'
                let num = +prev
                num = ((num % 60) + 60) % 60
                return String(num).padStart(2, '0')
              })
            }}
            value={minutes}
          />
          {showIntervalFirstMinutesArrow && (
            <p className="icon" onClick={decrementFirstMinutes}>
              <IoIosArrowDown />
            </p>
          )}
        </Selection>
      </Container>
    </Inputs>
  );
}

// STYLES
const Inputs = styled.div`
  display: flex;
  align-items: center;

  p {
    font-size: 0.8em;
    text-align: center;
  }
`;
const Container = styled.div`
  border: 1px solid #c5c5c5;
  border-radius: 10px;
  height: 40px;
  margin: 0 5px;
  display: flex;
  align-items: center;
`;
const Selection = styled.div`
  .icon {
    font-size: 1.2em;
    padding: 10px;
    cursor: pointer;
  }
`;

const SquareInput = styled.input`
  width: 50px;
  border: none;
  outline: none;
  font-size: 1.1em;
  text-align: center;
  ::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }
  ::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }
  -moz-appearance: textfield;
`;

export default TimePickerComponent;

修改说明

  • 新增onKeyDown事件拦截上下方向键的默认行为,直接复用已有的增减逻辑,保证键盘调整和箭头点击的循环规则完全一致
  • 补全了小时输入框的onChange校验,限制输入长度最大为2位,同时禁止输入超出0-24范围的数值
  • 修正了分钟输入框的校验逻辑:将原判断阈值>60调整为>=60,小于0时返回59而非错误的60
  • 优化了增减函数的前导零处理逻辑,统一用padStart方法补零,代码更简洁
  • 新增onBlur失焦事件,用户主动输入完成离开输入框时,自动补全前导零、修正非法数值,和组件的展示规则对齐
  • 补全了火狐浏览器下数字输入框的默认箭头隐藏样式

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 05:18:04