I have multiple react-select inputs, each have their own separate options array. The issue i'm having is I'm now sure how to properly store the output of each select input.
I want to use this select output to POST to my backend but i'm unsure how to do it all with a single handler function.
This is what I have so far, i have 2 paragraphs to just output the appropriate result from the select fields but i can't seem to get it working.
This is the codesandbox i have:
https://codesandbox.io/s/busy-yonath-rlj9e?file=/src/App.js
In your code you are using the useState() hook and everytime a user selects an option, you are setting your state variable to the selected value.
The problem is that everytime you run the setSelectOptions({ e }) function, you are overwriting the existing data with the value of 'e'.
What you can do is:
Create a selectHandler function that takes in 2 arguments (the new value and the value it corresponds to in the state variable)
The code will look something like this:
import "./styles.css";
import Select from "react-select";
import { useEffect, useState } from "react";
export default function App() {
const [selectOptions, setSelectOptions] = useState({
brand: "",
model: "",
color: "",
bodyType: "",
fuelType: "",
transmission: "",
location: ""
});
const brandOptions = [
{ value: "bmw", label: "Bmw" },
{ value: "audi", label: "Audi" },
{ value: "toyota", label: "Toyota" },
{ value: "nissan", label: "Nissan" }
];
const transmissionOptions = [
{ value: "automatic", label: "Automatic" },
{ value: "manual", label: "Manual" }
];
useEffect(() => console.log(selectOptions), [selectOptions])
const handleChange = (e, type) => {
setSelectOptions((previousState) => ({
...previousState,
[type]: e.value,
}));
};
return (
<div className="App">
selected brand: {selectOptions.brand}
<br />
selected transmission:{selectOptions.transmission}
<div className="mb-2" style={{ width: "40%", margin: "0 auto" }}>
<Select
value={selectOptions.brand}
onChange={(e) => handleChange(e, "brand")}
placeholder="Select brand"
options={brandOptions}
/>
<Select
value={selectOptions.transmission}
onChange={(e) => handleChange(e, "transmission")}
placeholder="Select transmission"
options={transmissionOptions}
/>
</div>
</div>
);
}
Just as an explanation, all I am doing in the setSelectOptions() function is passing in the previous values of the state variable and updating the value coinciding to the select field.
Note: Insert this code into your project, I ran it and it worked so let me know if it did help!
Related
I am trying to use mui Autocomplete multi select component but can't make it work as per my requirements. I want it to have default selected values which will be passed as a props from Parent component and dropdown options which I will be getting via an API.
Problem 1 :
I am able to render my deafult values in input box which was passed as props but my selected values should not be there in options. Right now I can add duplicate values meaning even though I have few items selected by default in input box but still I could see them in dropdown. I have tried to read the docs it says The value must have reference equality with the option in order to be selected. but can't understand clearly. Is it because I am using 2 different states(arrays) for value and options?.
Problem 2 (Extra requirement)
In addition to the list of usersOptions that I will get from API to populate the dropdown option, I also need to add an option of All.
Whenever few items are already selected and then user clicks ALL in dropdown all the selected items should be removed and only All should be there in input box and when user tries to open the dropdown, all other values should be disabled.
When the user deselect ALL from the dropdown then only he/she should be able to select individual value(user)
Basically the user should be able to select only ALL or Individual user(value).
Link to CodeSanbox(https://codesandbox.io/s/naughty-elbakyan-hi16x5?file=/src/AutoComplete.js:0-1399)
import { useEffect, useState } from "react";
//This is the structure of data for both users & usersOptions
const AutoComplete = ({ users }) => {
const [selectedUsers, setSelectedUsers] = useState(users);
const [usersOptions, setUsersOptions] = useState([]);
useEffect(() => {
// fetch("https://x.com/api/getUsers")
// .then((response) => response.json())
// .then((data) => setUsersOptions(data));
//let's assume API returned this
const usersOptions = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" },
{ id: 3, name: "User 3" },
{ id: 4, name: "User 4" },
{ id: 5, name: "User 5" },
{ id: 6, name: "User 6" }
];
const usersOptionsWithAll = [{ id: 0, name: "All" }, ...usersOptions];
setUsersOptions(usersOptionsWithAll);
}, []);
console.log("selectedUsers", selectedUsers);
console.log("usersOptions", usersOptions);
return (
<Autocomplete
multiple
id="tags-outlined"
options={usersOptions}
getOptionLabel={(option) => option.name}
filterSelectedOptions
renderInput={(params) => (
<TextField {...params} label="" placeholder="" />
)}
value={selectedUsers}
onChange={(event, newValue) => {
setSelectedUsers(newValue);
}}
/>
);
};
export default AutoComplete;
[1]: https://i.stack.imgur.com/83V7d.jpg
1. You should use isOptionEqualToValue prop to determine if the option represents the given value.
2. It is preferable to use filterOptions prop to add 'All' option or any options you want to add.
import { Autocomplete, TextField, createFilterOptions } from "#mui/material";
import { useEffect, useState } from "react";
const AutoComplete = ({ users }) => {
const [selectedUsers, setSelectedUsers] = useState(users);
const [usersOptions, setUsersOptions] = useState([]);
useEffect(() => {
const usersOptions = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" },
{ id: 3, name: "User 3" },
{ id: 4, name: "User 4" },
{ id: 5, name: "User 5" },
{ id: 6, name: "User 6" }
];
setUsersOptions(usersOptions); // Set options without adds
}, []);
return (
<Autocomplete
multiple
id="tags-outlined"
options={usersOptions}
getOptionLabel={(option) => option.name}
filterSelectedOptions
renderInput={(params) => (
<TextField {...params} label="" placeholder="" />
)}
value={selectedUsers}
onChange={(event, newValue) => {
// Check if 'All' option is clicked
if (newValue.find(option => option.id === 0)) {
// Check if all options are selected
if (usersOptions.length === selectedUsers.length) {
setSelectedUsers([]);
} else {
setSelectedUsers(usersOptions);
}
} else {
setSelectedUsers(newValue);
}
}}
// Add These props
isOptionEqualToValue={(option, value) => option.id === value.id}
filterOptions={(options, params) => {
const filtered = createFilterOptions()(options, params);
return [{ id: 0, name: 'All' }, ...filtered];
}}
/>
);
};
export default AutoComplete;
Mui Autocomplete API
I am trying to use react-select in combination with match-sorter as described in this stackoverflow answer (their working version). I have an initial array of objects that get mapped to an array of objects with the value and label properties required by react-select, which is stored in state. That array is passed directly to react-select, and when you first click the search box everything looks good, all the options are there. The onInputChange prop is given a call to matchSorter, which in turn is given the array, the new input value, and the key the objects should be sorted on. In my project, and reproduced in the sandbox, as soon as you type anything into the input field, all the options disappear and are replaced by the no options message. If you click out of the box and back into it, the sorted options show up the way they should. See my sandbox for the issue, and here's the sandbox code:
import "./styles.css";
import { matchSorter } from "match-sorter";
import { useState } from "react";
import Select from "react-select";
const objs = [
{ name: "hello", id: 1 },
{ name: "world", id: 2 },
{ name: "stack", id: 3 },
{ name: "other", id: 4 },
{ name: "name", id: 5 }
];
const myMapper = (obj) => {
return {
value: obj.id,
label: <div>{obj.name}</div>,
name: obj.name
};
};
export default function App() {
const [options, setOptions] = useState(objs.map((obj) => myMapper(obj)));
return (
<Select
options={options}
onInputChange={(val) => {
setOptions(matchSorter(options, val, { keys: ["name", "value"] }));
}}
/>
);
}
I am sure that the array in state is not getting removed or anything, I've console logged each step of the way and the array is definitely getting properly sorted by match-sorter. It's just that as soon as you type anything, react-select stops rendering any options until you click out and back in again. Does it have something to do with using JSX as the label value? I'm doing that in my project in order to display an image along with the options.
I had to do two things to make your code work:
Replaced label: <div>{obj.name}</div> with label: obj.name in your mapper function.
I am not sure if react-select allows html nodes as labels. Their documentation just defines it as type OptionType = { [string]: any } which is way too generic for anything.
The list supplied to matchSorter for matching must be the full list (with all options). You were supplying the filtered list of previous match (from component's state).
const objs = [
{ name: "hello", id: 1 },
{ name: "world", id: 2 },
{ name: "stack", id: 3 },
{ name: "other", id: 4 },
{ name: "name", id: 5 }
];
const myMapper = (obj) => {
return {
value: obj.id,
label: obj.name, // -------------------- (1)
name: obj.name
};
};
const allOptions = objs.map((obj) => myMapper(obj));
export default function App() {
const [options, setOptions] = useState(allOptions);
return (
<Select
options={options}
onInputChange={(val) => {
setOptions(
matchSorter(
allOptions, // ----------------> (2)
val,
{ keys: ["name", "value"]
}
));
}}
/>
);
}
I've been following a YouTube tutorial on how to add or remove input fields dynamically with React.
Most of the tutorial was easy to follow, I almost achieved what I wanted to do but I have a problem when I add an object, it duplicates itself instead of adding the different values.
Here is the code:
import React, { useState } from "react";
import { Button, Form } from "react-bootstrap";
const countries = [
{
key: "1",
name: "",
value: ""
},
{
key: "2",
name: "Afghanistan",
value: "Afghanistan"
},
{
key: "3",
name: "Albania",
value: "Albania"
},
{
key: "4",
name: "Algeria",
value: "Algeria"
},
{
key: "5",
name: "Angola",
value: "Angola"
},
{
key: "6",
name: "Argentina",
value: "Argentina"
},
]
const listanswers = [
{
key: '01',
name: '',
value: '',
},
{
key: '02',
name: 'Yes',
value: 'Yes',
},
{
key: '03',
name: 'No',
value: 'No',
},
{
key: '04',
name: 'Unsure',
value: 'Unsure',
},
];
export default function Section1_3() {
const [question1_7, setQuestion1_7] = useState("");
const [instance1_7, setInstance1_7] = useState([
{
Country: "",
Answer: "",
}
]);
// handle input change
const handleInputChange = (instance, setinstance, e, index) => {
const { name, value } = e.target;
const list = [...instance];
list[index][name] = value;
setinstance(list);
};
// handle click event of the Remove button
const handlRemoveInstance = (instance, setinstance, index) => {
const list = [...instance];
list.splice(index, 1);
setinstance(list);
};
// handle click event of the Add button
const handleAddInstance = (instance, setinstance) => {
setinstance([...instance, instance[0]]);
};
return (
<div>
<Form>
<Form.Group size="lg" controlId="formSection3">
{instance1_7.map((answer, i) => {
return (
<div key={(i + 1) * 3}>
<Form.Label>Instance variable 1</Form.Label>
{console.log(answer)}
<Form.Control
name="Country"
as="select"
onChange={e =>
handleInputChange(instance1_7, setInstance1_7, e, i)
}
>
{countries.map(country => (
<option
key={country.key}
value={country.value}
label={country.name}
/>
))}
</Form.Control>
<Form.Label>Instance variable 2</Form.Label>
<Form.Control
name="Answer"
as="select"
onChange={e =>
handleInputChange(instance1_7, setInstance1_7, e, i)
}
>
{listanswers.map(answer => (
<>
<option
key={answer.key}
value={answer.value}
label={answer.name}
/>
</>
))}
</Form.Control>
{instance1_7.length !== 1 && (
<Button
variant="danger"
onClick={() =>
handlRemoveInstance(instance1_7, setInstance1_7, i)
}
>
Remove
</Button>
)}
{instance1_7.length - 1 === i && (
<Button
variant="success"
onClick={() =>
handleAddInstance(instance1_7, setInstance1_7, i)
}
>
Add
</Button>
)}
</div>
);
})}
</Form.Group>
</Form>
<div style={{ marginTop: 20 }}>{JSON.stringify(instance1_7)}</div>
</div>
);
}
I don't know how to explain it properly, so I created a StackBlitz here : https://stackblitz.com/edit/react-dm6jwd?file=src%2FApp.js
Also if you have any suggestion on how to implement easily with a third party package that could be nice.
Thanks!
Edit:
Found solution, I added the array of the object instead of putting the object directly in the HandleAddInput function
// handle click event of the Add button
const handleAddInstance = (instance, setinstance) => {
setinstance([...instance, {
Country: "",
Answer: "",
});
};
Update: I just realised you'd posted your solution. :o) What follows is an explanation of what the problem was.
Would you take a look at your code on line 86?
// handle click event of the Add button
const handleAddInstance = (instance, setinstance) => {
setinstance([...instance, instance[0]]);
};
You'll see that there is a copying action on instance[0]. In javascript, when you pass an object as a copy, the object is passed as a reference. Once you change the values on the newly added object, it will update values on other references too.
If you intend to copy you will need to create a clone.
There are multiple ways of doing this. For instance, you could use the JSON API to remove referencing:
JSON.parse(JSON.stringify(instance[0]))
This is slower than:
Object.assign({}, instance[0])
Benchmark: https://www.measurethat.net/Benchmarks/Show/2471/2/objectassign-vs-json-stringparse#latest_results_block
A closer look at Object.assign(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Or, maybe you could require a function provided by lodash called cloneDeep and import in the following way:
import cloneDeep from 'lodash/cloneDeep';
And then:
setinstance([...instance, cloneDeep(instance[0]]));
In closing; I would suggest using the native assign method Object.assign({}, instance[0]).
// handle click event of the Add button
const handleAddInstance = (instance, setinstance) => {
setinstance([...instance, Object.assign({}, instance[0])]);
};
Best of luck :o)
I am new to React. I'm using react-select and I've used the following code. The dropdown is displayed but I'm unable to see names and unable to view after selecting.
<Select
variant="outlined"
margin="normal"
fullWidth
value={this.state.selected}
options={RewardAutomationsList}
name="selected"
onChange={this.handleChange}
placeholder='None'
>
{RewardAutomationsList.map((option) => (
<option key={option.id} value ={option.name} label={option.name}>
{option.name}
</option>
))}
</Select>
handleChange = event => {
this.setState({
selected: event.name
});
};
The RewardAutomationsList looks like this:
RewardAutomationsList:
0:{name: "TEST 1 (INR 100)", id: "123"}
1:{name: "test 2 (INR 250)", id: "456"}
Can someone help with this?
same npm package use like this block code.
import React, { Component } from 'react'
import Select from 'react-select'
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
]
const MyComponent = () => (
<Select options={options} />
)
react-select accepts an array of objects having label and value keys. Your option objects in RewardAutomationsList have id and name keys, so it can't be displayed. You need to change them.
Also, when you subscribe to change events with react-select's onChange prop, the callback function you provide receives the selectedOption, not the event.
The following should work:
const RewardAutomationsList = [
{ label: "TEST 1 (INR 100)", value: "123" },
{ label: "test 2 (INR 250)", value: "456" },
];
class App extends React.Component {
state = {
selected: null,
}
handleChange = (selectedOption) => {
this.setState({
selected: selectedOption,
});
};
render() {
return (
<React.Fragment>
<Select
fullWidth
margin="normal"
name="selected"
onChange={this.handleChange}
options={RewardAutomationsList}
placeholder="None"
value={this.state.selected}
variant="outlined"
/>
{/* It's not necessary and it's only here to show the current state */}
<pre>{JSON.stringify(this.state, null, 2)}</pre>
</React.Fragment>
);
}
}
I'm new to react and trying to learn on my own. I started using react-select to create a dropdown on a form and now I'm trying to pass the value of the option selected. My state looks like this.
this.state = {
part_id: "",
failure: ""
};
Then in my render
const {
part_id,
failure
} = this.state;
My form looks has 2 fields
<FormGroup>
<Label for="failure">Failure</Label>
<Input
type="text"
name="failure"
placeholder="Failure"
value={failure}
onChange={this.changeHandler}
required
/>
</FormGroup>
<FormGroup>
<Label for="part_id">Part</Label>
<Select
name="part_id"
value={part_id}
onChange={this.changeHandler}
options={option}
/>
</FormGroup>
the changeHandler looks like this
changeHandler = e => {
this.setState({ [e.target.name]: e.target.value });
};
The change handler works fine for the input but the Select throws error saying cannot read property name. I went through the API docs and came up with something like this for the Select onChange
onChange={part_id => this.setState({ part_id })}
which sets the part_id as a label, value pair. Is there a way to get just the value? and also how would I implement the same with multiselect?
The return of react-select onChange event and the value props both have the type as below
event / value:
null | {value: string, label: string} | Array<{value: string, label: string}>
So what the error means is that you can't find an attribute of null (not selected), or any attributes naming as name (you need value or label)
For multiple selections, it returns the sub-list of options.
You can find the related info in their document
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
Update
For your situation (single selection)
option having type as above
const option = [
{value: '1', label: 'name1'},
{value: '2', label: 'name2'}
]
state save selected value as id
changeHandler = e => {
this.setState({ part_id: e ? e.value : '' });
};
pick selected option item via saved id
<Select
name="part_id"
value={option.find(item => item.value === part_id)}
onChange={this.changeHandler}
options={option}
/>
For multiple selections
state save as id array
changeHandler = e => {
this.setState({ part_id: e ? e.map(x => x.value) : [] });
};
pick via filter
<Select
isMulti // Add this props with value true
name="part_id"
value={option.filter(item => part_id.includes(item.value))}
onChange={this.changeHandler}
options={option}
/>
onChange function is a bit different in react-select
It passes array of selected values, you may get first one like
onChange={([selected]) => {
// React Select return object instead of value for selection
// return { value: selected };
setValue(selected)
}}
I have tried the above solutions but some of these solutions does update the state but it doesn't gets rendered on the Select value instantly.
Herewith a demo example:
this.state = {
part_id: null,
};
handleUpdate = (part_id) => {
this.setState({ part_id: part_id.value }, () =>
console.log(`Option selected:`, this.state.part_id)
);
};
const priceOptions = [
{ value: '999', label: 'Item One' },
{ value: '32.5', label: 'Item Two' },
{ value: '478', label: 'Item Three' }
]
<Select
onChange={this.handleUpdate}
value={priceOptions.find(item => item.value === part_id)}
options={priceOptions}
placeholder={<div>Select option</div>}
/>