react-select not updating value when changing option - reactjs

I load my supplier using SWR. But it is wrapped in various classes. I get it like so:
const supplier = new Supplier(supplierID)
This gives me a supplier object.
I then have a select drop down:
options = assetPriceCategories.map((category) => {
return {
value: category.node.id,
label: category.node.name,
property: 'assetPriceCategory'
}
})
selected = { value: supplier.assetPriceCategory.id, label: supplier.assetPriceCategory.name }
<Select name="assetPriceCategory"
instanceId={useId()}
value={selected}
options={options}
onChange={option => supplierDataChange(option)}
/>
This works, the select shows and the options show, and the selected item is selected. My onChange:
const supplierDataChange = (object) => {
if(typeof supplier[object.property] === 'object') {
supplier[object.property].id = object.value
} else {
supplier[object.property] = object.value
}
}
Doing console.log() at the end of this function proves that the supplier has changed, but the <Select> object does not update, it remains the same option as loaded in the beginning.

Related

Material UI Autocomplete Dropdown option not working after switching tabs

In my new project, I am using material UI autocomplete within tabs, and using useImmer hooks for state management. Values in the autocomplete are populated through map function and everything works properly. However, the dropdown functionality is not working after switching the Tabs.
The values are reaching to this component as
const Dropdownlist = ({ defaultRates, value, onChange, index }) => {
return (
<Autocomplete
{...defaultRates}
size="small"
inputValue={value}
value={value}
autoSelect={true}
clearOnEscape={true}
onChange={(event, newValue) => {
onChange( newValue, index );
}}
renderInput={(params) => <TextField {...params} />}
/>
);
};
export default Dropdownlist;
Values of 'defaultRates' was built using
const ratings =
Rates.map((charge) => {
return ({ id: charge.rateid, label: charge.rate });
});
const defaultRates = {
options: ratings,
getOptionLabel: (option) => option.label,
};
Then,
const Rates = [
{
rateid: 101,
rate:"10"
},
{
rateid: 102,
rate:"30"
},
{
rateid: 103,
rate:"1"
},
{
rateid: 104,
rate:"2"
},
];
export default Rates;
Finally, On Change functionality
const onChange = (e,i) => {
let newState;
if(e)
{
const { id, label } = e;
newState = transactions.map((item, index) => {
var tds = (label/100)*item.amount;
if (index === i) {
return {
id: item.id,
transaction: item.transaction,
amount: item.amount,
name: item.name,
type: item.type,
ts:item.ts,
tr:label,
tds: tds,
error:false,
};
} else {
return item;
}
});
setTransactions(newState);
}
}
In the first tab I have many autocomplete dropdown and the selected values are also using in the second tab. If I switch to Tab2 and return back to Tab1, I can see the selected values there. But If I want to change the selected value, nothing happens while clicking the dropdown icon. Please let me know if anyone ever experienced in this context. Would like to know if I using Material UI autocomplete parameters in the right way?
I have gone through Material UI documentation and Autocomplete params. Please advise if it is a state management issue or Mat UI bug?

React-Select on selecting a value the dropdown options unavailable

Process the prop from another component to match in the react-select options
const labelOptionsProcessed = []
labelOptions.map(item => {
let tmpObj = {
id: item.id,
label: item.name,
name: item.name
}
labelOptionsProcessed.push(tmpObj)
})
tmpObj is the structure of the options
<Select
options={labelOptionsProcessed}
isMulti
></Select>
When a select a dropdown value , the options change to no data available
Before selecting an Option :
After Selecting an Option:
StackBlitz: https://stackblitz.com/edit/react-4ddnq7
You have to set a value property for options instead of id
EX:
const labelOptionsProcessed = []
labelOptions.map(item => {
let tmpObj = {
value: item.id, // here
label: item.name,
name: item.name
}
labelOptionsProcessed.push(tmpObj)
});
I guess this is not the proper way to generate the options list, I'd do something like this
const labelOptionsProcessed = labelOptions.map(({ value, name: label, name }) => {
return {
value,
label,
name
};
});

