2

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

Gagik
  • 96
  • 1
  • 6
  • I don't think that's possible, maybe you can create custom controls and add events on them? – Siddharth Jun 25 '20 at 19:27
  • Can you add your own custom arrows or do you absolutely need to check the default arrows? – itsanewabstract Jun 25 '20 at 19:30
  • @itsanewabstract I use material ui's inputs, I think it is default arrow with customized design, so I don't want to add my arrows – Gagik Jun 25 '20 at 19:33
  • If they're using a custom design, you might be able to go into your node_modules and modify the code they use to render them. You can attach an event listener there. I'm not sure of a simpler solution – itsanewabstract Jun 25 '20 at 19:37
  • @itsanewabstract thanks but it's not good idea I think. – Gagik Jun 25 '20 at 19:46
  • I have an idea. I will let it here maybe for someone it will be useful. If in onchange event's function check the sub of new value and prev value and if it equal 1 (default step size) then we can suppose arrow has been clicked – Gagik Jun 25 '20 at 19:46

3 Answers3

1

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>
  );
}
Siddharth
  • 1,200
  • 8
  • 13
  • You will need to handle the step if you change it in the input but that's a good idea – Quentin Grisel Jun 25 '20 at 20:02
  • That's not required. Let's say prevVal is 9, if you change in the input, you'll have to delete 9 first(to change it to 8/10), which will make current value 0. I hope you get the idea. – Siddharth Jun 25 '20 at 20:11
  • @Siddharth I've already written about this method in comments of my question, but thanks. First point won't be in my case, because on page load my inputs already have a value. – Gagik Jun 25 '20 at 20:17
0

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>
Józef Podlecki
  • 10,453
  • 5
  • 24
  • 50
0

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

Quentin Grisel
  • 4,794
  • 1
  • 10
  • 15