0

I've used React create a time input component like this:

img

import { useEffect, useState } from "react";

function padLeadingZeros(num, size) {
  var s = num + "";
  while (s.length < size) s = "0" + s;
  return s;
}

function TimeInput({ hour, minute, onChange }) {
  const [time, setTime] = useState("");
  const [valuep, setValuep] = useState(0);

  const setFormatTime = (hour, minute) => {
    setTime(padLeadingZeros(hour, 2) + ":" + padLeadingZeros(minute, 2));
  }

  const handleAdd = () => {
    let [hour_str, minute_str] = time.split(":");
    let hour_i = parseInt(hour_str);
    let minute_i = parseInt(minute_str);
    if (valuep < 3) {
      hour_i = hour_i + 1;
      if (hour_i > 23) {
        hour_i = 0;
      }
    } else {
      minute_i = minute_i + 1;
      if (minute_i > 59) {
        minute_i = 0;
      }
    }
    setFormatTime(hour_i, minute_i);
  };

  const handleMin = () => {
    let [hour_str, minute_str] = time.split(":");
    let hour_i = parseInt(hour_str);
    let minute_i = parseInt(minute_str);
    if (valuep < 3) {
      hour_i = hour_i - 1;
      if (hour_i < 0) {
        hour_i = 23;
      }
    } else {
      minute_i = minute_i - 1;
      if (minute_i < 0) {
        minute_i = 59;
      }
    }
    setFormatTime(hour_i, minute_i);
  };

  const markCurrentPosition = (e) => {
    setValuep(e.target.selectionStart);
  }

  useEffect(() => {
    setFormatTime(hour, minute);
  }, [hour, minute]);

  return <>
    <input type="text" value={time} onChange={onChange} onClick={markCurrentPosition} onKeyUp={markCurrentPosition} />
    <div className="flex flex-col">
      <div className="hover:bg-gray-200" onClick={handleAdd}>
        <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 cursor-pointer" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
          <path strokeLinecap="round" strokeLinejoin="round" d="M5 15l7-7 7 7" />
        </svg>
      </div>
      <div className="hover:bg-gray-200" onClick={handleMin}>
        <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 cursor-pointer" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
          <path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
        </svg>
      </div>
    </div>
  </>

}

export { TimeInput }

And used it in a parent component:

import { TimeInput } from "../components/TimeInput";

const Parent = () => {
  const [hour, setHour] = useState(0);
  const [minute, setMinute] = useState(0);
  const handleTimeInputChange = (e) => {
    console.log("handleTimeInputChange", e.target.value);
  };

 return <>
   <TimeInput hour={hour} minute={minute} onChange={handleTimeInputChange} />
</>
}

The time input arrow handle work and input display as expected. But onChange handle handleTimeInputChange was not invoked as expected. I want child componet time input pass data back to parent.

See demo here: https://codesandbox.io/s/bc15y3

What is a right react way to create a reusable packaged component?

Ricardo Sanchez
  • 4,935
  • 11
  • 56
  • 86
qichunren
  • 4,405
  • 7
  • 37
  • 48

2 Answers2

0

In your App component, minute is not defined. When I remove minute variable, the code seems to work as normal. Next time try to provide a minimal producible example instead of posting everything. Not everyone is patient enough to dive into your code.

Kudo
  • 328
  • 2
  • 8
  • Thanks for your reply. But the problem is not fixed. Input add/min handle worked, but onChange callback not trigged. I edit code to add missing code: `const [minute, setMinute] = useState(0);` This is forked code: https://codesandbox.io/s/bc15y3 – qichunren May 26 '22 at 02:41
  • The code works when I checked it. Are you using any plugin that might block script? When I click on the text and press a number, the event is triggered just fine, but if you use the arrow it will not call `onChange`. – Kudo May 26 '22 at 03:18
  • Yes, directly type input will trigger onchange event, but input text is not changed. – qichunren May 26 '22 at 03:38
0

It is because your component TimeInput is a Controlled Components , the value of the input tag in TimeInput is totally controlled by the Parent (here in your codesandbox is App).

Back to the onChange callback, here is handleTimeInputChange , we can find that it never change any state other than log the input value. To reach your goal, you're supposed to change the minute \ hour state value in handleTimeInputChange when onchange fired, like this:

//...
const handleTimeInputChange = (e) => {
    console.log("handleTimeInputChange", e.target.value);
    const [_hour, _minute] = e.target.value.split(":");
    setHour(parseInt(_hour));
    setMinute(parseInt(_minute));
  };

The above is just an example, the actual development needs to consider many boundary conditions.

Liam_1998
  • 1,157
  • 3
  • 12
  • 27
  • I taked your advice, nothing changed. https://codesandbox.io/s/bc15y3 – qichunren May 26 '22 at 03:43
  • I guess you just pressed the delete key. In this scenario, the new `minute` value is still 0, try pressing any number key other than 0, open the console, and you will find logs. – Liam_1998 May 26 '22 at 03:49
  • Maybe question changed to: Why input onChange event not triggerd when change its value by code? https://stackoverflow.com/questions/42550341/react-trigger-onchange-if-input-value-is-changing-by-state – qichunren May 26 '22 at 06:14