My input has following code
<InputMask
id='phone'
name='phone'
type='tel'
value={this.state.phone}
mask='+63(\999) 999-99-99'
maskChar='X'
onChange={(e: SyntheticEvent<HTMLInputElement>): void => {
this.setState({
phone: e.currentTarget.value.replace(/[^\d.+]/g, ''),
});
}}
onBlur={this.validateInput}
onPaste={(e) => this.handlePaste(e)}
className='form-control'
placeholder='Your Phone'
/>
here +639 digits that can not be deleted. But when I try to paste phone number I get incorrect phone.
For example i paste +639055943784
and get +63(963) 963-90-55 which is not desired result.
My paste handler is
handlePaste = (e) => {
this.setState({phone: e.clipboardData.getData("Text")})
}
Try that one
handlePaste = (e) => {
const number = e.clipboardData.getData("Text");
const formattedNumber = number.replace(/639+/i, '')
this.setState({phone: formattedNumber})
}
There also could be edge cases when pasted value will contain another 639s, but I think you can fetch it independently
I created a package that exposes an input component that displays a masked value according to the mask it receives.
The mask will change keeping the cursor at the correct position (even if you change part of the value in the middle of the input, paste some characters, or delete a part of it, and even if the mask changes).
I created a live demo for your use case, using the mask +63(9XX) XXX-XX-XX and defining custom mask rules that uses X as the user provided characters (instead of 9, because you want a digit 9 static in the mask) and associated it with a regex that only accepts a digit/number:
https://codesandbox.io/s/react-phone-mask-70fwc?file=/src/index.js:123-141
import React from "react";
import { MaskedInput } from "react-hook-mask";
const mask = "+63(9XX) XXX-XX-XX";
const maskGenerator = {
rules: new Map([["X", /\d/]]),
generateMask: () => mask
};
const PhoneMaskedInput = () => {
const [value, setValue] = React.useState("");
return (
<div>
<p>Phone Number:</p>
<MaskedInput
maskGenerator={maskGenerator}
value={value}
onChange={setValue}
/>
<p>Value: {value ? "+639" + value : undefined}</p>
</div>
);
};
You can see a demo with several examples at:
https://lucasbasquerotto.github.io/react-masked-input
To install the package: npm i react-hook-mask
This component wraps a default input, but the package also expose hooks to make you able to use it with any kind of component as well as the option to use dynamic masks and dynamic rules (as in the example above).
You can see more information about the package at:
https://www.npmjs.com/package/react-hook-mask
Related
I have a Mui TextField component for currency input and I made it to show numeric keyboard only in Safari Browser.
But when the user tries to paste literal string into the field, I'd like to prevent it and make it sure that allows enter currency number inputs only.
import { TextField } from '#mui/material';
export default function CustomCurrencyTestScreen(props){
const [amount, setAmount] = useState('');
const handleChange = e => {
setAmount(e.target.value);
}
const handleBlur = e => {
//some validation functionalities
}
return (
<TextField
size="small"
id="amount"
name="amount"
onChange={handleChange}
onBlur={handleBlur}
inputProps={{
style:{
fontSize:14,
}
inputMode:'numeric',
}}
InputProps={{
startAdornment: (
<InputAdornment position="start">
$
</InputAdornment>
),
endAdornment:(
<InputAdornment position='end'>
{
Boolean(amount) &&
<CancelRounded size="small" color="grey" sx={{padding:0}} onClick={()=>
setAmount('')}/>
}
</InputAdornment>
),
}}
/>
);
}
It shows numeric keyboard successfully in Safari browser, however, when you paste the literal string into the field, the field shows it.
How can I prevent user not to input or paste other characters than numbers(float value also)?
I tried with CurrencyInput and IMaskInput, but there's a problem in implementing startAdornment and endAdornment, so I don't prefer it.
Is there any solution to implement Currency Input with Adornment in Mui, working correctly in Safari Browser?
I don't think there's any way to prevent an actual paste, but you could use a useEffect on amount, and strip out all non-numeric characters:
useEffect(() => {
function hasNonNumeric(s) {
// return true if has non numeric
}
function stripNonNumeric(s) {
// logic here that returns a string with non-numeric chars stripped
}
if(hasNonNumeric(amount)) {
setAmount(stripNonNumeric(amount))
}
}, [amount])
That being said, I probably wouldn't recommend doing it, I hate when websites mess with stuff I paste. Rather, I'd recommend having validation, and showing an error if it contains non-numeric allowing the user to fix it on their terms.
You can add onPaste handler to your TextField component so that you can implement your logic within this function.
const handleOnPaste = e => {
//do whatever you want with the pasted value
//add your logic here
}
<TextField
...
onPaste={handleOnPaste}
...
We are using antd for datepicker and moments as util. I'm stuck for a week in this ISSUE.The thing is, in the filter sidepanel,on pressing clear,all the fields should clear or set to their default values(in case of dropdown).But the date picket is not resetting.
The above picture is the Activity component and left side to its his the filter.A basic filter with API change from backend on every action event.
useEffect(()=>{
if(clearFilter){
form.resetFields()
setActivitySearchText('')
setFromDate('')
setToDate('')
setStatusSearchText('')
onStatusChange('')
setClearFilter(false)
}
},[clearFilter])
const onChangeFromDate = dateString => {
setFromDate(new Date(dateString).toISOString())
}
const onPageToDate = dateString => {
setToDate(new Date(dateString).toISOString())
}
<StyledDatePicker
allowClear={false}
format={dateFormat}
disabledDate={disabledFromDate}
placeholder={'From'}
onChange={(fromdate, dateString) =>
onChangeFromDate(fromdate, dateString)
}
showTime={{
use12Hours: true,
defaultValue: moment('00:00:00', 'HH:mm:ss'),
}}
/>
<StyledDatePicker
format={dateFormat}
disabledDate={disabledToDate}
placeholder={'To'}
onChange={(todate, dateString) => onPageToDate(todate, dateString)}
showTime={{ use12Hours: true }}
/>
The above code is the index file for all the components,we'll be passing clearfilter prop,if its true ,the filter components are set to empty.The StyledDatePicker is just wrapped in styled components of some custom width.that's it.
You can clearly see,onChangeFromDate() and onPageToDate() are the event functions happening on Change,onChange. As I said above,I'm setting the setFromDate('') and setTodate('') when clearFilter is true.
To give some context,this another main file,from which the props are passed to the others.In there,we are defining setFromDate('') and setTodate('') as,
const [fromDate, setFromDate] = useState('')
const [toDate, setToDate] = useState('')
I think I've given enough details. If need anything, request, I'm ready to give. This is a live project, I'm stuck for a week.Thanks in advance!
I have a range input that has a few things happening onChange. This works as I'd expect with manual click/drag usage. However, when I try to change the value with JavaScript, my onChange event doesn't seem to fire.
Here is my code:
const App = () => {
const [currentValue, setCurrentValue] = useState(0);
const setRangeValue = () => {
const range = document.querySelector("input");
range.value = 50;
range.dispatchEvent(new Event("change", { bubbles: true }));
};
return (
<div className="App">
<h1>Current Value: {currentValue}</h1>
<input
type="range"
min={0}
max={100}
step={10}
onChange={e => {
console.log("Change!");
setCurrentValue(+e.target.value);
}}
defaultValue={0}
/>
<button onClick={setRangeValue}>Set current value to 50</button>
</div>
);
};
And here it is (not) working in CodeSandbox:
https://codesandbox.io/s/divine-resonance-rps1n
NOTE:
Just to clarify. My actual issue comes from testing my component with jest/react testing library. The button demo is just a nice way to visualize the problem without getting into the weeds of having to duplicate all of my test stuff too.
const getMessage = (value, message) => {
const slider = getByRole('slider');
fireEvent.change(slider, { target: { value } });
slider.dispatchEvent(new Event('change', { bubbles: true }));
return getByText(message).innerHTML;
};
When the fireEvent changes the value, it doesn't run the onChange events attached to the input. Which means that getByText(message).innerHTML is incorrect, as that will only update when a set hook gets called onChange. (All of this works when manually clicking/dragging the input slider, I just can't "test" it)
Any help would be great!
The issue is that React has a virtual DOM which doesn't connect directly to the DOM. Note how the events from React are SyntheticEvents. They are not the actual event from the DOM.
https://github.com/facebook/react/issues/1152
If this is for a unit test, create a separate component for the slider and text and make sure they perform as expected separate from each other with props.
For a more in-depth article on how to specifically test a range slider, checkout https://blog.bitsrc.io/build-and-test-sliders-with-react-hooks-38aaa9422772
Best of luck to ya!
I am trying to build an input field where it accepts time as an input here is my code
<InputMask
mask="99:99"
onBlur={handleOnBlur}
onChange={(e) => {
const text = e.target.value
setInputValue(text)
setValueValid(true)
const fixedText = text.replace(/:/g, '')
if (onChange) {
onChange({
...e,
target: {
...e.target,
value: fixedText,
},
})
}
}}
//value={inputValue}
{...rest}
>
{(inputProps) => (
<Tooltip
open={!valueValid}
placement="bottom-start"
title="Ops Wrong Time Format!"
>
<StyledInput
{...inputProps}
autoFocus={rest.autoFocus}
className={rest.className}
onKeyDown={(e) => {
if (e.keyCode === 13) {
checkTimeFormat(inputValue)
if (valueValid) {
if (rest.onBlur) rest.onBlur(e)
e.target.blur()
if (onSubmit) {
const fixedText = inputValue.replace(/:/g, '')
onSubmit({
...e,
target: {
...e.target,
value: fixedText,
},
}, fixedText)
}
}
}
}}
/>
</Tooltip>
)}
</InputMask>
)
I am trying to use InputMask to set the correct format and to make sure only numbers are used as an input. I would like to display a tooltip if the time is not an acceptable time (eg/ 75:89, 99:78...etc) So everything seems to be working and I am able to see the tooltip if i type in something invalid, however, I am not able to get rid of a strange behavior where the cursor always ends up at the end of the input box after any sort of change... I have searched online for an answer but it seems that nothing was of much help. Note that if I remove the tooltip component wrapping my custom input (StyledInput) then everything turns back to normal (cursor stops where the user is currently modifying).
At first I thought this was a rendering issue so I tried commenting out the hooks
setInputValue(text)
setValueValid(true)
and even tried to remove the toggle for the tooltip
open={!valueValid}
but it seems that the tooltip is what's causing the issue... does anyone have any idea how to solve this? Any recommendations or reference to a doc would be of great help!
Here is the sandbox code:
https://codesandbox.io/s/wandering-frost-0dy78
If you put the Tooltip around the entire InputMask element (rather than around the input inside InputMask) it works fine. I haven't taken the time to fully understand why the Tooltip causes issues in the previous location (which would require digging deeper into the internals of InputMask to understand how it manages cursor position), but it isn't surprising to me that it causes issues there.
Below is a modified version of your sandbox:
import React, { useState, useEffect } from "react";
import Tooltip from "#material-ui/core/Tooltip";
import InputMask from "react-input-mask";
import "./styles.css";
export default function App() {
const [inputValue, setInputValue] = useState("value");
const [valueValid, setValueValid] = useState(true);
const checkTimeFormat = time => {
const [hour, minute] = time.split(":");
if (parseInt(hour, 10) > 23 || parseInt(minute, 10) > 59) {
setValueValid(false);
}
};
return (
<Tooltip
open={!valueValid}
placement="bottom-start"
title="Ops Wrong Time Format"
>
<InputMask
mask="99:99"
//beforeMaskedValueChange={beforeMaskedValueChange}
onChange={e => {
const text = e.target.value;
setInputValue(text);
setValueValid(true);
const fixedText = text.replace(/:/g, "");
}}
value={inputValue}
//{...rest}
>
{inputProps => (
<input
{...inputProps}
//autoFocus={rest.autoFocus}
//className={rest.className}
onSubmit={checkTimeFormat(inputValue)}
onKeyDown={e => {
if (e.keyCode === 13) {
if (valueValid) {
console.log("Value is valid");
}
}
}}
/>
)}
</InputMask>
</Tooltip>
);
}
I have two inputs.
Email Address
First Name
On initial load, first name is hidden.
Clicking on the Email address input, will hide a div and show the first name input.
It will also hide a div above the inputs and the inputs will fill that space.
Now, the functionality I am looking for is after a user clicks the email address input, and then the user clicks the first name the first name should not disappear and the div to return.
To do so, at the moment, I have added a lodash debounce delay on the onBlur event listener to wait to see if a user focuses on the first name input.
I have a state variable onBlurWait that defaults to false and gets updated to true when a user focuses on the first name input.
Focusing on the first name input, calls the onBlur event listener to check the value of the onBlurWait state. This listener for now, just returns a console.log or onBlurWait.
The onBlur on the email address input seems to get called before the onFocus of the first name input. So it seems that the onBlurWait state does not get updated so the console.log in the onBlur event listener will return false.
Here is a CodeSandbox that will help you play around with it, so you can hopefully understand what I am referencing above.
Below is my Component
import React, { useState } from "react";
import ReactDOM from "react-dom";
import _ from "lodash";
import InputControl from "./InputControl";
import "./styles.css";
const App = () => {
const [hideContent, setHideContent] = useState(false);
const [emailSignUpRef, setEmailSignUpRef] = useState(null);
const [firstNameSignUpRef, setFirstNameEmailSignUpRef] = useState(null);
const [onBlurWait, setOnBlurWait] = useState(false);
let registerEmailInput = null;
let firstNameInput = null;
// If you click on email address then on first name, I would expect the console.log below to return true
// due to when a user focuses on the first name input the handleInputFocus function gets called
// inside that function, setOnBlurWait(true); is ran updating the value of onBlurWait to true
// But it seems like it has not actually been updated
// Now when you click on email address to first name, back to email address and first name again, the console log now returns true?
const waitOnUpdateState = _.debounce(() => console.log(onBlurWait), 2000);
const hideContentAfterState = _.debounce(
() =>
setHideContent(
emailSignUpRef.length > 0 || firstNameSignUpRef.length > 0 || onBlurWait
),
2000
);
const handleOnFocus = event => {
setEmailSignUpRef(registerEmailInput.value);
setFirstNameEmailSignUpRef(firstNameInput.value);
setHideContent(true);
};
const handleOnInput = () => {
setEmailSignUpRef(registerEmailInput.value);
setFirstNameEmailSignUpRef(firstNameInput.value);
};
const handleInputFocus = () => {
console.log("clicked on first name, onBlurWait should be set as true");
setOnBlurWait(true);
};
const handleOnBlur = () => {
console.log("clicked away from email address");
// waitOnUpdateState is just a test function to return a console log, hideContentAfterState is what is going to be used
waitOnUpdateState();
hideContentAfterState();
};
return (
<div className="App">
<div className={hideContent ? "hidden" : "visible"} />
<InputControl
autoComplete="off"
refProp={input => {
registerEmailInput = input;
}}
onInput={handleOnInput}
onFocus={handleOnFocus}
onBlur={handleOnBlur}
type="text"
name="emailAddressRegister"
placeholder="Email address"
label="Email Address"
/>
<InputControl
className={
!hideContent ? "InputControl--hidden" : "InputControl--visible"
}
autoComplete="off"
refProp={input => {
firstNameInput = input;
}}
onInput={handleOnInput}
onFocus={handleInputFocus}
type="text"
name="firstName"
placeholder="First Name"
label="First Name"
/>
<input type="submit" value="Submit" />
</div>
);
};
export default App;
Consider making it simpler, you are overthinking it.
onFocus and onBlur both receive relatedTarget as one of the properties in their SyntheticEvent. In case of onBlur, relatedTarget is the DOM element for which the focus is leaving the target.
What you could do is handle the blur event and compare relatedTarget property of the event to the input refs. Then you will be able to figure out whether focus is leaving for another form element or some other DOM element on the page.