MUI Custom groupBy - arrays

I have built MUI grouped labels, but the moment I try to add the element into the state, the application crashes. Here's the minimal code.
const options = labels.map(option => {
return {
type: JSON.parse(localStorage.getItem("recentTags") as string).includes(option) ? "RECENT" : "ALL ",
labelText: option
};
});
<Autocomplete
multiple
disableClearable
filterSelectedOptions
groupBy={option => option.type}
isOptionEqualToValue={(option, value) => {
const labelText = value.labelText ? value.labelText : value;
return option.labelText.toUpperCase() === labelText.toUpperCase();
}}
value={value}
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
onChange={(_event, newValue) => {
console.log(newValue.map(el => el));
const latestLabel: any = newValue.slice(-1).pop();
const prevLabels = JSON.parse(localStorage.getItem("recentTags") as string);
if (!prevLabels.includes(latestLabel.labelText) && prevLabels.length < 5) {
localStorage.setItem("recentTags", JSON.stringify([...prevLabels, latestLabel.labelText]));
}
const newLabel = newValue.filter((x) => !labels.includes(x))[0];
setValue(newValue);
onSaveEcardLabels!(newValue, ecardItem.id);
if (!!newLabel) {
labels.push(newLabel.labelText);
}
}}
/>
I am storing recently used tags in localstorage, but I am confused in it's logic as well, I am not able to replace any newly recently used tag in local storage. I believe the problem lies in onChange event.
While onChange in newValue I get previous index and current Object, I would like only single array with index i.e. labelText.

Related

React retrieve checked value in checkbox

I am trying to retrieve the checked values of the checkboxes and save them into array.
I tried :
arr.push(setNewItem(checked))
arr.push(e.target.value.checked)
arr.push(items.checked)
But these return type error or undefined values.
const [checkedItems, setCheckedItems] = useState([]);
const handleChange = (e) => {
if (e.target.checked) {
var arr = [...checkedItems];
//arr.push(setNewItem(e.target.value.checked));
setCheckedItems(arr);
console.log(arr);
} else {
checkedItems = "";
}
setIsChecked((current) => !current);
};
return (
<div className="App">
<StyleForm>
<StyleInput
type="text"
placeholder="Add"
value={newItem}
onChange={(e) => setNewItem(e.target.value)}
onKeyPress={handleOnKeyPress}
/>
<ButtonAddStyle onClick={() => addItem()}>add</ButtonAddStyle>
<StyleUl>
{items.map((item) => {
return (
<StyleLi key={item.id}>
<StyleCheckBox
type="checkbox"
value={isChecked}
onChange={handleChange}
/>
{item.value}
{""}
<ButtonDelStyle onClick={() => deleteItem(item.id)}>
X
</ButtonDelStyle>
</StyleLi>
);
})}
</StyleUl>
</StyleForm>
</div>
);
arr.push(e.target.checked);
Is the way to go and get rif of :
else {
checkedItems = "";
}
you cannot update a hook this way you will get an error when you try to unchek an input:
Uncaught TypeError : Assignment to constant variable
Now let's see what you are trying to do you are storing e.target.checked each time an input is cheked so checkedItems will look something like this :
[true, true, true, true, true, true, true, true]
why do you need this ? better is to store the ids of checked items :
const handleChange = (isChecked, id) => {
var arr = [...checkedItems];
if (isChecked) {
arr.push(id);
setCheckedItems(arr);
} else {
setCheckedItems(checkedItems.filter((storedId) => storedId !== id)); // delete the id from checkedItems if the corresponding input is unckecked
}
};
and from jsx :
<StyleCheckBox
type="checkbox"
value={item.id}
onChange={(e) => {
handleChange(e.target.checked, item.id);
}}
/>;
Now look at this :
<StyleCheckBox
value={isChecked} // this line
>
you are mapping through items creating multiple checkBoxes but all of them share the same value. and the value attribute of an input of type checkbox is not what you think it is, learn more here. so you can use value={item.id} to have an unique value for each input and get rid of isChecked useState hook you really don't need it
this could solve your problem.
const [checkedItems, setCheckedItems] = useState([]);
const handleChange = (e) => {
setCheckedItems( prev => [...prev, e.target.checked]);
setIsChecked((current) => !current);
};

How do I get my options to display with Autocomplete (MUI)?