How to set value for the checkbox in ReactJS

I try to show my value using checkbox. Value always comes for the console log. But it didn't set for the checkbox. Here is the code and image for my problem:
var NotePage = createClass({
addTags(e) {
console.log("id****************", e.target.id);
let id = e.target.id;
let selectedTags = this.state.selectedTags;
if (selectedTags.includes(id)) {
var index = selectedTags.indexOf(id)
selectedTags.splice(index, 1);
} else {
selectedTags.push(id);
}
console.log("id****************selectedTags", selectedTags);
this.setState({
selectedTags: selectedTags
})
},
render: function () {
assignStates: function (note, token, tagCategories) {
let fields = [];
fields["title"] = note.title_en;
fields["body"] = note.body_en;
let selectedFileName = null
if (note.file_url_en != "") {
console.log("note.file_url_en ", note.file_url_en);
selectedFileName = note.file_url_en
}
let selectedTags = [];
let n = 0;
(note.note_tag).forEach(tag => {
selectedTags.push(tag.id.toString());
n++;
});
console.log("id****************first", selectedTags);
let initial_values = {
note: note,
id: note.id,
api: new Api(token),
message: "",
title: note.title_en,
body: note.body_en,
fields: fields,
isEdit: false,
selectedTags: selectedTags,
tagCategories: tagCategories,
selectedFileName: selectedFileName,
}
return initial_values;
},
const { selectedTags } = this.state;
{(tagCategory.tags).map((tag) => (
<div className="col-3">
<div>
<input
type="checkbox"
value={selectedTags.includes(tag.id)}
id={tag.id}
onChange={this.addTags} />
<label style={{ marginLeft: "10px", fontSize: "15px" }}>
{tag.name_en}
</label>
</div>
</div>
))
}
})
Image related for the problem
You've an issue with state mutation. You save a reference to the current state, mutate it, and then save it back into state. This breaks React's use of shallow reference equality checks during reconciliation to determine what needs to be flushed to the DOM.
addTags(e) {
let id = e.target.id;
let selectedTags = this.state.selectedTags; // reference to state
if (selectedTags.includes(id)) {
var index = selectedTags.indexOf(id)
selectedTags.splice(index, 1); // mutation!!
} else {
selectedTags.push(id); // mutation!!
}
this.setState({
selectedTags: selectedTags // same reference as previous state
});
},
To remedy you necessarily return a new array object reference.
addTags(e) {
const { id } = e.target;
this.setState(prevState => {
if (prevState.selectedTags.includes(id)) {
return {
selectedTags: prevState.selectedTags.filter(el => el !== id),
};
} else {
return {
selectedTags: prevState.selectedTags.concat(id),
};
}
});
},
Use the "checked" attribute.
<input
type="checkbox"
value={tag.id}
checked={selectedTags.includes(tag.id)}
id={tag.id}
onChange={this.addTags} />
also, about the value attribute in checkboxes:
A DOMString representing the value of the checkbox. This is not displayed on the client-side, but on the server this is the value given to the data submitted with the checkbox's name.
Note: If a checkbox is unchecked when its form is submitted, there is
no value submitted to the server to represent its unchecked state
(e.g. value=unchecked); the value is not submitted to the server at
all. If you wanted to submit a default value for the checkbox when it
is unchecked, you could include an inside the
form with the same name and value, generated by JavaScript perhaps.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#value
I think you should use checked property instead of value.
For reference check react js docs here
You are mutating state variable directly with selectedTags.splice(index, 1); and selectedTags.push(id);
What you need to do is make a copy of the state variable and change that:
addTags(e) {
let id = e.target.id;
if (this.state.selectedTags.includes(id)) {
this.setState(state => (
{...state, selectedTags: state.selectedTags.filter(tag => tag !== id)}
))
} else {
this.setState(state => (
{...state, selectedTags: [...state.selectedTags, id]}
))
}
}

Semantic UI dropdown not setting value after selecting option

I am using React semantic ui. I am rendering a dropdown in Fieldset. I have written code such that, once a option is selected, the options is updated such that the selected option is removed from the list. But when I select an option from the dropdown, the selected value is not displayed, rather it shows empty.
Here is my code:
This is my dropdown code:
<Dropdown
name={`rows.${index}.mainField`}
className={"dropdown fieldDropdown"}
widths={2}
placeholder="Field"
fluid
selection
options={mainFieldOptions}
value={row.mainField}
onChange={(e, { value }) => {
setFieldValue(`rows.${index}.mainField`, value)
updateDropDownOptions(value)
}
}
/>
My options:
let mainField = [
{ key: "org", text: "org", value: "org" },
{ key: "role", text: "role", value: "role" },
{ key: "emailId", text: "emailId", value: "emailId" },
]
Also, I have:
const [mainFieldOptions, setMainFieldOptions] = useState(mainField)
And,
const updateDropDownOptions = (value:any) => {
let updatedOptions: { key: string; text: string; value: string }[] = []
mainFieldOptions.forEach(option => {
if(option.key != value){
updatedOptions.push({ key:option.key , text:option.key, value:option.key })
}
})
setMainFieldOptions(updatedOptions)
console.log("mainfield", mainField)
}
In onChange, if I dont call updateDropDownOptions() method, the dropdown value is set. But when I call the method, its giving blank value. Please help.
There are few changes required in your code,
You are pushing the entire initialValues when you are adding a row which is an [{}] but you need to push only {} so change your code to initialValues[0] in your push method.
Its not needed to maintain a additional state for the options. You can filter the options based on the selected option in other rows which is available in the values.rows .
Util for filtering the options
const getMainFieldOptions = (rows, index) => {
const selectedOptions = rows.filter((row, rowIndex) => rowIndex !== index);
const filteredOptions = mainField.filter(mainFieldOption => !selectedOptions.find(selectedOption => mainFieldOption.value === selectedOption.mainField));
return filteredOptions;
}
Call this util when rendering each row
values.rows.length > 0 &&
values.rows.map((row, index) => {
const mainFieldOptions = getMainFieldOptions(values.rows, index);
Working Sandbox

Why isn't my React app re-rendering when I change the state?

I have a React app which uses the following Model:
export interface DataModel {
itemId: string,
itemName: string,
}
I define an initial value for the model like this:
export const INIT_ITEM:DataModel = {
itemId: "",
itemName: "",
}
This is used in a Dashbaord Component, which shows a form allowing the user to set a name, minimumValue and maximumValue. Name is required; minimumValue and maximumValue are not, but if they are both present then maximumValue needs to be greater than minimumValue. This validation is done when the form is submitted. This is the Dashboard Component:
interface ErrorHolder { // Holds error strings
selectedItems: string,
selection1: string,
selection2: string,
valid: boolean
}
const INIT_VALIDATION:ErrorHolder = {
selectedItems: "",
selection1: "",
selection2: "",
valid: false
}
// availableItems is an array of <DataModel>, prepopulated with available options
// selectedItems is an array of <DataModel>, and is initially empty
// selection1 and selection2 are both <DataModel> objects, which are initialised as above
//
// The items from availableItems are displayed with checkboxes. The user must select at least one item to // display on the Dashboard; the list of selected items is stored as selectedItems
// The user must also select an item to be 'selection1' and a different item to be 'selection2'
//
const Dashboard = ( {availableItems, selectedItems, setSelectedItems, selection1, setSelection1, selection2, setSelection2} ) => {
const [valid, setValid] = useState<ErrorHolder>(INIT_VALIDATION); // Initialise error strings
const handleChangeItemSelected = (event:React.ChangeEvent<HTMLInputElement>, checked:boolean) => {
const itemId:number = event.target.value;
if(checked) {
// Checkbox is checked, so this item should be shown
const newSelectedItems = [...selectedItems];
const fullItem = availableItems.filter(item => item.itemId === itemId);
newSelectedItems .push(fullItem[0]);
setSelectedItems(newSelectedItems );
}else {
// Checkbox is not checked, so this item should not be shown
const newSelectedItems = selectedItems.filter(item=> item.itemId !== itemId);
setSelectedItems(newSelectedItems );
}
}
const handleSelectItem = (event: { target: HTMLSelectElement }) => {
let thisItem:DataModel|undefined = undefined;
let selectedId = parseInt(event.target.value);
if(selectedId !== 0) {
const findItem = availableItems.filter(item => item.itemId === selectedId);
thisItem = findItem[0];
}
// Check which item is being changed
switch(event.target.id) {
case 'selection1':
setSelection1(thisItem);
break;
case 'metric2Select':
setSelection2(thisItem);
break;
}
}
const handleSubmit = (event: SyntheticEvent) => {
event.preventDefault();
// Validate
let tempValid:ErrorHolder = INIT_VALIDATION;
tempValid.valid = true;
// Validate everything - set error if found
if(selectedItems.length === 0) { // There must be at least 1 selected item
tempValid.valid = false;
tempValid.selectedItems = "Please select at least one item";
}
// Make sure 2 different items are selected for selection 1 and selection 2
if(!selection1) {
tempValid.valid = false;
tempValid.selection1 = "Please select two items to show on the chart";
}else if(!selection2) {
tempValid.valid = false;
tempValid.selection2 = "Please select two items to show on the chart";
}else if(selection1.itemId === selection2.itemId) {
tempValid.valid = false;
tempValid.selection2= "Please select two different items to show on the chart";
}
}
setValid(tempValid);
}
return (
<form onSubmit = { handleSubmit }>
// List available options
<div>{valid.selectedItems}</div> // Display error message for selected items
<table>
<thead>
<tr>
<td>Name</td>
<td>Show</td>
</tr>
</thead>
<tbody>
{
availableItems.map((item) => { // List all available items with checkboxes
<tr key={item.itemId}>
<td>{item.itemName}</td>
<td><Checkbox value={item.itemId}
onChange={handleChangeItemSelected}
checked={selectedItems.includes(item)} /></td>
</tr>
})
}
</tbody>
</table>
<select id = "selection1" onChange = { event => handleSelectItem(event) }
value = { selection1?.itemId}>
<option key="sel1" value="0">Please Select</option>
{
availableItems.map((item) => <option key={item.itemId} value={item.itemId}>{item.itemName}</option>;
}
</select>
<div>{valid.selection1}</div>
<select id = "selection2" onChange = { event => handleSelectItem(event) }
value = { selection2?.itemId}>
<option key="sel2" value="0">Please Select</option>
{
availableItems.map((item) => <option key={item.itemId} value={item.itemId}>{item.itemName}</option>;
}
</select>
<div>{valid.selection2}</div>
</form>
);
}
export default Dashboard;
The problem I'm having is that the errors don't show if I enter invalid data (eg don't select anything, select same thing for selection1 and selection2 etc) and click 'Submit'. If I do a console log I can see that the valid variable has been set correctly, but it seems that the component is not re-rendering to show it.
What am I doing wrong?
In every call to handleSubmit you're mutating the same object INIT_VALIDATION, and passing the same object to a setState function won't trigger a rerender.
If you make a copy by replacing
let tempValid:ErrorHolder = INIT_VALIDATION
with
const tempValid:ErrorHolder = {...INIT_VALIDATION}
I would expect it to work. (The let wasn't part of the problem, but if you're not changing the variable itself you can use a const instead.)

Resources