Gagik
Gagik

Reputation: 96

Detect numeric input's arrow has been clicked React

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>
  );
}

Upvotes: 2

Views: 4229

Answers (4)

Jaybeecave
Jaybeecave

Reputation: 886

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

Upvotes: 1

Quentin Grisel
Quentin Grisel

Reputation: 4987

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).

Upvotes: 0

J&#243;zef Podlecki
J&#243;zef Podlecki

Reputation: 11305

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>

Upvotes: 0

Siddharth
Siddharth

Reputation: 1270

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>
  );
}

Upvotes: 1

Related Questions