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;
Related
I have created a Wordle game, I am trying to get the values from the inputs by line. Despite many attempts, a value is received only after I enter the next letter in the next input and an alert message pops up according to the new letter from the beginning of the next line appears.
import React, { ChangeEvent, useEffect,useRef, useState } from "react";
interface Props {
word: string;
numberOfLines: number;
numberOfInputs: number;
}
const Wordle = ({ word, numberOfLines, numberOfInputs }: Props) => {
const [inputs, setInputs] = useState<string[][]>(
Array(numberOfLines)
.fill(null)
.map(() => Array(numberOfInputs).fill(""))
);
const fullWord = useRef("");
const [currentRow, setCurrentRow] = useState(0);
const [currentCol, setCurrentCol] = useState(0);
const [color, setColor] = useState<string[][]>(
Array(numberOfLines)
.fill(null)
.map(() => Array(numberOfInputs).fill("white"))
);
const handleChange = (
e: ChangeEvent<HTMLInputElement>,
row: number,
col: number
) => {
const updatedInputs = inputs.map((inputRow, i) => {
if (i === row) {
return inputRow.map((input, j) => (j === col ? e.target.value : input));
}
return inputRow;
});
setInputs(updatedInputs);
if (col === numberOfInputs - 1) {
setCurrentRow(row + 1);
setCurrentCol(0);
} else {
setCurrentRow(row);
setCurrentCol(col+1);
}
const updatedColor = color.map((colorRow, i) => {
if (i === row) {
return colorRow.map((color, j) => {
if (j === col) {
if (word.includes(e.target.value)) {
if (word[j] === e.target.value) {
return "green";
}
return "brown";
}
return "white";
}
return color;
});
}
return colorRow;
});
setColor(updatedColor);
if(fullWord.current.length < word.length)
{fullWord.current = fullWord.current + e.target.value;
document
.getElementsByTagName("input")[row * numberOfInputs + col + 1].focus();
}
else if (fullWord.current.length === word.length) {
if (fullWord.current == word) {
alert("Success!");
fullWord.current="";
document
.getElementsByTagName("input")[row * numberOfInputs + col].focus();
} else {
fullWord.current="";
alert("Fail!");
}
}
}
return (
<div>
{inputs.map((inputRow, i) => (
<div key={i} className="word">
{inputRow.map((input, j) => (
<input
key={j}
type="text"
value={input}
maxLength={1}
onChange={(e) => handleChange(e, i, j)}
style={{ backgroundColor: color[i][j] }}
/>
))}
</div>
))}
{/* <Keyboard onClick={handleClick} /> */}
</div>
);
};
export default Wordle;
I want to get the value before the focus moves to the next input.
Thanks
I usedDateInput global component but in the userPRofile date is not visible in input filed,but when I am displaying the whole date its showing the data:
I am calling dateInput here:
BIRTHDAY{details.birthDate}
<div className="birthdayDate">
<DateInput
name="birthDate"
value={details.birthDate}
error={errors.birthDate}
onChange={handleChange}
onBlur={handleBlur}
required
/>
</div>
and dateInput.tsx files are:
const DateInput = ({
name,
value,
onChange,
onBlur,
error,
isMoreGridGap,
isSmallPlaceHolderSize,
isLessPaddingRequired,
startFromCurrentYear,
required,
className,
isLaterDateInput,
startFromCurrentMonth,
startFromCurrentDate,
}: Props) => {
const device = useDevice();
const months = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
const currentMonth = new Date().toLocaleString("default", { month: "short" }).toString().toUpperCase();
const getMonthsFromCurrent = () => {
return [...months].splice(months.indexOf(currentMonth));
};
type Month = typeof months[number];
const getNumberOfDaysInMonth = (month: Month, year?: string) => {
const fullYear = year && year !== "YEAR" ? parseInt(year) : new Date().getFullYear();
return new Date(fullYear, months.indexOf(month) + 1, 0).getDate();
};
const createReactSelectOptions = (values: string[]) => {
return values.map((value) => ({ label: value, value }));
};
const beautifyDays = (values: string[]) => {
return values.map((day) => (parseInt(day) < 10 ? "0" + day : day));
};
const createArrayOfNumbers = (amount: number, start?: number) => {
return Array.from({ length: amount }, (_, i) => `${i + (start || 1)}`);
};
const datesToString = (month: string, day: string, year: string) => {
const cleanMonth = ("00" + (months.indexOf(month) + 1)).slice(-2);
const cleanDay = ("00" + day).slice(-2);
return `${year}-${cleanMonth}-${cleanDay}`;
};
const isDateValid = (date: string) => {
return dayjs(date, "YYYY-MM-DD").format("YYYY-MM-DD") === date;
};
const getInputs = (inputValue: string) => {
if (!inputValue) {
return {
day: "",
month: "",
year: "",
};
}
const [year, month, day] = inputValue.split("-");
const intYear = parseInt(year);
const intMonth = parseInt(month) - 1;
return {
day: day ? day.toString() : "",
year: intYear !== 0 && !isNaN(intYear) ? intYear.toString() : "",
month: months[intMonth],
};
};
let initialDayPlaceholder: string = device === "DESKTOP" ? "DAY" : "DD";
let initialYearPlaceholder: string = device === "DESKTOP" ? "YEAR" : "YY";
let initialMonthPlaceholder: string = device === "DESKTOP" ? "MONTH" : "MM";
let initialDatePlaceholder: string = device === "DESKTOP" ? "DATE" : "DD";
if (required) {
initialDayPlaceholder = initialDayPlaceholder + "*";
initialMonthPlaceholder = initialMonthPlaceholder + "*";
initialYearPlaceholder = initialYearPlaceholder + "*";
initialDatePlaceholder = initialDatePlaceholder + "*";
}
const initialDateOrDayPlaceholder = isLaterDateInput ? initialDatePlaceholder : initialDayPlaceholder;
const [selectedMonth, setSelectedMonth] = useState(initialMonthPlaceholder);
const [selectedDay, setSelectedDay] = useState(initialDateOrDayPlaceholder);
const [selectedYear, setSelectedYear] = useState(initialYearPlaceholder);
const [monthOptions, setMonthOptions] = useState(startFromCurrentMonth ? getMonthsFromCurrent() : months);
let arrayOfYears;
if (startFromCurrentYear) {
arrayOfYears = createArrayOfNumbers(200, dayjs().year());
} else {
arrayOfYears = createArrayOfNumbers(200, 1900);
}
const [daysOptions, setDaysOptions] = useState(createReactSelectOptions(beautifyDays(createArrayOfNumbers(31))));
const [yearOptions] = useState(createReactSelectOptions(arrayOfYears));
const getMonthOptions = (month: string, year: string) => {
if (!month) {
return createReactSelectOptions(createArrayOfNumbers(31));
}
const daysInCurrentMonth = getNumberOfDaysInMonth(month, year);
return createReactSelectOptions(beautifyDays(createArrayOfNumbers(daysInCurrentMonth)));
};
useEffect(() => {
if (!value || value.length === 0 || !(selectedDay.includes("Y") && selectedMonth.includes("M") && selectedYear.includes("Y"))) {
return;
}
const { day, month, year } = getInputs(value);
setDaysOptions(getMonthOptions(month, year));
setSelectedMonth(month);
setSelectedYear(year);
setSelectedDay(day);
}, [value]);
const currentYear = new Date().getFullYear().toString();
const getMonths = () => {
if ((selectedYear.includes("Y") || selectedYear === currentYear) && startFromCurrentMonth) {
setMonthOptions(getMonthsFromCurrent());
if (currentMonth !== selectedMonth) {
setSelectedDay("");
setSelectedMonth("");
}
} else {
setMonthOptions(months);
}
};
useEffect(() => {
getMonths();
}, [selectedYear]);
const updateMonth = (monthValue: string) => {
setSelectedMonth(monthValue);
};
const updateDay = (dayValue: string) => {
setSelectedDay(dayValue);
};
const updateYear = (yearValue: string) => {
setSelectedYear(yearValue);
};
const currentDate = new Date().toLocaleString("default", { day: "2-digit" });
useEffect(() => {
let newDaysOptions: Option[] = [];
if (!startFromCurrentDate) {
newDaysOptions = getMonthOptions(selectedMonth, selectedYear);
} else {
if (selectedMonth === currentMonth && (selectedYear === currentYear || selectedYear.includes("Y"))) {
const daysInCurrentMonth = getNumberOfDaysInMonth(selectedMonth, selectedYear);
newDaysOptions = createReactSelectOptions(beautifyDays(createArrayOfNumbers(daysInCurrentMonth + 1 - +currentDate, +currentDate)));
} else {
newDaysOptions = getMonthOptions(selectedMonth, selectedYear);
}
}
if (newDaysOptions.length === daysOptions.length) {
return;
}
setSelectedDay("");
setDaysOptions(newDaysOptions);
}, [selectedMonth, selectedYear, startFromCurrentDate]);
useEffect(() => {
if (selectedDay.includes("Y") && selectedMonth.includes("M") && selectedYear.includes("Y")) {
onChange(name, "");
return;
}
if (!(selectedDay && selectedMonth && selectedYear)) {
onChange(name, "Invalid Date");
return;
}
const formattedDate = datesToString(selectedMonth, selectedDay, selectedYear);
if (!isDateValid(formattedDate)) {
onChange(name, "Invalid Date");
return;
}
onChange(name, formattedDate);
}, [selectedDay, selectedMonth, selectedYear]);
const handleBlur = () => {
if (!(selectedMonth || selectedYear || selectedDay)) {
return;
}
onBlur(name, value);
};
return (
<DateInputStyled isMoreGridGap={isMoreGridGap} className={className}>
<TimePeriodInput
options={createReactSelectOptions(monthOptions)}
placeholder={initialMonthPlaceholder}
onChange={updateMonth}
value={selectedMonth}
onBlur={handleBlur}
required
isSmallPlaceHolderSize={isSmallPlaceHolderSize}
isLessPaddingRequired={isLessPaddingRequired}
isCalenderIcon={true}
isLaterDateInput={isLaterDateInput}
ariaLabel="Month"
/>
<TimePeriodInput
options={daysOptions}
placeholder={isLaterDateInput ? initialDatePlaceholder : initialDayPlaceholder}
onChange={updateDay}
value={selectedDay}
onBlur={handleBlur}
required
isSmallPlaceHolderSize={isSmallPlaceHolderSize}
isLessPaddingRequired={isLessPaddingRequired}
isCalenderIcon={true}
isLaterDateInput={isLaterDateInput}
ariaLabel="Day"
/>
<TimePeriodInput
options={yearOptions}
placeholder={initialYearPlaceholder}
onChange={updateYear}
value={selectedYear}
onBlur={handleBlur}
required
isSmallPlaceHolderSize={isSmallPlaceHolderSize}
isLessPaddingRequired={isLessPaddingRequired}
isCalenderIcon={true}
isLaterDateInput={isLaterDateInput}
ariaLabel="Year"
/>
<Error className="errorMessage errorRed">{error}</Error>
</DateInputStyled>
);
};
THe main issue is the value is not showing in the date filed like in day 01,month 03,year 1999
Please help me
For this project I am currently working on, I need to highlight the button that was clicked on each layer/row. However, the way I have right now it highlights every button that was clicked.
I need something like this:
correct highlighted path
But then when I click on the same row, it does not remove the highlight from the button that I pressed before. How can I update and reset the state of the previous button that was clicked? I tried to use the useRef hook for this but I haven't been successful so far.
wrong highlighted path
EDIT: Added code
This is the code that I have for the component of each row in the website:
function StackRow({ data, partition, level, index, onClick, getInfo, isApp }) {
const classes = useStyles({ level: level });
const rowRef = useRef();
const handleSelectedButtons = (flag, setFlag, btnRef) => {
console.log(rowRef);
};
return (
<Card
key={partition + '_' + index + '_' + level}
className={classes.card}
id={level}
ref={rowRef}
>
{data.map((field) => {
return (
<StackItem
key={partition + '_' + field[0] + '_' + level}
data={field[0]}
info={field[1]}
level={level}
onClick={onClick}
getInfo={getInfo}
isApp={isApp}
handleSelectedButtons={handleSelectedButtons}
rowRef={rowRef}
/>
);
})}
</Card>
);
}
And this is the code I have for each button of the row:
function StackItem({
data,
info,
level,
onClick,
getInfo,
isApp,
handleSelectedButtons,
}) {
const [flag, setFlag] = useState(false);
const btnRef = useRef();
const styleProps = {
backgroundColor: flag ? '#06d6a0' : level % 2 === 0 ? '#22223b' : '#335c67',
};
const classes = useStyles(styleProps);
return (
<Button
ref={btnRef}
isselected={flag.toString()}
key={data}
className={classes.button}
variant="outlined"
onClick={(event) => {
onClick(event, setFlag, btnRef);
handleSelectedButtons(flag, setFlag, btnRef);
getInfo(info, level, isApp);
}}
disableElevation={true}
>
{data}
</Button>
);
}
There are some useless variables and states there because I have been trying all sort of stuff to do this.
EDIT: Added data sample & project structure
Data looks like:
{
application: {
cmake: {
info: str,
versions: {
version_no: {
application: {...}
}
}
},
gcc: {...},
git: {...},
intel: {...},
.
.
.
}
}
The structure of the project is like:
App
L Stack
L StackRow
L StackItem
Where App is the entire application, Stack is the container for everything in the images apart from the search box, StackRow matches one row of the Stack, and StackItem is one item/button from the StackRow.
EDIT: Added Stack component
function Stack({ data, partition, getInfo }) {
const [level, setLevel] = useState(0);
const [cards, setCards] = useState([]);
const [isApp, setIsApp] = useState(true);
const [selected, setSelected] = useState([]);
const [prevLevel, setPrevLevel] = useState(-1);
const cardsRef = useRef();
const handleClick = (event, setFlag, btnRef) => {
let rows = cardsRef.current.childNodes;
let currBtn = event.target.innerText;
let curr;
for (let i = 0; i < rows.length; i++) {
let rowItems = rows[i].childNodes;
for (let j = 0; j < rowItems.length; j++) {
if (currBtn === rowItems[j].textContent) {
curr = rowItems[j].parentElement;
}
}
}
let id;
for (let i = 0; i < rows.length; i++) {
if (curr.textContent === rows[i].textContent) {
id = i;
}
}
if (level === id) {
if (id % 2 === 0) {
setIsApp(true);
if (selected.length === 0) {
setSelected([...selected, data[currBtn].versions]);
} else {
let lastSelected = selected[selected.length - 1];
setSelected([...selected, lastSelected[currBtn].versions]);
}
} else {
let lastSelected = selected[selected.length - 1];
setSelected([...selected, lastSelected[currBtn].child]);
setIsApp(false);
}
setPrevLevel(level);
setLevel(level + 1);
} else {
let newSelected = selected.slice(0, id);
if (id % 2 === 0) {
setIsApp(true);
if (newSelected.length === 0) {
setSelected([...newSelected, data[currBtn].versions]);
} else {
let lastSelected = newSelected[newSelected.length - 1];
setSelected([...newSelected, lastSelected[currBtn].versions]);
}
} else {
let lastSelected = newSelected[newSelected.length - 1];
setSelected([...newSelected, lastSelected[currBtn].child]);
setIsApp(false);
}
setPrevLevel(level);
setLevel(id + 1);
}
setFlag(true);
};
useEffect(() => {
let fields = [];
let lastSelected = selected[selected.length - 1];
if (level % 2 !== 0) {
fields = Object.keys(lastSelected).map((key) => {
let path = lastSelected[key].path;
let module = lastSelected[key].module_name;
let info = 'module: ' + module + ' path: ' + path;
return [key, info];
});
} else {
if (selected.length !== 0)
fields = Object.keys(lastSelected).map((key) => {
let info = lastSelected[key].info;
return [key, info];
});
}
if (fields.length > 0) {
if (level > prevLevel) {
setCards((prevState) => [...prevState, fields]);
} else {
setCards((prevState) => [
...prevState.slice(0, selected.length),
fields,
]);
}
}
}, [selected, level, prevLevel]);
useEffect(() => {
let fields = Object.keys(data).map((key) => {
let info = data[key].info;
return [key, info];
});
setCards([fields]);
setLevel(0);
}, [data]);
useEffect(() => {
setLevel(0);
setPrevLevel(-1);
setSelected([]);
}, [partition]);
if (cards) {
return (
<div ref={cardsRef}>
{cards.map((card, index) => (
<StackRow
data={card}
partition={partition}
level={index}
index={cards.indexOf(card)}
onClick={handleClick}
getInfo={getInfo}
isApp={isApp}
/>
))}
</div>
);
} else return null;
}
EDIT: Added data sample
{
cmake: {
info: "A cross-platform, open-source build system. CMake is a family of tools designed to build, test and package software.",
versions: {
"3.17.3": {
child: {},
module_name: "cmake/3.17.3",
path: "/opt/apps/nfs/spack/var/spack/environments/matador/modules/linux-centos8-x86_64/Core/cmake/3.17.3.lua",
version_no: "3.17.3"
}
}
},
gcc: {
info: "...",
versions: {
"8.4.0": {
child: {
cmake: {...},
cuda: {...},
cudnn: {...},
openmpi: {...},
.
.
.
},
module_name: "...",
path: "...",
version_no: "..."
}
"9.3.0": {...},
"10.1.0": {...}
}
}
}
My redux store updates perfectly
after sorting
before sorting
But the props in my component don't update even when the store has updated and so the child components still show the old values. I'm sorting my channels according to the timestamp. The sorting function updates the redux store but it still renders the non-sorted data.
the output it should be sorted but it remains the same(unsorted)
This is my component code
export const RoomItem = (props) => {
const [roomLiveStatus, setRoomLiveStatus] = useState(false);
const compareTimestamp = (t1 = 0, t2 = 0) => {
return (t1.lastMessage && t2.lastMessage) && t2.lastMessage.timestamp - t1.lastMessage.timestamp
}
const getRoomData = () => {
const { roomData, workspace } = props;
const { workspaceId } = workspace
const workspaceIdLowerCase = workspaceId.toLowerCase()
const { roomId } = roomData;
const roomIdLowerCase = roomId.toLowerCase()
firebase
.firestore()
.collection(`sessions/${workspaceIdLowerCase}/rooms`)
.doc(`${roomIdLowerCase}`)
.onSnapshot(doc => {
if (doc.exists) {
// console.log("LIVE Sessions doc: ", doc.data())
const { currentSession } = doc.data()
if (currentSession !== "") {
setRoomLiveStatus(true)
} else {
setRoomLiveStatus(false)
}
}
})
}
useEffect(() => {
getRoomData();
},[])
useEffect(() => {
// do something
getRoomData();
// console.log(props,"props of roomitem")
},[props.sortType])
const strip = (value) => {
const { user, content } = value
let name = user.name;
let firstName = name.trim().split(" ")[0]
if (value.type === 0) {
if ( (firstName.length + content.length) > 32 ) {
let completeContent = `${firstName}: ${content}`
return `${completeContent.slice(0, 32)}...`
} else {
return `${firstName}: ${content}`
}
} else if (value.type === 1) {
return <span>{firstName}: <FontAwesomeIcon icon={faCalendarAlt} className="text-theme" /> Schedule</span>
} else if (value.type === 2) {
return (
<span>{firstName}: <FontAwesomeIcon icon={faClipboard} className="text-theme" />Files</span>
);
} else if (value.type === 3) {
return <span>{firstName}: <FontAwesomeIcon icon={faPoll} className="text-theme" /> Poll</span>
} else if (value.type === 4) {
return <span>{firstName}: <FontAwesomeIcon icon={faTasks} className="text-theme" /> Quiz</span>
} else if (value.type === 6) {
if ( (firstName.length + content.length) > 32) {
let len = 32 - firstName.length;
return <span>{firstName}: <FontAwesomeIcon icon={faImage} /> {content.length > len ? content.slice(0, len) + '…' : content}</span>
} else {
return <span>{firstName}: <FontAwesomeIcon icon={faImage} /> Photo</span>
}
} else if (value.type === 7) {
if ( (firstName.length + content.length) > 32) {
let len = 32 - firstName.length;
return <span>{firstName}: <FileIcon message={value} /> {content.length > len ? content.slice(0, len) + '…' : content}</span>
} else {
return <span>{firstName}: <FileIcon message={value} /> {value.metaData && value.metaData.name}</span>
}
} else if (value.type === 8) {
return <span>{content.length > 36 ? `${content.slice(0, 36)}...` : content}</span>
} else if (value.type === 9) {
return <span>{content.length > 36 ? `${content.slice(0, 36)}...` : content}</span>
} else {
return value.type
}
}
const {
key,
currentChannel,
workspaceData,
workspace,
setCurrentChannel,
setCurrentWorkspace,
setParticipants,
resetData
} = props;
const roomData = props.roomData;
const { roomId } = roomData;
return(
<li
className={currentChannel && (roomData.roomId === currentChannel.roomId)
? "active rounded-lg py-1 m-1 bg-card-theme shadow-sm text-theme"
: "rounded-lg py-1 m-1 bg-card-theme shadow-sm text-theme"}
key={key}
onClick={() => {
setCurrentChannel({ ...roomData, roomId })
setCurrentWorkspace({ ...workspaceData, ...workspace })
setParticipants(workspace.workspaceId, roomId)
resetData()
// setLeftPanel(!this.props.displayLeftPanel);
}}
name={roomData.roomName}
active={currentChannel && (roomData.roomId === currentChannel.roomId)}
>
<div className="d-flex align-items-center p-2 w-100">
<div className={roomLiveStatus ? "liveroom" : ""}>
<img
className={roomLiveStatus ? "mr-2 rounded-circle profile-image" : "mr-2 rounded-circle"}
src={roomData.roomPic}
style={{ height: 45, width: 45 }} />
</div>
<div className="flex-grow-1">
<div className="d-flex align-items-center">
{(roomData.roomType === 1)
&& <FontAwesomeIcon
icon={faLock}
className="text-success mr-2"
size="xs" />}
<p className="mb-0 text-theme">{roomData.roomName}</p>
</div>
{roomData.lastMessage
&& <small className="text-theme text-theme-lighter">
<span>{strip(roomData.lastMessage)}</span>
</small>}
</div>
<div className="text-right align-self-start">
{/* <FontAwesomeIcon
icon={faThumbtack}
style={isPinned ? { fontSize: 12, transform: "rotate(45deg)" } : { fontSize: 12 }}
className={isPinned ? "text-theme" : "text-secondary"} /> */}
<p
className="mb-0 text-theme small text-theme-lighter"
style={{ whiteSpace: "nowrap" }}
>
{roomData.lastMessage
&& timeFromNow(roomData.lastMessage.timestamp)}
</p>
{/* Messages Notification */}
{/* <span className="text-white bg-primary smaller font-weight-bold" style={{ whiteSpace: "nowrap", borderRadius: "2px", padding: "3px 3px 3px 3px" }}>
99+</span> */}
</div>
</div>
</li>
)
}
const WorkspaceListElement = (props) => {
const [workspaceData, setWorkspaceData] = useState({});
const [loadingWorkspaceData, setLoadingWorkspaceData] = useState(true);
const [roomsDataArray, setRoomsDataArray] = useState([]);
const [sortingCount, setSortingCount] = useState(0);
const getWorkspaceData = async () => {
const { workspace } = props;
let docRef = await firebase.firestore().collection(`workspaces`).doc(`${workspace.workspaceId}`)
let workspace_data = await docRef.get()
.then(function (doc) {
if (doc.exists) {
// console.log("Document data workspace:", doc.data());
const workspaceData = doc.data()
return workspaceData;
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
})
.catch(function (error) {
console.log("Error getting document:", error);
})
setWorkspaceData(workspace_data);
setLoadingWorkspaceData(false);
}
const getAllRoomsData = () => {
const { workspace, roomsVisible, setChannels } = props
let roomsArray = []
let roomsDataPromises = []
let roomsDataArray = []
getWorkspaceData()
roomsArray = workspace[roomsVisible] && Object.values(workspace[roomsVisible]).map((room, key) => (
room.roomId
))
// console.log(`roomsArray ${JSON.stringify(roomsArray)}`)
roomsDataPromises = roomsArray.map((roomId, key) => firebase.firestore().collection(`workspaces/${workspace.workspaceId}/rooms`).doc(`${roomId}`).get())
Promise.all(roomsDataPromises).then(values => {
roomsDataArray = values.map(value => {
return { ...value.data(), roomId: value.id }
})
setChannels(roomsDataArray)
})
}
const {
workspace,
_handleAddRoom,
_handleOpenWorkspaceDetails,
roomsVisible,
currentChannel,
allChannels,
searchTerm,
sortType
} = props;
const regex = new RegExp(searchTerm, "gi");
useEffect(() => {
getAllRoomsData()
},[])
useEffect(() => {
getAllRoomsData()
},[props.roomsVisible, props.workspace[props.roomsVisible]])
useEffect(() => {
getWorkspaceData()
},[props.workspace])
useEffect(() => {
console.log('sorttype changed')
switchSort(allChannels, sortType)
setSortingCount((prev) => prev + 1)
},[sortType])
const compareTimestamp = (t1 = null, t2 = null) => {
if (t1 && t2) {return t2.timestamp - t1.timestamp}
if (t1 && t2 == null) {return -1}
if (t2 && t1 == null) {return 1}
return 0
}
const compareTimestampLodashLatestFirst = (allChannels) => {
const sorted = _.orderBy(allChannels, (channel) => {
})
props.setChannels(sorted);
return (sorted);
}
const compareAlphabetLodashAtoZ = (allChannels) => {
const sorted = _.sortBy(allChannels, (channel) => channel.roomName)
// console.log('atoz')
props.setChannels(sorted)
return (sorted);
}
const compareAlphabetLodashZtoA = (allChannels) => {
const sorted = _.sortBy(allChannels, (channel) => channel.roomName)
// console.log('ztoa')
props.setChannels(sorted.reverse())
return (sorted.reverse());
}
const switchSort = (allChannels, sortType) => {
// console.log(allChannels,"allChannels")
switch (sortType) {
case 0:
return compareTimestampLodashLatestFirst(allChannels)
case 1:
return compareAlphabetLodashAtoZ(allChannels)
case 2:
return compareAlphabetLodashZtoA(allChannels)
case 3:
return compareTimestampLodashLatestFirst(allChannels)
default:
return compareTimestampLodashLatestFirst(allChannels)
}
}
// console.log(allChannels,"before return")
return(
<>
{
searchTerm && searchTerm.length > 0
? allChannels
&& allChannels
.filter(item => {
return item.roomName.match(regex) || (item.lastMessage && item.lastMessage.content && item.lastMessage.content.match(regex))
})
.sort((a, b) => switchSort(a, b, sortType))
.map((room, key) => (
<RoomItem
roomData={room}
key={key}
index={key}
currentChannel={currentChannel}
workspace={workspace}
workspaceData={workspaceData}
allChannels={allChannels}
{...props}
/>
))
: allChannels &&
allChannels.map((room, key) => {
return(
<RoomItem
roomData={room}
key={room.roomName + key}
index={key}
currentChannel={currentChannel}
workspace={workspace}
workspaceData={workspaceData}
{...props}
/>
)
})
}
</>
)
}
const mapStateToProps = state => ({
roomsVisible: state.workspace.roomsVisible,
currentChannel: state.channel.currentChannel,
allChannels: state.channel.allChannels,
platform: state.channel.platform
})
export default connect(mapStateToProps, {
setChannels,
setCurrentChannel,
setCurrentWorkspace,
setParticipants,
resetData
})(WorkspaceListElement);
Edit: I fixed it by using lodash cloneDeep. If anyone is stuck in a similar situation refer to this react-redux update item in array doesn't re-render
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>);
}