Clearing values from react-select - javascript

I have created this function inside onChange to handle my inputs using react-select. it works perfectly fine in terms of submitting the input and using it later. The thing is, the clear property is not working. Before I created my function inside onChange, it used to work whenever I click on "x". Yet now it is not. Any help?
{["role1", "role2", "role3"].map(role => (
<Select
cacheOptions
defaultOptions
loadOptions={this.loadOptions}
key={role}
getOptionLabel={i => i.full_name}
getOptionValue={i => i.id}
closeMenuOnSelect={false}
isMulti
className="selectStyle"
placeholder={t(role)}
value={form.users.filter(item => item.role === role)}
onChange={values => {
let newValues = form.users;
values.forEach(item => {
if (
newValues.filter(v => v.id === item.id && v.role === role)
.length
) {
return;
}
item.role = role;
newValues.push(item);
});
setFormValue({ name: "users", value: newValues });
}}
clearValue={() => {
setFormValue({
name: "users",
value: form.users.filter(item => item.role !== role)
});
}}
/>

Related

MUI Select with Array of Objects as values. How to unselect predefined state?

I have an array of object that looks like this:
[
{
_id: "6311c197ec3dc8c083d6b632",
name: "Safety"
},
........
];
I load this array as potential Menu Items for my Select:
{categoryData &&
categoryData.map((cat: any) => (
<MenuItem key={cat._id} value={cat}>
<Checkbox
checked={categories.some((el: any) => el._id === cat._id)}
/>
<ListItemText primary={cat.name} />
</MenuItem>
))}
In my Select I have predefined value for it:
const [categories, setCategories] = useState([
{
name: "Safety",
_id: "6311c197ec3dc8c083d6b632"
}
]);
.......
<Select
labelId="demo-multiple-checkbox-label"
id="demo-multiple-checkbox"
multiple
value={categories}
onChange={(event: any) => {
const {
target: { value }
} = event;
console.log(value);
setCategories(value);
}}
input={<OutlinedInput label="Tag" />}
renderValue={(selected) => selected.map((cat) => cat.name).join(", ")}
>
The problem is I am unable to unselect(de-select) the predefined value. In stead of removing it from array of categories I got it once again in it.
Here is the sandbox example:
https://codesandbox.io/s/recursing-river-1i5jw8?file=/src/Select.tsx:632-757
I understand that values has to be exactly equal to be removed but how I can do that? What is wrong with this kind of handling?
Also I found this case as reference but still couldn't do it as in the case they use formik:
Unselect MUI Multi select with initial value
You can't directly save the Object as the value. You must use a unique string or stringify the entire object and store it as the value. And based on that value calculate the selected value rendered text. Here is something that will work for you.
Changes: use _id as the value instead of the entire object. And added a new selected value renderer.
import {
FormControl,
Select,
MenuItem,
InputLabel,
Checkbox,
ListItemText,
OutlinedInput
} from "#mui/material";
import React, { useState, useMemo } from "react";
const categoryData = [
{
_id: "6311c197ec3dc8c083d6b632",
name: "Safety"
},
{
_id: "6311c8e6ec3dc8c083d6b63b",
name: "Environment"
},
];
const SelectForm = () => {
const [categories, setCategories] = useState(["6311c197ec3dc8c083d6b632"]);
const selectedCategories = useMemo(() => {
let value = "";
categoryData.forEach((cat) => {
if (categories.some((catId: any) => catId === cat._id)) {
if (value) {
value += ", " + cat.name;
} else {
value = cat.name;
}
}
});
return value;
}, [categories]);
return (
<FormControl fullWidth>
<InputLabel id="demo-multiple-checkbox-label">Category</InputLabel>
<Select
labelId="demo-multiple-checkbox-label"
id="demo-multiple-checkbox"
multiple
value={categories}
onChange={(event: any) => {
const {
target: { value }
} = event;
console.log(value);
setCategories(value);
}}
input={<OutlinedInput label="Tag" />}
renderValue={() => selectedCategories}
>
{categoryData &&
categoryData.map((cat: any) => (
<MenuItem key={cat._id} value={cat._id}>
<Checkbox
checked={categories.some((catId: any) => catId === cat._id)}
/>
<ListItemText primary={cat.name} />
</MenuItem>
))}
</Select>
</FormControl>
);
};
export default SelectForm;
Initially, you have to pass an empty array while setting the state. This will solve your problem.
Code changes will look like this -
const [categories, setCategories] = useState([]);

React Material UI Textfield onChange

const [formData, setFormData] = useState({});
useEffect(() => {
fetch("/formdata")
.then((res) => res.json())
.then((data) => setFormData(data));
}, []);
console.log("Form Data", formData);
//Sorting by order
let attr;
form.forms.map((y) => {
return (attr = y.formAttributes.sort((a, b) => {
return a.order < b.order ? -1 : 1;
}));
});
return (
{attr.map((attri, index) => {
return (
<TextField
key={index}
label={attri.label}
value={formData[attri.datakey] || ""}
onChange={event => {const {value} = event.target; setFormData({formData: value})}}
/>
);
})}
)
I would like to ask help on how to manage multiple fields in Textfield onChange area? Currently, if I am going to input a value there are no changes that is happening.
Here's my code.
Tried the approach of using e.target.value however it would still stay the same.
You should add a name attribute, where the 'name' is the key of the key-value-pair in your object.
e.g.
<TextField
key={index}
name="somename"
label={attri.label}
value={formData[attri.datakey] || ""}
onChange={handleChange}
/>
Then you can update the field like that.
setFormData({
...formData,
[e.target.name]: e.target.value // becomes "somename: some value"
})

How to remove a object from an array in react hook

I have a list of buttons and they are multi selectable. when I select the buttons I want to add , it will be added to the array perfectly and turned to blue and when I click on a already selected button it should be get removed from the array and turned to white but it doesn't. Below shows what I tried so far.
The first array (products) is to save the API data. Second one is to save the selected products.
const [products, setProducts] = useState([]);
const [selectedProducts, setselectedProducts] = useState<any>([]);
{products.length !== 0 ? (
products?.map(
(item: any, index) => (
<SButton
key={item.key}
label={item.value}
onClick={() => {
selectedProducts(item);
}}
isSelected={item.selected === "YES"}
/>
)
)
) : (
<p>No products</p>
)}
function selectedProducts(item:any){
if(selectedProducts.length !== 0){
selectedProducts.map((selecteditem:any)=>{
if(selecteditem.key == item.key ){
item.selected = "NO";
setselectedProducts(selectedProducts.filter((item: any )=> item.key !== selecteditem.key))
}else{
item.selected = "YES";
setselectedProducts([...selectedProducts, item]);
}
})
}else{
setselectedProducts([...selectedProducts, item]);
item.selected = "YES";
}
}
How about something like this?
const [products, setProducts] = useState([]);
const [selectedProduct, setSelectedProduct] = useState();
{(products.length > 0) ? (
<Fragment>
{products.map((item)=>{
const {key, value, selected } = item;
return (
<SButton
key={key}
label={value}
onClick={() => {
setSelectedProduct(item);
const newState = !selected;
products.forEach((p)=>{
if (p.key === item.key) p.selected = newState;
});
setProducts([...products]);
}}
isSelected={selected}
/>
);
})}
</Fragment>
): (
<p>No products</p>
)}
First, you are using selectedProducts both as function name and name of selected products state.
Second, you should not assign values to item. Use spread operator instead.
Also, you can access the previous state from setState instead of using state directly.
function removeFromSelectedProducts(item: any) {
// Set selected to 'NO' in products array
setProducts((prevProducts) =>
prevProducts.filter((product) =>
product.key === item.key ? { ...product, selected: 'NO' } : product
)
)
// Remove product from selectedProducts
setSelectedProducts((prevSelectedProducts) =>
prevSelectedProducts.filter((product) => product.key !== item.key)
)
}
function addToSelectedProducts(item: any) {
// Set selected to 'YES' in products array
setProducts((prevProducts) =>
prevProducts.filter((product) =>
product.key === item.key ? { ...product, selected: 'YES' } : product
)
)
// Add item to selectedProducts
setSelectedProducts((prevSelectedProducts) => [...prevSelectedProducts, { ...item, selected: 'YES'}])
}
function selectProduct(item: any) => {
if (selectedProducts.some((product) => product.key === item.key)) {
removeFromSelectedProducts(item)
} else {
addToSelectedProducts(item)
}
}
You can simplify this using useReducer hook instead of using separate functions for addition & removal of selected products.

How to capture option selected event in Material UI autocomplete component?

I am using the autocomplete component with filterOptions to suggest the creation of a new value as shown below:
<Autocomplete
multiple
name="participant-tags"
options={people}
getOptionLabel={(option) => option.name}
renderInput={(params) => {
return (
<TextField
{...params}
variant="outlined"
label="Participants"
/>
)
}}
filterOptions={(options, params) => {
const filtered = filter(options, params);
logger.debug('filterOptions(params) %j', params)
// Suggest the creation of a new value
if (params.inputValue !== '') {
filtered.push({
inputValue: params.inputValue,
name: `Add "${params.inputValue}"`,
});
}
return filtered;
}}
onKeyDown={(e) => {
if(e.keyCode === 13) {
// TODO: select currently highlighted option
e.preventDefault()
}
}}
onChange={(e, value, reason) => {
logger.debug(e.type)
logger.debug(value)
logger.debug(reason)
e.preventDefault()
}}
/>
However, I can't figure out where to handle the selection of the "Add this option" to actually add the option?
This was solved leveraging the 'reason' parameter in the onChange handler, and the onKeyDown handler isn't needed:
filterOptions={(options, params) => {
const filtered = filter(options, params);
if (params.inputValue !== '') {
filtered.push({
inputValue: params.inputValue,
[displayOptionsField]: `Add New ${newOptionLabel} "${params.inputValue}"`,
});
}
return filtered;
}}
onChange={(e, value, reason) => {
let newOptions
if (reason==='select-option') {
const last = value.pop();
if (last.inputValue) {
newOptions = value.concat([{[displayOptionsField]: last.inputValue}])
}
else {
newOptions = value.concat([last])
}
}
if (reason==='create-option') {
const last = value.pop();
newOptions = value.concat([{[displayOptionsField]: last}])
}
if (reason==='remove-option') {
newOptions = value
}
if (newOptions) {
onChange(newOptions)
}
}}
The onChange inside the onChange handler is there as a prop from a wrapping component.

update is not capturing and unable to update the input field

please find below code which contains name id and am rendering initially using map
am replacing id value to input type in UI
with the updated input type am trying to update the value onchange
update is not capturing and unable to update the input field
any suggestion?
please refer below snippet
import React, { useState } from "react";
const CstmInput = (props) => {
return (
<input
name={props.name}
type="text"
value={props.value}
onChange={(event) => props.onInputChange(event)}
/>
);
};
export default CstmInput;
import React, { useState } from "react";
import CstmInput from "./CstmInput";
const HierarcyTest = () => {
let rowData = [
{ name: "first", id: 10 },
{ name: "second", id: 20 },
];
const [data, setData] = useState(rowData);
const [name, setName] = useState({ fn: "test" });
const onInputChange = (e) => {
console.log("---event---", e.target.value);
setName({ ...name, fn: e.target.value });
};
let updateValue = () => {
let newData = data.map(
(item, index) =>
(item.id = (
<CstmInput name={item.name} value={item.id} onInputChange={(e) => onInputChange(e)} />
))
);
setData([...data, newData]);
};
return (
<div>
<div>Testing</div>
{data.map((val) => (
<h6>
{" "}
{val.name} {val.id}
</h6>
))}
<button onClick={updateValue}> Click </button>
</div>
);
};
export default HierarcyTest;
A few things why your code isn't working as intended:
1.
let updateValue = () => {
let newData = data.map((item, index) => {
if (item.id === 10) {
return [
(item.id = (
<CstmInput
value={item.id}
onInputChange={(e) => onInputChange(e)}
/>
)),
];
}
});
setData([...data, newData]);
};
In the above function inside the callback of map, you're only returning when a condition satisfies. Are you trying to filter the array instead? If not then return something when the if condition fails.
And why are you returning an array?
return [
(item.id = (
<CstmInput
value={item.id}
onInputChange={(e) => onInputChange(e)}
/>
)),
];
the above code seems logically wrong.
2.
const onInputChange = (e) => {
console.log("---event---", e.target.value);
setName({ ...name, fn: e.target.value });
};
If you want to update state which depends on the previous state then this is how you do it:
setName((prevState) => ({ ...prevState, fn: e.target.value }));
but since you're not actually relying on the properties of the previous state you can just use:
setName({fn: e.target.value });
Note that since your state only has one property and you want to update that single property you can completely overwrite the state, you don't need to spread the previous state.
update
change the updateValue function as the following:
let updateValue = () => {
setData(prevData => {
return prevData.map(el => {
return { ...el, id: <CstmInput value={el.id} onInputChange={(e) => onInputChange(e)} /> };
})
});
};
A stackblitz example I've created that implements what you're trying to do.

Categories

Resources