skip to Main Content

I am quite new to react here. I am trying to constrain my input to min -10 and max 10 input value. I have it so the input can accept input and I am trying to use hooks useState and useEffect to try to change and set the value in the input. No matter what is in the input when you click off of it it should be constrained to -10 or 10. I know there is onBlur to trigger a function or state for that, but I don’t know where to go from there. Any help would be greatly appricated.

// For reference, this component is rendered
// by its parent like this:
//
// const ParentForm = () => {
//   const [value, setValue] = useState(0)
//   return (
//     <NumberSelector
//       min={-10}
//       max={10}
//       value={value}
//       onChange={setValue}
//       css={{ background: theme.white }}
//     />
//   )
// }

const NumberSelector = ({
  min = -10,
  max = 10,
  value,
  onChange: setValue,
  className,
}) => {
  const [tempValue, tempSetValue] = useState(0)
  useEffect(() => {

  }, [tempValue])
  return (
    <div
      css={{
        display: "flex",
        border: `3px solid ${theme.richBlack}`,
        borderRadius: 5,
      }}
      className={className}
    >
      <StepButton
        aria-label="Reduce by one"
        tabIndex="-1"
        onClick={() => {
          setValue((prev) => prev - 1)
        }}
      >
        <svg width="16" height="16" viewBox="0 0 16 16">
          <rect x="0" y="6.5" width="16" height="3" fill={theme.richBlack} />
        </svg>
      </StepButton>
      
      <input
        type="text"
        maxLength={Math.max(min.toString().length, max.toString().length + 1)}
        css={inputStyles}
        value={tempValue}
        onChange={(e) => tempSetValue(e.target.value)}
      />

      <StepButton
        aria-label="Increase by one"
        tabIndex="-1"
        onClick={() => {
          setValue((prev) => prev + 1)
        }}
      >
        <svg width="16" height="16" viewBox="0 0 16 16">
          <rect x="0" y="6.5" width="16" height="3" fill={theme.richBlack} />
          <rect x="6.5" y="0" width="3" height="16" fill={theme.richBlack} />
        </svg>
      </StepButton>
    </div>
  )
}

export default NumberSelector

NumberSelector.propTypes = {
  min: PropTypes.number,
  max: PropTypes.number,
  value: PropTypes.number.isRequired,
  onChange: PropTypes.func.isRequired,
  className: PropTypes.string,
}

3

Answers


  1. This has nothing to do with React.

    As I understand you are looking for the numeric value. Then:

     <input
        type="number"
        min={min}
        max={max}
        css={inputStyles}
        value={tempValue}
        onChange={(e) => tempSetValue(e.target.value)}
      />
    

    For more information Input type

    Login or Signup to reply.
  2. Usually it isn’t recommended to use useEffect for any side-modifications.

    You can try having some custom handling in the onChange

    const onTempChange = (e) => {
        // default to 1 if no input is entered
        let tempFloat = parseFloat(e.target.value || '1');
        // default to 1 if input is invalid
        if (isNan(tempFloat) {
            tempFloat = 1;
        } else {
            tempFloat = Math.min(max, Math.max(min, tempFloat));
        }
        // usually you name the setter something like setTempValue
        tempSetValue(tempFloat);
    };
    
    return (
        // ...
        <input ... onChange={onTempChange} />
    );
    

    Obviously there’s room for improvement with the code, but that’s the gist of one way you can control the input value given to the setter.

    If you want this to only happen after the user leaves the input, then you’ll just need to listen to another event (onFocusOut iirc?).

    Hope this helped.

    Login or Signup to reply.
  3. Since the NumberSelector component is completely controlled from the parent, you should not have local state to store the value. The useEffect is also unnecessary.

    You can then attach an onBlur handler to clamp the value.

    <input
        type="text"
        maxLength={Math.max(min.toString().length, max.toString().length + 1)}
        value={value}
        onChange={e => setValue(e.target.value)}
        onBlur={e => {
          if (value && !isNaN(value))
            setValue(Math.min(max, Math.max(min, value)));
        }}
    />
    

    For the increment and decrement buttons, you need to check that the value will not exceed the valid range after incrementing.

    <StepButton
        aria-label="Reduce by one"
        tabIndex="-1"
        onClick={() => setValue(Math.max(min, value - 1))}>
    // ...
    <StepButton
        aria-label="Increase by one"
        tabIndex="-1"
        onClick={() => setValue(Math.min(max, value + 1))}>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search