I am trying to create function that let user can select multiple options in react-select.
Here is my following code for onChange function:
const [searchUsers, setSearchUsers] = useState(false);
const [searchTeams, setSearchTeams] = useState(false);
const [searchOrganizations, setSearchOrganizations] = useState(false);
const find = (value) => {
value.forEach((option) => {
if (option.value === "Users" && value.length === 1) {
setSearchUsers(true);
setSearchTeams(false);
setSearchOrganizations(false);
} else if (option.value === "Teams" && value.length === 1) {
setSearchUsers(false);
setSearchTeams(true);
setSearchOrganizations(false);
} else if (option.value === "Organizations" && value.length === 1) {
setSearchUsers(false);
setSearchTeams(false);
setSearchOrganizations(true);
} else {
setSearchUsers(false);
setSearchTeams(false);
setSearchOrganizations(false);
}
});
};
const findList = [
{ value: "Users", label: "Users" },
{ value: "Teams", label: "Teams" },
{ value: "Organizations", label: "Organizations" },
];
const searchData = [];
findList.forEach((item) =>
searchData.push({ label: item.label, value: item.value })
);
and here is my Select tag:
<Select
className="search-select"
styles={customStyles}
instanceId="long-value-select"
closeMenuOnSelect={false}
components={animatedComponents}
defaultValue="Select"
isMulti
onChange={find}
options={searchData}
theme={(theme) => ({
...theme,
colors: {
...theme.colors,
neutral50: "#fff",
},
})}
/>
My onChange function is only getting one option value at a time right now. How can I make it take multiple option value?
If you want to update state from more than one selected option, you must rewrite you're find function:
const find = (value) => {
setSearchUsers(value.some((element) => element.value === "Users"));
setSearchTeams(value.some((element) => element.value === "Teams"));
setSearchOrganizations(value.some((element) => element.value === "Organizations")
);
};
FYI - Array.prototype.some()
The some() method tests whether at least one element in the array
passes the test implemented by the provided function
Related
So basically I am making a shopping cart and I want to add a functionality if an item is already in the cart then increase it's quantity by 1. If you add same item and they have different sizes then show them separetely. I managed to deal with increasing the quantity in my reducer's logic but when I add another block condition it doesn't work. Here is the code:
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
bagData: [],
};
export const bagDataSlice = createSlice({
name: "bagDataProducts",
initialState,
reducers: {
sendDataToCardComponent: (state, action) => {
let { id, size } = action.payload;
const findItemById = state.bagData.find(
(item) => item.id === id && item.size === size
);
if (findItemById) {
state.bagData.filter((item, i) => (state.bagData[i].quantity += 1));
} else {
state.bagData.push({ ...action.payload, quantity: 1 });
}
},
increaseItemQuantity: (state, { payload }) => {
state.bagData.filter((item, i) =>
item.id === payload ? (state.bagData[i].quantity += 1) : item
);
},
decreaseItemQuantity: (state, { payload }) => {
state.bagData.filter((item, i) =>
item.id === payload && item.quantity > 1
? (state.bagData[i].quantity -= 1)
: item
);
},
removeItem: (state, { payload }) => {
state.bagData = state.bagData.filter((item) => item.id !== payload);
},
},
});
when I add the condition of
const findItemById = state.bagData.find(
(item) => item.id === id && item.size === size
);
it only increments the item without checking it's size, only checks it's id even though there are 2 conditions for that function. Could you please explain that to me?
state.bagData.filter((item, i) => (state.bagData[i].quantity += 1));
For your first case, this is updating every item's quantity if you found a matching item by id and size. Since you've already found the item and stored it in findItemById, you should be able to use the following.
Caveat, Immer only supports mutating array elements by index so use findIndex() instead of find().
const itemIndex = state.bagData.findIndex(
(item) => item.id === id && item.size === size
);
if (itemIndex !== -1) {
state.bagData[itemIndex].quantity++;
} else {
state.bagData.push({ ...action.payload, quantity: 1 });
}
Here's a quick demo showing that this works
const initialState = {
bagData: [{
id: 1,
quantity: 1,
size: "S"
}]
};
const sendDataToCardComponent = (action) =>
immer.produce(initialState, (state) => {
let { id, size } = action.payload;
const itemIndex = state.bagData.findIndex(
(item) => item.id === id && item.size === size
);
if (itemIndex !== -1) {
state.bagData[itemIndex].quantity++;
} else {
state.bagData.push({ ...action.payload, quantity: 1 });
}
});
console.log(
"increment existing",
sendDataToCardComponent({ payload: { id: 1, size: "S" } })
);
console.log(
"add new",
sendDataToCardComponent({ payload: { id: 1, size: "M" } })
);
.as-console-wrapper { max-height: 100% !important; }
<script src="https://cdn.jsdelivr.net/npm/immer"></script>
As mentioned in the comments, you're misusing Array.prototype.filter() which should only be used to return a new array with items filtered in or out based on a predicate. Your code can be cleaned up somewhat
increaseItemQuantity: (state, { payload }) => {
const found = state.bagData.findIndex(({ id }) => id === payload);
if (found !== -1) {
state.bagData[found].quantity++;
}
},
decreaseItemQuantity: (state, { payload }) => {
const found = state.bagData.findIndex(({ id }) => id === payload);
if (found !== -1) {
state.bagData[found].quantity--;
}
},
Your last reducer is using filter() correctly but Immer also supports splice()
removeItem: (state, { payload }) => {
const found = state.bagData.findIndex(({ id }) => id === payload);
if (found !== -1) {
state.bagData.splice(found, 1);
}
},
I am trying to create a searchable dropdown that allows users to select only 2 genres which are then added to genreName. I currently have it working but the values will not set or all the values set ?
I have it working with another set of code but I couldn't add the search feature for the material ui select.
Anyone have any idea how to fix this ?
const genres = [
{ value: 'acoustic', label: 'acoustic' },
{ value: 'afrobeat', label: 'afrobeat' },
{ value: 'alt-rock', label: 'alt-rock' },
{ value: 'alternative', label: 'alternative' },
{ value: 'brazil', label: 'brazil' },
{ value: 'breakbeat', label: 'breakbeat' },
]
const AddGenre = ({ }) => {
const [ariaFocusMessage, setAriaFocusMessage] = useState('');
const [isMenuOpen, setIsMenuOpen] = useState(false);
const onFocus = ({ focused, isDisabled }) => {
const msg = `You are currently focused on option ${focused.label}${isDisabled ? ', disabled' : ''
}`;
setAriaFocusMessage(msg);
return msg;
};
const onMenuOpen = () => setIsMenuOpen(true);
const onMenuClose = () => setIsMenuOpen(false);
//trying to set the values here
const [genreName, setGenreName] = useState([]);
const handleInputChange = (value, e) => {
if (e.action === 'input-change') {
setGenreName(value);
console.log(genreName)
}
}
return (
<div className="bodyComp">
<form>
<label style={style.label} id="aria-label" htmlFor="aria-example-input">
Select a Genre
</label>
<Select
isMulti
aria-labelledby="aria-label"
ariaLiveMessages={{
onFocus,
}}
onInputChange={handleInputChange}
options={genres}
//if I set the value as genres all values are set ?
value={genres}
inputId="aria-example-input"
name="aria-live-color"
onMenuOpen={onMenuOpen}
onMenuClose={onMenuClose}
/>
</form>
</div>
)
};
export default AddGenre
In the below render method, I am performing search filter & dropdown filter on the cards. Searching & dropdwon filtering are working fine, but the problem is, 1st time when my component gets render, I do not get any Card sen on the screen. I see the card on the screen only when I enter some value in my dropdown .Can anyone help me with me whats wrong in my code, and how to show Cards first and then perform filtering & searching there..
Also If I remove .filter((data) => data === this.state.filter) , I'll be able to render the data as component gets render, but then I wont be allow to perform dropdwon filter. I think this filter is causing the issue, but not sure how to fix it, as well as to perform search & dropdown filtering
searchSpace = (event) => {
this.setState({
search: event.target.value,
});
};
sortOptions = [
{value: "ALL", label: "All"},
{ value: "Iphone", label: "Iphone" },
{ value: "Samsung", label: "Samsung" },
];
getSelectedItem = (items, selectedValue) => {
return items.find((item) => item.value === selectedValue);
};
dropdown = (event) => {
console.log("filter", event.selectedItem.value);
this.setState({
filter: event.selectedItem.value,
});
};
render() {
const { details} = this.state;
const items = this.state.details
.filter((data) => {
if (this.state.search == null) return data;
else if (data.includes(this.state.search)) {
return data;
}
})
const data = items
.filter((data) => data === this.state.filter)
.map((data) =>
<>
<ShareCard></ShareCard>
</>
);
return (
<Search placeHolderText="Search" onChange={(e) => this.searchSpace(e)}/>
<Dropdown id="filtering items={this.sortOptions}
onChange={(e) => this.dropdown(e)}
selectedItem={this.getSelectedItem(
this.sortOptions,
this.state.filter
)}
/>
<div>{data}</div>
)
Since you leave the this.state.filter initially undefined thats why at the first render you don't see any results.
you should either add a default filter to the initial state:
state: {
filter: "<an initial value based on your data>"
}
or apply the filter when it is set :
const data = items
.filter((data) => this.state.filter && (data === this.state.filter) ? true: false)
const items = this.state.details
.filter((data) => {
if (this.state.search == null || this.state.search == "" || this.state.search == undefined) return data;
else if (data.includes(this.state.search)) {
return data;
}
})
I am trying to watch a field which have watch:true field.In other words I want add useEffect dynamically.I have one json (which is coming from server).I want to watch field value (which have property watch:true).using it's value I want to update other field value .
here is my code
https://codesandbox.io/s/crimson-violet-uxbzd
see this object have watch: true, so I need to watch or check if it value is changed or not
{
label: "First",
name: "first",
type: "select",
remove: true,
watch: true,
options: ["", "one", "two"]
},
if it's value is changed then call this function
const getSecondDropdownValue = function(value) {
return new Promise((resolve, reject) => {
if (value === "one") {
setTimeout(function() {
resolve(["three", "four"]);
}, 1000);
}
if (value === "two") {
setTimeout(function() {
resolve(["five", "six"]);
}, 1000);
}
});
};
any update?.
Check if the item has watch property, if it does pass getSecondDropdownValue to onChange event of the select option. something like
<select onChange={hasWatch ? getSecondDropdownValue : () => {}}>...</select>
Create a component that will render select options.
// options = array of list options
// onChange = onchange event handler
// name = name of the select
const Option = ({ options, onChange, name }) =>
(options.length && (
<select onChange={(e) => onChange(e.target.value, name)}>
{Array.isArray(options) &&
options.map((option, index) => (
<option value={option} key={option + index}>{option}</option>
))}
</select>
)) ||
false;
Add useState for storing the data from the api.
// initial data from the api
const [data, updateData] = useState(apiData);
// update select options and the list
const updateSelectData = (list, updated) => {
const index = list.findIndex(item => item.name === updated.name);
return [
...list.slice(0, index),
Object.assign({}, list[index], updated),
...list.slice(index + 1)
];
};
getSecondDropdownValue function
const getSecondDropdownValue = function(value, name) {
const updated = data.find(
item => item.dependentField && item.dependentField[0] === name
);
// return new Promise((resolve, reject) => {
if (value === "one") {
// setTimeout(function() {
// resolve(["three", "four"]);
// }, 1000);
updated.options = ["three", "four"];
}
if (value === "two") {
// setTimeout(function() {
// resolve(["five", "six"]);
// }, 1000);
updated.options = ["five", "six"];
}
// });
updateData(updateSelectData(data, updated));
};
Example
// Get a hook function
const {useState} = React;
const apiData = [
{
label: "First",
name: "first",
type: "select",
watch: true,
options: ["", "one", "two"]
},
{
label: "Second",
name: "second",
options: [],
dependentField: ["first"],
type: "select"
}
];
// option component
const Option = ({ options, onChange, name }) =>
(options.length && (
<select onChange={(e) => onChange(e.target.value, name)}>
{Array.isArray(options) &&
options.map((option, index) => (
<option value={option} key={option + index}>{option}</option>
))}
</select>
)) ||
false;
function App() {
const [data, updateData] = useState(apiData);
const updateSelectData = (list, updated) => {
const index = list.findIndex(item => item.name === updated.name);
return [
...list.slice(0, index),
Object.assign({}, list[index], updated),
...list.slice(index + 1)
];
};
const getSecondDropdownValue = function(value, name) {
const updated = data.find(
item => item.dependentField && item.dependentField[0] === name
);
// return new Promise((resolve, reject) => {
if (value === "one") {
// setTimeout(function() {
// resolve(["three", "four"]);
// }, 1000);
updated.options = ["three", "four"];
}
if (value === "two") {
// setTimeout(function() {
// resolve(["five", "six"]);
// }, 1000);
updated.options = ["five", "six"];
}
// });
updateData(updateSelectData(data, updated));
};
return (
<div className="App">
{data.map((options, index) => (
<Option
name={options.name}
key={index}
onChange={options.watch ? getSecondDropdownValue : () => {}}
options={options.options}
/>
))}
</div>
);
}
// Render it
ReactDOM.render(
<App />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I am using react-select for autocomplete and option related field. In single selection the below code works for only sending the value as string but in multiple it does not work though it works if i want the value as object.
Here is the code
const normalizedValue = (input, options, valueType, isMulti) => {
if (valueType === "string" && !isMulti) {
options.find(option => option.value === input.value);
}
if (valueType === "string" && isMulti) {
console.log("input value", input);
options.filter(option => option.value === input.value);
}
if (valueType === "object" && !isMulti) {
options.find(option => option === input.value);
}
if (valueType === "object" && isMulti) {
options.filter(option => option === input.value);
}
};
const SearchableTextField = ({
children,
input,
options,
isMulti,
valueType,
...props
}) => {
return (
<Select
{...props}
clearable={props.clearable}
searchable={props.searchable}
options={options}
{...input}
value={normalizedValue(input, options, valueType, isMulti)}
onChange={option =>
valueType === "string"
? input.onChange(option.value)
: input.onChange(option)
}
onBlur={() => input.onBlur(input.value)}
isMulti={isMulti}
/>
);
};
const MyForm = reduxForm({ form: "MyForm" })(
class extends React.PureComponent {
handleSubmit(values) {
console.log("values", values);
}
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(this.handleSubmit.bind(this))}>
<Field
name="myCoolSelect"
component={SearchableTextField}
options={[
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
]}
valueType="string"
isMulti
/>
<button type="submit">SUbmit</button>
</form>
);
}
}
);
If i have the options as following
options = [
{value: 'abc', label: 'ABC'},
{value: 'abc2', label: 'ABC2'},
]
the expected output when i select suppose first option is selectedOption: "abc" in single selection which works and in multiple it should be selectedOption: ["abc", "abc2"] (this is not working)
I have a reproducible code as well in sandbox https://codesandbox.io/s/wnvpnlj245
The problem is here
onChange={option =>
valueType === "string"
? input.onChange(option.value)
: input.onChange(option)
}
You need also check here if its multi or not. Because when its multi it pass value as array.
Try like
onChange={option => {
valueType === "string"
? input.onChange(isMulti ? option.map(o => o.value) : option.value)
: input.onChange(option)
}
}