I'm using Office UI Fabric and have a SpinButton implemented. There is some kind of automatic validation that prevents me from using anything apart from numbers as an input. The problem is, I cannot even use the subtract sign.
How can I change that?
I tried implementing my own onValidate() method, to cancel out the default one, and even though it's called, it doesn't stop the SpinButton input from being deleted when different from numbers
onValidate={() => console.log('Validate meeee')}
The whole SpinButton looks like this (it's mixed with JS):
static getNumericBox(formField: EntityFormField, onChange: EntityFormControlOnChangeType): JSX.Element {
let rangeFrom: number = -999999;
let rangeTo: number = 999999;
const controlParams = EntityFormHelpers.getNumericControlParams(formField.controlType);
if (controlParams) {
rangeFrom = controlParams.rangeFrom;
rangeTo = controlParams.rangeTo;
}
return (
<React.Fragment key={formField.fieldName}>
<SpinButton
key={formField.fieldName}
className={'NumericBox ' + this.getValidationClassName(formField)}
label={formField.displayName}
labelPosition={Position.top}
componentRef={(component: any) => {
if (component) {
const inputElement = component._input.current;
const labelElement = inputElement.parentElement.previousSibling.firstChild;
if (formField.validation.isRequired && !formField.displayOnly) {
labelElement.setAttribute('required', '');
}
inputElement.value = formField.value;
inputElement.onkeydown = (event: any) => {
if (event.target.value.toString().length > rangeTo.toString().length + 1 && event.target.value.toString().length > rangeFrom.toString().length + 1) {
event.preventDefault();
event.stopPropagation();
}
};
inputElement.onkeyup = (event: any) => {
let numberValue = Number(event.target.value);
if (!numberValue) {
numberValue = formField.validation.isRequired ? Number(0) : null;
}
onChange(formField.fieldName, numberValue);
};
}
}}
onValidate={() => console.log('Validate meeee')}
onIncrement={(value: string) => {
if (Number(value) + 1 > rangeTo) {
formField.value = value;
} else {
value = String(+value + 1);
onChange(formField.fieldName, value);
formField.value = value;
}
}}
onDecrement={(value: string) => {
if (Number(value) - 1 < rangeFrom) {
formField.value = value;
} else {
value = String(+value - 1);
onChange(formField.fieldName, value);
formField.value = value;
}
}}
value={formField.value}
min={rangeFrom}
max={rangeTo}
step={1}
onBlur={(event: any) => {
const numberValue = Number(event.target.value);
if (numberValue) {
if (numberValue < rangeFrom) {
onChange(formField.fieldName, rangeFrom);
}
else if (numberValue > rangeTo) {
onChange(formField.fieldName, rangeTo);
}
}
}}
disabled={formField.displayOnly}
/>
{this.getDescriptionControl(formField)}
</React.Fragment>);
}
Problem is in the onkeyup methods. Everything kept overwriting as soon as a single key was pressed. A bit of fiddling around fixed the whole thing.
In case anyone is interested, here's the final and working state:
static getNumericBox(formField: EntityFormField, onChange: EntityFormControlOnChangeType): JSX.Element {
let rangeFrom: number = -999999;
let rangeTo: number = 999999;
const controlParams = EntityFormHelpers.getNumericControlParams(formField.controlType);
if (controlParams) {
rangeFrom = controlParams.rangeFrom;
rangeTo = controlParams.rangeTo;
}
return (
<React.Fragment key={formField.fieldName}>
<SpinButton
key={formField.fieldName}
className={'NumericBox ' + this.getValidationClassName(formField)}
label={formField.displayName}
labelPosition={Position.top}
componentRef={(component: any) => {
if (component) {
const inputElement = component._input.current;
const labelElement = inputElement.parentElement.previousSibling.firstChild;
if (formField.validation.isRequired && !formField.displayOnly) {
labelElement.setAttribute('required', '');
}
inputElement.value = formField.value;
inputElement.onkeydown = (event: any) => {
if (event.target.value.toString().length > rangeTo.toString().length + 1 && event.target.value.toString().length > rangeFrom.toString().length + 1) {
event.preventDefault();
event.stopPropagation();
}
};
inputElement.onkeyup = (event: any) => {
const isValidKeyCode =
event.which === KeyCodes.up ||
event.which === KeyCodes.down ||
event.which === KeyCodes.left ||
event.which === KeyCodes.right ||
event.which === KeyCodes.backspace ||
event.which === KeyCodes.del ||
event.which === KeyCodes.a && event.ctrlKey ||
event.which === KeyCodes.x && event.ctrlKey ||
event.which === KeyCodes.c && event.ctrlKey ||
event.which === KeyCodes.v && event.ctrlKey ||
event.which === KeyCodes.subtract ||
event.which === KeyCodes.dash;
const isValidNumberKeyCode = (
(event.which >= KeyCodes.zero && event.which <= KeyCodes.nine) ||
(event.which >= KeyCodes.zero_numpad && event.which <= KeyCodes.nine_numpad)
);
if (!isValidKeyCode && !isValidNumberKeyCode) {
onChange(formField.fieldName, null);
} else if (event.target.value === "-") {
return;
} else {
let numberValue = parseInt(event.target.value);
if (!numberValue && numberValue !== 0) {
numberValue = formField.validation.isRequired ? +"0" : null;
}
onChange(formField.fieldName, numberValue);
}
};
}
}}
onIncrement={(value: string) => {
if (Number(value) + 1 > rangeTo) {
formField.value = value;
} else {
value = String(+value + 1);
onChange(formField.fieldName, value);
formField.value = value;
}
}}
onDecrement={(value: string) => {
if (Number(value) - 1 < rangeFrom) {
formField.value = value;
} else {
value = String(+value - 1);
onChange(formField.fieldName, value);
formField.value = value;
}
}}
value={formField.value}
min={rangeFrom}
max={rangeTo}
step={1}
onBlur={(event: any) => {
const numberValue = Number(event.target.value);
if (numberValue) {
if (numberValue < rangeFrom) {
onChange(formField.fieldName, rangeFrom);
}
else if (numberValue > rangeTo) {
onChange(formField.fieldName, rangeTo);
}
}
}}
disabled={formField.displayOnly}
/>
{this.getDescriptionControl(formField)}
</React.Fragment>);
}
Related
Given this code, I want to update selectedOption value by selecting an option.
on Change it will return correct value as event.target, but not in useState.
let options = [
{
value: "",
label: "All",
},
{
value: "0",
label: "With Producers",
},
{
value: "1",
label: "In our warehouse",
},
];
<Form.Control
as="select"
onChange={(e) => {
console.log("e.target.value ==> in select", e.target.value);
setSelectedOption(e.target.value);
handleSubmit(e);
}}
className="form-select"
>
{options.map((option) => {
return (
<option key={option.value} value={option.label}> {option.label} </option>
);
})}
</Form.Control>
const [selectedOption, setSelectedOption] = useState('');
const [filteredData, setFilteredData] = useState([]);
const handleSubmit = (e) => {
console.log('e.target.value', e.target.value)
setSelectedOption(e.target.value);
const data = dataAuction;
let filteredArray = [];
if ( selectedOption == 'With Producers' ) {
filteredArray = data.filter(item => {
// console.log('condition', item.is_resell == '1' && item.is_no_deposit == '1' || item.is_resell == '0')
console.log('is_resell', item.auction.is_resell)
// return item.auction.is_resell == selectedOption;
return item.auction.is_resell == 1 && item.auction.is_no_deposit == 1
});
// setFilteredData(filteredArray);
// setDataAuction(filteredArray)
console.log('if 1');
} else if ( selectedOption == 'In our warehouse' ) {
filteredArray = data.filter(item => {
// console.log('condition', item.is_resell == '1' && item.is_no_deposit == '1' || item.is_resell == '0')
console.log('is_resell', item.auction.is_resell)
// return item.auction.is_resell == selectedOption;
return item.auction.is_resell == 1 && item.auction.is_no_deposit == 0
});
// setFilteredData(filteredArray);
// setDataAuction(filteredArray)
console.log('else if 1');
} else if ( selectedOption == 'All' ) {
console.log('else');
filteredArray = data.map(item => {
// console.log('condition', item.is_resell == '1' && item.is_no_deposit == '1' || item.is_resell == '0')
console.log('else if 2')
// return item.auction.is_resell == selectedOption;
return item
});
// setDataAuction(filteredArray)
}
setFilteredData(filteredArray);
setDataAuction(filteredArray)
console.log('selectedOption =====>', selectedOption)
};
I expect to update selectedOption state. but instead if i click of select, the event.target is correct but it iterates over 'options' array, not in sync with the click event.
I am trying to push data into state, but it's not pushing the way I want it to. Whenever it pushes the data, when the data gets rendered, it shows the same distance multiple times rather than one time. I am not sure what I'm doing wrong. I attached my code at the bottom.
const d = new Date();
const shopArray = [];
const [number, setNumber] = useState(null);
const [data, setData] = useState(false);
const weekday = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
const currentDay = weekday[new Date().getDay()];
var distanceArray = [];
const rawTime = d.toString().slice(16, 21);
useEffect(() => {
const unsubscribe = () => {
var i = 0;
const shopOpening = [];
const shopClosing = [];
db.collection("shops")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// Get Shop Availability
const hourObj = doc.data().hoursOfOps;
const timings = hourObj[currentDay];
shopOpening.push(timings[0]);
shopClosing.push(timings[1]);
let [hours, minutes] = rawTime.split(":");
const [opening, openingModifier] = timings[0].split(" ");
let [openingHours, openingMinutes] = opening.split(":");
if (openingHours === "12" && modifier == "AM") {
openingHours = "00";
}
if (openingModifier === "PM" && openingHours !== 12) {
openingHours = parseInt(openingHours, 10) + 12;
}
// Closing Time in Military Time
const [closing, closingModifier] = timings[1].split(" ");
let [closingHours, closingMinutes] = closing.split(":");
if (closingHours === "12") {
closingHours = "00";
}
if (closingModifier === "PM") {
closingHours = parseInt(closingHours, 10) + 12;
}
const userHour = parseInt(hours);
const openingHour = parseInt(openingHours);
const closingHour = parseInt(closingHours);
const shopAvailability = [];
if (userHour >= openingHour && userHour <= closingHour) {
if (
userHour === closingHour &&
closingMinutes !== "0" &&
minutes < closingMinutes
) {
shopAvailability.push("Open");
} else if (
userHour === closingHour &&
closingMinutes !== "0" &&
minutes > closingMinutes
) {
shopAvailability.push("Closed");
} else if (userHour === closingHour && closingMinutes == 0) {
shopAvailability.push("Closed");
} else if (
userHour === openingHour &&
userMinutes >= openingMinutes
) {
shopAvailability.push("Open");
} else if (
userHour === openingHour &&
userMinutes < openingMinutes
) {
shopAvailability.push("Closed");
} else {
shopAvailability.push("Open");
}
} else if (userHour <= openingHour || userHour >= closingHour) {
shopAvailability.push("Closed");
} else {
shopAvailability.push("Closed");
}
// Get Distance from User
fetch(
`https://maps.googleapis.com/maps/api/distancematrix/json?units=metric&origins=${userLocation}&destinations=${
doc.data().address
}&key=${GOOGLE_MAPS_APIKEY}`
)
.then((res) => res.json())
.then((data) => {
var number = data.rows[0].elements[0].distance.text.split(" ");
setNumber(number[0]);
});
// Push data into an array for iterative rendering
if (number == null || number !== undefined) {
shopArray.push({
name: doc.data().name,
id: doc.id,
hoursOfOps: doc.data().hoursOfOps,
image: doc.data().shop_image,
menu: doc.data().menu,
location: doc.data().address,
distanceK: number + " km",
availability: shopAvailability,
opening: shopOpening[i],
closing: shopClosing[i],
});
}
i = i + 1;
});
setData(shopArray.sort((a, b) => (a.distance < b.distance ? 1 : -1)));
});
};
return unsubscribe;
}, [userLocation, GOOGLE_MAPS_APIKEY]);
Also, when I log num[0], a different number comes up for each instance, but when I render it on the screen, the same number comes up multiple times. I think the issue lies in how I'm setting the data in the state. How do I fix this? Feel free to comment on the post to ask for further clarification.
I am using the antd-country-phone-input in my form for international phone numbers. However this does not have a mask and I want to have a masked input. I have searched for hours for a solution to this, but have not come up with any workable solution.
Any ideas how to add a mask to this input?
Code
//library
import CountryPhoneInput, {
ConfigProvider,
CountryPhoneInputValue,
} from "antd-country-phone-input";
//returning
<ConfigProvider
locale={en}
areaMapper={(area) => {
return {
...area,
emoji: (
<img
alt="flag"
style={{ width: 18, height: 18, verticalAlign: "sub" }}
src={getFlag(area.short)}
/>
),
};
}}
>
<CountryPhoneInput
id={id}
value={{
code: countryValue.code,
short: countryValue.short,
phone: countryValue.phone,
}}
onChange={(value) => {
if (value.code !== Number(countryValue.code)) {
setCountryValue({
code: value.code,
short: value.short,
phone: value.phone,
});
}
// onChange("+" + value.code!.toString() + phone);
}}
onBlur={() => setValidNumber(isValidPhoneNumber(value))}
style={{ height: "50px" }}
className="phone-height"
autoComplete="none"
placeholder={mask}
></CountryPhoneInput>
</ConfigProvider>
This is what i put together in the end to come up with a masked input for international phone numbers. It works for most countries phone numbers
const PhoneMaskWidget = (props: any) => {
const {value, onChange, id} = props;
const [countryValue, setCountryValue] = useState<CountryPhoneInputValue>({
short: "US",
code: 1,
phone: "",
});
const [mask, setMask] = useState<string>("(XXX)XXX-XXXX");
const [mounting, setMounting] = useState<boolean>(true);
const [validNumber, setValidNumber] = useState<boolean>(true);
const [countrySwitched, setCountrySwtiched] = useState<boolean>(false);
const stripPhone = (phone: any) => {
let formattedPhone = phone.replace(/[()\-. ]/g, "");
return formattedPhone;
};
const getMask = (mask: any) => {
mask = Array.isArray(mask) ? mask[1].split("#").join("X") : mask.split("#").join("X");
const firstIndex = mask.search(/\d/); // will give you the first digit in the string
let lastIndex = -1;
if (firstIndex > -1) {
for (let i = firstIndex + 1; i < mask.length; i++) {
const element = mask[i];
if (!Number(element)) {
lastIndex = i - 1;
break;
}
}
let remove = "";
if (mask[firstIndex - 1] === "(" && mask[lastIndex + 1] === ")") {
remove = mask.slice(firstIndex - 1, lastIndex + 2);
mask = mask.replace(remove, "");
setMask(mask);
}
}
return mask;
};
const getFlag = (short: string) => {
const data = require(`world_countries_lists/flags/24x24/${short.toLowerCase()}.png`);
// for dumi
if (typeof data === "string") {
return data;
}
// for CRA
return data.default;
};
const maskInput = useCallback(
(phoneValue: string, masking?: string) => {
masking = masking || mask;
let phone = stripPhone(phoneValue);
const phoneLength = stripPhone(masking).length;
if (phone.length > phoneLength) {
phone = phone.substring(0, phoneLength);
setCountryValue({ ...countryValue, phone: phone });
}
let maskedPhone = "";
let brackets = -1;
let dash = -1;
let secondDash = -1;
if (masking.indexOf("(") > -1) {
const open = masking.indexOf("(");
const close = masking.indexOf(")");
brackets = close - (open + 1);
}
if (masking.indexOf("-") > -1) {
dash = masking.indexOf("-");
}
if (masking.lastIndexOf("-") > -1) {
secondDash = masking.lastIndexOf("-");
}
if (brackets > -1) {
if (phone!.length > dash - 2) {
maskedPhone =
"(" +
phone?.substring(0, brackets) +
") " +
phone?.substring(brackets, dash - 2) +
"-" +
phone.substring(6, phone.length);
} else if (phone!.length > brackets && brackets >= 0) {
maskedPhone =
"(" +
phone?.substring(0, brackets) +
") " +
phone.substring(brackets, phone.length);
} else {
maskedPhone = phone;
}
} else {
if (phone.length > secondDash - 1 && secondDash > -1 && secondDash !== dash) {
maskedPhone =
phone.substring(0, dash) +
"-" +
phone.substring(dash, secondDash - 1) +
"-" +
phone.substring(secondDash - 1, phone.length);
} else if (phone.length > dash && dash > -1) {
maskedPhone =
phone.substring(0, dash) + "-" + phone.substring(dash, phone.length);
} else {
maskedPhone = phone;
}
}
return maskedPhone;
},
[mask]
);
const parsePhoneNumber = useCallback(
(value: string) => {
if (!value) return;
let phone = phoneparser.parse(value);
const number = phone?.localized?.stripped || phone.stripped;
const countryShort = phone?.country?.iso3166?.alpha2;
if (!countryShort) {
return;
}
const country = countries.find((c: any) => c.iso === countryShort);
const mask = getMask(country.mask);
setMask(mask);
if (countryShort && number && country.code) {
setCountryValue({
short: countryShort,
code: country.code,
phone: maskInput(number.slice(-stripPhone(mask).length), mask),
});
}
},
[maskInput]
);
useEffect(() => {
if (!countryValue) return;
if (!mounting) {
const country = countries.find((c: any) => c.iso === countryValue.short);
setMask(getMask(country.mask));
setCountryValue({
...countryValue,
phone: countrySwitched ? "" : countryValue.phone,
});
setCountrySwtiched(false);
}
if (mounting) {
if (value) parsePhoneNumber(value);
setMounting(false);
}
}, [countryValue.short, countrySwitched, mounting, value, parsePhoneNumber]);
return (
<ConfigProvider
locale={en}
areaMapper={(area) => {
return {
...area,
emoji: (
<img
alt="flag"
style={{ width: 18, height: 18, verticalAlign: "sub" }}
src={getFlag(area.short)}
/>
),
};
}}
>
<CountryPhoneInput
id={id}
value={{
code: countryValue.code,
short: countryValue.short,
phone: !value || value === "" ? "" : countryValue.phone,
}}
onChange={(value) => {
if (value.short !== countryValue.short) {
setCountrySwtiched(true);
}
const maskedPhone = maskInput(value.phone ? value.phone : "");
setCountryValue({
code: value.code,
short: value.short,
phone: maskedPhone,
});
onChange("+" + value.code!.toString() + " " + maskedPhone);
// onChange("+" + value.code!.toString() + stripPhone(maskedPhone));
}}
onBlur={() => {
const maskedPhone = maskInput(countryValue.phone!);
setValidNumber(
isValidPhoneNumber("+ " + countryValue.code + stripPhone(maskedPhone))
);
}}
style={{ height: "50px" }}
className="phone-height"
autoComplete="none"
placeholder={mask}
></CountryPhoneInput>
</ConfigProvider>
);
};
export default PhoneMaskWidget;
I'm not sure how to convert this section to fit into a useEffect. I can't pull the prevProps conditional out since it should only run within the loop. I dont think I can just add the props to dependency array either, as I need to do something else whenever selectedCurrency does not change.
public componentDidUpdate(prevProps: Readonly<Props>): void {
if (api != null) {
const MyData: {}[] = [];
api.forEach(el=> {
if (!el.data) {
return;
}
if (selectedCurrency === "") {
el.setDataValue("val1", "-");
el.setDataValue("val2", "-");
el.setDataValue("val3", "-");
el.setDataValue("val4", "-");
} else {
const originalCcy = el.data.Currency;
const exchangeRate = referenceCurrencies
.filter(x => x.originalCurrency === originalCcy)
.flatMap(value => value.conversionCurrencies)
.find(value => value.currency === selectedCurrency);
const middleMarketRate = exchangeRate ? exchangeRate.middleMarketRate : 1;
el.setDataValue("val2", el.data.val2 * middleMarketRate);
el.setDataValue(
"val3",
el.data.val3 * middleMarketRate
);
el.setDataValue("val1", middleMarketRate);
if (
prevProps.dateFrom === dateFrom &&
prevProps.dateTo === dateTo &&
prevProps.selectedCurrency !== selectedCurrency
) {
const dateToMoment = moment(dateTo);
const dateFromMoment = moment(dateFrom);
const totalValBalCols = dateToMoment.diff(dateFromMoment, "days");
for (let i = 0; i <= totalValBalCols; i += 1) {
const dayDiff = i;
const currentDate = moment(dateFrom)
.add(dayDiff, "days")
.format("DD-MMM-YYYY");
el.setDataValue(
"refCol",
el.data[currentDate]
);
}
} else {
MyData.push(el.data);
}
}
});
}
}
You can use useRef() to hold the previous props, with a 2nd useEffect() to update preProps.current after you check if anything changed.
Example (not tested):
const prevProps = useRef();
useEffect(() => {
// skip the effect since this is the initial render
if(!prevProps.current) return;
api?.forEach(el => {
if (!el.data) return;
if (selectedCurrency === "") {
// Do something
return;
}
if (
prevProps.current.dateFrom === dateFrom &&
prevProps.current.dateTo === dateTo &&
prevProps.current.selectedCurrency !== selectedCurrency
) {
//Do something
return;
}
//Do something else
});
}, [api, dateFrom, dateTo, selectedCurrency]);
useEffect(() => { prevProps.current = props; })
const [user, setuser] = useState({
fName: '', lName: '', password: '', email: '', username: ''
})
const [fNameError, setfNameError] = useState('');
const [lNameError, setlNameError] = useState('');
const [emailError, setemailError] = useState('');
const [passwordError, setpasswordError] = useState('');
const [usernameError, setusernameError] = useState('');
const changeHandler = (e) => {
setuser({
...user, [e.currentTarget.name]: e.currentTarget.value
})
}
const inputChecker = () => {
user.fName === '' || user.fName.length < 3 ? setfNameError('invalid') : setfNameError('valid');
user.lName === '' || user.lName.length < 3 ? setlNameError('invalid') : setlNameError('valid');
user.username === '' || user.username.length < 5 ? setusernameError('invalid') : setusernameError('valid');
user.password === '' || user.password.length < 6 ? setpasswordError('invalid') : setpasswordError('valid');
validateEmail(user.email) ? setemailError('valid') : setemailError('invalid');
if (fNameError == 'valid' && lNameError == 'valid' && emailError == 'valid' && passwordError == 'valid' && usernameError == 'valid') {
if (fNameError == 'valid') {
return true
}
return false
}
const submitHandler = (e) => {
e.preventDefault();
//
On submitting the form and calling the submitHandler if all errors
in the inputChecker function are valid I need inputChecker to return
true but it returns false on first click even when all are valid but
when i click it for the second time it return true and below check works
// Can someone tell me what I am doing wrong
if (inputChecker()) {
console.log(user);
}
}
Set state is async operation. You are setting the state and then checking its value which will always return you the old one. Thats the reason it returns true in the second time.
Refactor your code as below, and check again.
const inputChecker = () => {
let isFNameValid = true;
let isLNameValid = true;
let isUsernameValid = true;
let isPasswordValid = true;
let isEmailValid = true;
if(user.fName === '' || user.fName.length < 3) {
setfNameError('invalid');
isFNameValid = false;
}
else {
setfNameError('valid');
isFNameValid = true;
}
if(user.lName === '' || user.lName.length < 3) {
setlNameError('invalid');
isLNameValid = false;
}
else {
setlNameError('valid');
isLNameValid = true;
}
if(user.username === '' || user.username.length < 5) {
setusernameError('invalid');
isUsernameValid = false;
}
else {
setusernameError('valid');
isUsernameValid = true;
}
if(user.password === '' || user.password.length < 6) {
setpasswordError('invalid');
isPasswordValid = false;
}
else {
setpasswordError('valid');
isPasswordValid = true;
}
if(validateEmail(user.email)) {
setemailError('valid');
isEmailValid = true;
}
else {
setemailError('invalid');
isEmailValid = false;
}
if (isFNameValid && isLNameValid && isUsernameValid && isPasswordValid && isEmailValid) {
return true;
} else
return false;
}