Detect numeric input's arrow has been clicked React

3.8k Views Asked by At

I'm wondering if there is any way to detect that numeric input's arrow has been clicked. OnChange event function is called then arrow has been clicked or input value has been changed with keyboard. I want to find a way to detect change when only arrow has been clicked.

Numeric input arrows

Here is link of a little Demo and it's code

import React from "react";

export default function App() {
  const [log, setLog] = React.useState("");

  return (
    <div className="App">
      <input
        type="number"
        onChange={e => {
          setLog(log + "+");
        }}
      />
      <br />
      <span>{log}</span>
    </div>
  );
}

4

There are 4 best solutions below

3
Siddharth On

I don't think you can attach an event on the arrows.

Though a workaround is possible, It assumes that if the difference between prev value and the current value is 1 then the arrow was clicked.

A couple of points to note:

  • It will fail for the first time if the user directly type 1 in the input box.
  • It will track up & down key strokes as well.

import React from "react";

export default function App() {
  const [log, setLog] = React.useState("");
  const [prevVal, setPrevVal] = React.useState(0);

  const setLogIfArrowClicked = e => {
    const currentVal = e.target.value;
    if (currentVal - prevVal === 1) {
      setLog(`${log}+`);
    } else if (currentVal - prevVal === -1) {
      setLog(`${log}-`);
    }
    setPrevVal(currentVal);
  };

  return (
    <div className="App">
      <input type="number" onChange={setLogIfArrowClicked} />
      <br />
      <span>{log}</span>
    </div>
  );
}

0
Józef Podlecki On

I think it is default arrow with customized design, so I don't want to add my arrows

No this is actually browser builtin input control if you inspect element

This is an example custom control using arrows made with css.

const { useState, useEffect } = React;

const NumberInput = ({value, onChange, onUpArrow, onDownArrow}) => {
  return <div>
    <input value={value} onChange={() => onChange(event.target.value)} type="text"/>
    <div className="arrow arrow-up" onClick={onUpArrow}/>
    <div className="arrow arrow-down" onClick={onDownArrow}/>
  </div>
}

function App() {
  const [log, setLog] = useState("");
  const [value, setValue] = useState(0);

  const onChange = (value) => {
    setValue(value);
  }
  const onUpArrow = () => {
    setValue(value => value + 1);
  }
  const onDownArrow = () => {
    setValue(value => value - 1);
  }

  return (
    <div className="App">
      <NumberInput value={value} onChange={onChange} onUpArrow={onUpArrow} onDownArrow={onDownArrow} />
      <span>{log}</span>
    </div>
  );
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
  );
.arrow {
  border: solid black;
  border-width: 0 3px 3px 0;
  display: inline-block;
  padding: 3px;
  position: absolute;
  cursor: pointer;
}

.arrow-up {
  transform: rotate(-135deg);
  -webkit-transform: rotate(-135deg);
  margin-top: 5px;
  margin-left: -15px;
}

.arrow-down {
  transform: rotate(45deg);
  -webkit-transform: rotate(45deg);
  margin-top: 8px;
  margin-left: -15px;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>

0
Quentin Grisel On

There is no event fired when clicking on the arrows so here is what I can propose you, see this custom component on Stackblitz.

Here is the full code :

.css :

.custom-input {
  display: flex;
}

.custom-input > .arrows {
  margin-left: -21px;
}

/*+++++++++ ARROWS +++++++++++++*/
.arrows-component {
  display: inline-block;
}

.arrows {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.arrows button {
  background-color: transparent;
  border: 1px solid transparent; 
}

.arrows button:hover {
  border: 1px solid rgba(0, 0, 0, .24);
}

.arrows button:focus {
  border: 1px solid rgba(0, 0, 0, .24); 
}

.arrow-top {
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 0 3.5px 7px 3.5px;
  border-color: transparent transparent #007bff transparent;
}

.arrow-bottom {
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 7px 3.5px 0 3.5px;
border-color: #007bff transparent transparent transparent;
}

.js :

import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";

const App = () => {
  return (
    <div>
      <CustomInput />
    </div>
  );
};

const CustomInput = () => {
  const [currentValue, setCurrentValue] = React.useState(0);

  const handleChanges = e => {
    setCurrentValue(event.target.value.replace(/\D/, ""));
  };

  const topArrowClicked = (e) => {
    setCurrentValue(prevState => prevState + 1);
  }

  const bottomArrowClicked = (e) => {
    setCurrentValue(prevState => prevState - 1);
  }

  return (
    <div className="custom-input">
      <input
        type="text"
        value={currentValue}
        pattern="[0-9]*"
        onChange={e => handleChanges(e)}
      />
      <span className="arrows">
        <InputArrows topArrowClick={topArrowClicked} bottomArrowClick={bottomArrowClicked} />
      </span>
    </div>
  );
};

const InputArrows = ({topArrowClick, bottomArrowClick}) => {

  return (
    <div className="arrows-component">
      <div className="arrows">
        <button onClick={topArrowClick}>
          <div className="arrow-top" />
        </button>
        <button onClick={bottomArrowClick}>
          <div className="arrow-bottom" />
        </button>
      </div>
    </div>
  );
};

render(<App />, document.getElementById("root"));

Here you have a full access to the method triggered when clicking on the top and bottom arrow, and you can implement whatever functionality you want (as a step or different behavior).

1
Jaybeecave On

Use a combination of oninput and onkeydown.

let isKeyDown_flagForArrowInput = false // this flag is set from keydown when a user types a value. 

function onKeyDown() {
  isKeyDown_flagForArrowInput = true
}

function onInput(e) {
    console.log('isKeyDown_FlagForArrowInput', this.isKeyDown_FlagForArrowInput)
    
    if (this.isKeyDown_FlagForArrowInput) { 
      // INPUT Was triggered after a keydown i.e. user typed something
    } else {
      // INPUT Was triggered after an arrow key (or context menu paste or any other event that doesnt require interaction with the keyboard :D )
    }
    // reset the flag after input
    this.isKeyDown_flagForArrowInput = false

}

Need to consider things like copy and paste from context menu (keyboard shortcuts will still trigger keydown).

But it gives the basic concept