I'm trying to help my friend figure out why Autocomplete isn't showing anything.
Below is the code:
var names = [];
const schoolList = async () => ( await axios.get("http://localhost:5000/api/grabUnivNames/")
.then((res) => {
// schoolList = JSON.stringify(res.data.msg)
names = res.data.msg.map(user => user.school_name);;
console.log(names)
// return res.data.msg.map(user => user.school_name);
})
.catch((error) => {
console.log("ERROR");
console.log(error);
})
);
schoolList();
return() with Autocomplete:
<Autocomplete
options={names}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="School Name" />}
/>
What names contains:
What shows:
I only started learning about Autocomplete today but I think the problem may be in how he is obtaining names or how names is formatted but I am also very unfamiliar with Autocomplete.
How can I get names to display on the dropdown?
first of all i am assuming that your data fetching is done correctly and you use react functional based components.
You will need 2 main requirements to achieve what you want
first of all replace normal variable names with useState hook of
names array and loading boolean, cause normal variables will not have dynamic values over multiple renders
MUI Autocomplete supports async operation , so you will attach the getSchoolList handler to onOpen prop, and loading prop so let the component show progress while loading
const [names,setNames] = React.useState([])
const [loading, setLoading] = React.useState(false)
const getSchoolList = () => {
setLoading(true)
axios.get("http://localhost:5000/api/grabUnivNames/")
.then((res) => {
// schoolList = JSON.stringify(res.data.msg)
const namesArr = res.data.msg.map(user => user.school_name)
setNames(namesArr)
// return res.data.msg.map(user => user.school_name);
})
.catch((error) => {
console.log("ERROR");
console.log(error);
}).finally(() => setLoading(false))
}
<Autocomplete
options={names}
onOpen={() => getSchoolList()}
loading={loading}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="School Name" />}
/>

How to paste number in separate text fields by material ui?

I'm using material-ui in my react project.
I wanna copy a number and paste in multiple text fields.
const [otpArr, setOtpArr] = useState<string[]>(['', '', '', '', '', ''])
let inputRefs = useRef([React.createRef<HTMLDivElement>(), React.createRef<HTMLDivElement>(), React.createRef<HTMLDivElement>(), React.createRef<HTMLDivElement>(), React.createRef<HTMLDivElement>(), React.createRef<HTMLDivElement>()])
{otpArr && otpArr.map((x, index) => (
<NumberTextField
inputRef={inputRefs.current[index]}
variant='outlined'
size='small'
id='otp'
value={x}
key={index}
onChange={(e: any) => {
const temp = [...otpArr]
temp[index] = e.target.value
setOtpArr(temp)
if ((index < otpArr.length - 1) && e.target.value.length === 1) {
inputRefs.current[index + 1]?.current?.focus()
}
}}
inputProps={{ maxLength: 1 }}
style={{
width: '3rem',
direction: 'ltr',
marginTop: '3rem',
}}
/>
))}
any solution?
Here is a working example:
https://codesandbox.io/s/simple-6-digite-input-past-2qqdg
You can read the paste event and get the 6 digit with an event listener:
// listen "paste" event in useEffect
useEffect(() => {
const onPaste = (e) => {
let paste = (e.clipboardData || window.clipboardData).getData("text");
paste = paste.toUpperCase().split("");
if (paste.length === 6) {
setValue(paste);
}
};
document.addEventListener("paste", onPaste);
return () => {
document.removeEventListener("paste", onPaste);
};
}, [value]);
PS: You can add Event on an input element

#material-ui Autocomplete: set input value programmatically

I have an asynchronous Autocomplete component that works fine so far.
Hopefully the simplified code is understandable enough:
export function AsyncAutocomplete<T>(props: AsyncAutocompleteProps<T>) {
const [open, setOpen] = useState(false);
const [options, setOptions] = useState<T[]>();
const onSearch = (search: string) => {
fetchOptions(search).then(setOptions);
};
return (
<Autocomplete<T>
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
onChange={(event, value) => {
props.onChange(value as T);
}}
getOptionSelected={props.getOptionSelected}
getOptionLabel={props.getOptionLabel}
options={options}
value={(props.value as NonNullable<T>) || undefined}
renderInput={(params) => (
<TextField
{...params}
onChange={(event) => onSearch(event.currentTarget.value)}
/>
)}
/>
);
}
The component above works easily: when the user clicks on the input, the Autocomplete component displays an empty input field where the user can type in a value to search for. After the input has changed, the options are refetched to show matching results.
Now I want to add support for shortcodes: when the user types qq, the search term should be replaced by something, just like if the user would have typed something himself.
However, I found no way to update the value of the rendered TextField programmatically. Even if I set value directly on the TextField, it won't show my value but only the users input.
So, any ideas how to solve this problem?
Thank you very much.
What I've tried so far was to simply update the input within onKeyUp:
// ...
renderInput={(params) => (
<TextInput
{...params}
label={props.label}
onChange={(event) => onSearchChange(event.currentTarget.value)}
InputProps={{
...params.InputProps,
onKeyUp: (event) => {
const value = event.currentTarget.value;
if(value === 'qq') {
event.currentTarget.value = 'something';
}
},
}}
/>
)}
With the code above I can see the something for a short time, but it gets replaced by the initial user input very soon.
Autocomplete is useful for setting the value of a single-line textbox in one of two types of scenarios: combobox and free solo.
combobox - The value for the textbox must be chosen from a predefined set.
You are using it so it not allowing you to add free text (onblur it replaced)
Answer: To take control of get and set value programmatically.
you need a state variable.
Check here codesandbox code sample taken from official doc
Your code with my comment:-
export function AsyncAutocomplete<T>(props: AsyncAutocompleteProps<T>) {
... //code removed for brevity
//This is a state variable to get and set text value programmatically.
const [value, setValue] = React.useState({name: (props.value as NonNullable<T>) || undefined});
return (
<Autocomplete<T>
... //code removed for brevity
//set value
value={value}
//get value
onChange={(event, newValue) => setValue(newValue)}
renderInput={(params) => (
<TextInput
{...params}
label={props.label}
onChange={(event) => onSearchChange(event.currentTarget.value)}
InputProps={{
...params.InputProps,
onKeyUp: (event) => {
//get value
const value = event.currentTarget.value;
//if qq then set 'something'
if (value === "qq") {
setValue({ name: "something" });
}
//otherwise set user free input text
else {
setValue({ name: value });
}
},
}}
/>
)}
/>
);
}

Removing tag element from array also removes the tag behind it react antd

I'm having an issue when I delete a tag that has another tag behind it. For example if i have two ant Tags and i delete the first one, the second one disappears, but when i console.log it, the second one technically is still there. But if I have two ant Tags and I delete the second Tag first, it deletes the second Tag fine without the first Tag disappearing too.
Here is all the code that deleting Tags is associated with, I use mobx for stores, the emailTemplate.toEmail is a string formatted array containing emails seperated with ',' and in the allToEmails variable it gets split into an actual array and I put that variable in a state so I can manipulate it.
const allToEmails = emailTemplate && emailTemplate.toEmail ?
emailTemplate.toEmail.replace(/ /g, '').split(",") : [];
const [ toEmails, setToEmails ] = useState(allToEmails);
useEffect(() =>{
emailTemplate.setAttr("toEmail", toEmails.toString());
emailTemplate.save();
},[toEmails]);
const handleRemoveTag = (value) => {
setToEmails(toEmails.filter(email => email !== value));
};
<React.Fragment>
{
toEmails.map( (email, index) => {
return (
<EmailTag
key={index}
email={email}
index={index}
handleEmailChange={handleEmailChange}
handleRemoveTag={handleRemoveTag}
/>
)
})
}
<React.Fragment />
Here is the EmailTag class:
function EmailTag({intl: { formatMessage }, email, index, handleEmailChange,
handleRemoveTag, ...props}) {
const [ isEditing, setIsEditing ] = useState(false);
const [ emailValue, setEmailValue ] = useState(email);
const handleEmailValueChange = (e) => setEmailValue(e.target.value);
if (!isEditing) {
return (
<EmailTagLabel
key={index}
closable={true}
onClose={() => handleRemoveTag(email)}
>
<span
onDoubleClick={() => {
setIsEditing(true);
}}
>
{emailValue}
</span>
</EmailTagLabel>
)
} else {
return (
<EditEmailInput
autoFocus
value={emailValue}
size="small"
onChange={handleEmailValueChange}
onBlur={() => { handleEmailChange(emailValue, index) ? setIsEditing(false) : setIsEditing(true) }}
onPressEnter={() => { handleEmailChange(emailValue, index) ? setIsEditing(false) : setIsEditing(true) }}
/>
)
}
}
export default injectIntl(observer(EmailTag))
I fixed it by replacing value of key prop with something not index, index messes it up.

Resources