Unable to limit the selections of items to 2 in react hooks - reactjs

In my React-hooks I am unable to limit my selections of items to 2, below code is not validating at the moment. Would like to display a validation message "Maximum items has been selected". What could be the reason ?
https://codesandbox.io/s/kind-brown-n8ki4?file=/src/App.js:463-492
import React, { useRef, useEffect, useState } from "react";
import { Link, useHistory } from "react-router-dom";
import Multiselect from "multiselect-react-dropdown";
const options = [
{ key: "Apple", id: 1 },
{ key: "Bags", id: 2 },
{ key: "Coat", id: 3 },
{ key: "Drum", id: 4 }
];
const App = () => {
const maxOptions = 2;
const [selectedOption, setSelectedOption] = useState([]);
const handleTypeSelect = (e) => {
setSelectedOption(e);
};
const onNominate = () => {
alert("hello");
// ...
};
return (
<div className="App">
<div className="navbar-nav">
<div className="leftNavItem"></div>
</div>
<h1>Maximum 2 selections</h1>
<div className="nomineeSelectBox">
<Multiselect
onChange={handleTypeSelect}
options={selectedOption.length === maxOptions ? [] : options}
displayValue="key"
showCheckbox={true}
noOptionsMessage={() => {
return selectedOption.length === maxOptions
? "You have reached the max options value"
: "No options available";
}}
/>
</div>
<div className="nominateButton">
<input type="button" value="Next" onClick={onNominate} />
</div>
</div>
);
};
export default App;

You have some problems in your code to Multiselect works properly:
You need onSelect event of the multiselect-react-dropdown component not onChange.
selectedOption.length === maxOptions ? [] should change to selectedOption.length + 1 === maxOptions ? []
For showing the correct message for the no options selected, you need to change noOptionsMessage to emptyRecordMsg
So, final result should be:
<Multiselect
onSelect={handleTypeSelect}
onRemove={handleTypeRemove}
// onChange={handleTypeSelect}
options={selectedOption.length + 1 === maxOptions ? [] : options}
displayValue="key"
showCheckbox={true}
emptyRecordMsg={"You have reached the max options value"}
/>
In addition you should change the handleTypeSelect to :
const handleTypeSelect = (e) => {
const copy = [...selectedOption];
copy.push(e);
setSelectedOption(copy);
};
Also you need to handle the multiselect when a item removes with onRemove method:
const handleTypeRemove = (e) => {
const copy = [...selectedOption];
let index = copy.indexOf(e);
copy.splice(index, 1);
setSelectedOption(copy);
};

You are always setting only one option. So you have always one option selected. What you need to do is
const handleTypeSelect = (e) => {
setSelectedOption(prevArr => [...prevArr, e]); };
Do it like this so you can pull the option selected before.

Related

How to apply multiple filters in React for e-commerce app

I am creating an e-commerce app where I am trying to apply filters e.g. filter by gender and/or brands. The filters only work individually and if I try to click both, the other filter cancels.
Please help! thank you in advance.
I am creating an e-commerce app where I am trying to apply filters e.g. filter by gender and/or brands. The filters only work individually and if I try to click both, the other filter cancels.
Please help! thank you in advance.
Sorry about the duplicate of question, Stackoverflow kept stopping me from posting because I have too much codes instead of description.
import React, { useState, useEffect } from 'react';
import './Clothing.css'
import data from '../../data/data2.json';
const Clothing = () => {
const [items, setItems] = useState([]);
const [color, setColor] = useState(null);
const types = [
{ id: 11, value: 'All' },
{ id: 22, value: 'Cap' },
{ id: 33, value: 'Sweatshirt' }
]
const genders = [
{ id: 55, value: 'Men' },
{ id: 66, value: 'Women' }
]
const brands = [
{ value: 'Graver' },
{ value: 'LMC' }
]
useEffect(() => {
setItems(data);
}, [])
const handleGender = (e) => {
const filteredItems = data.filter((d) => {
return d.gender === e.target.value
})
setItems(filteredItems)
}
const handleBrand = (e) => {
const filteredItems = data.filter((d) => {
return d.brand === e.target.value
})
setItems(filteredItems)
}
return (
<div className="bodyDiv">
<div className="leftMenu">
<div className='leftList'>
<h3>Gender</h3>
{
genders.map((g) =>
<button
id={g.id}
value={g.value}
onClick={(e) => {
handleGender(e);
handleColor(e);
}}
>{g.value}</button>
)
}
</div>
<div className='leftList'>
<h3>Brand</h3>
{
brands.map((b) =>
<button
id={b.id}
value={b.value}
onClick={(e) => {
handleBrand(e);
}}
>{b.value}</button>
)
}
</div>
<div className='itemSection'>
<div className='itemCount'>
Total {items.length}
</div>
<div className="itemLists">
{items.length === 0
? "No items found"
:
items.map((item) =>
<div className="itemCard" key={item.id}>
<img src={item.img} alt='clothes' />
<div className="itemText">
<h4>{item.brand}</h4>
<h4>{item.gender}</h4>
</div>
</div>
)}
</div>
</div>
</div>
)
}
export default Clothing
I have solved this problem. Message me if you need help.

How can I display maximum item selected once i have selected 3 items from dropdown

I have multiselect dropdown items with checkboxes in my react app, what I am trying to achieve is if I checked any three items, then dropdown should display maximum items selected and if I unchecked anyone of them, it should display back the drop down box with items.
Somehow it doesn't work, could someone please advise.
CodeSanbox link
https://codesandbox.io/s/musing-sun-swvj6y?file=/src/App.js
import { useState } from "react";
import Multiselect from "multiselect-react-dropdown";
import "./styles.css";
export default function App() {
const options = [
{ key: "Apple", email: "apple#test.com", id: 1 },
{ key: "Orange", email: "oranges#test.com", id: 2 },
{ key: "Mango", email: "mango#test.com", id: 3 },
{ key: "Grapes", email: "grapes#test.com", id: 4 }
];
const [option, setOption] = useState([]);
const [selectedOption, setSelectedOption] = useState([]);
const [maxOptions, setMaxOptions] = useState(0);
const handleTypeSelect = (e, i) => {
const copy = [...selectedOption];
copy.push(e[3 - maxOptions]);
setSelectedOption(copy);
setMaxOptions((prevState) => prevState - 1);
};
const handleTypeRemove = (e) => {
const copy = [...selectedOption];
let index = copy.indexOf(e);
copy.splice(index, 1);
setSelectedOption(copy);
setMaxOptions((prevState) => prevState + 1);
};
options.forEach((option) => {
option.displayValue = option.key + "\t" + option.email;
});
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Multiselect
onSelect={(e) => handleTypeSelect(e, selectedOption.length)}
onRemove={handleTypeRemove}
options={selectedOption.length + 1 === maxOptions ? [] : options}
// options={!showOptions ? [] : option}
displayValue="displayValue"
showCheckbox={true}
emptyRecordMsg={"Maximum fruits selected !"}
/>
</div>
);
}
The library doesn't support manually selecting/deselecting the options. Reference
There is one hack that you can do. You can play around with key. Use the selectedItems as key and then it will re-mount the component whenever the selectedItems changes.
Note that this hack is not the recommended way to do "React".
You update the options based on the size of the selected options
const maxSelectableItems = 3;
const options = [
{ key: "Apple", email: "apple#test.com", id: 1 },
{ key: "Orange", email: "oranges#test.com", id: 2 },
{ key: "Mango", email: "mango#test.com", id: 3 },
{ key: "Grapes", email: "grapes#test.com", id: 4 }
];
<Multiselect
// This will re-mount your component whenever
// selected items changes
key={selectedItems}
onSelect={handleSelection}
onRemove={handleRemove}
// This will set the pre-selected values on re-mount
selectedValues={selectedItems}
options={
selectedItems.length === maxSelectableItems
? []
: options.map((o) => ({
...o,
displayValue: `${o.key}\t${o.email}`
}))
}
displayValue="displayValue"
showCheckbox
emptyRecordMsg={"Maximum fruits selected !"}
/>
And, your handleSelection and handleRemove will look like this:
const [selectedItems, setSelectedItems] = useState([]);
const handleSelection = (selectedItems) => {
setSelectedItems(selectedItems.slice(0, maxSelectableItems));
};
const handleRemove = (selectedItems) => {
setSelectedItems(selectedItems);
};
One issue with this is that since it re-mounts the entire multi-select component, when you select/remove an item, the input will lose focus. So, you will have to manually give focus to the input after selection/removal.
const focusOnInput = () => {
setTimeout(() => {
// You can use a better selector (this is just a generic input selector)
document.querySelector("input").focus();
// Adding some delay to allow the component to re-mount
}, 10);
};
And then, use this in your selection/removal handlers
const handleSelection = (selectedItems) => {
setSelectedItems(selectedItems.slice(0, maxSelectableItems));
focusOnInput()
};
const handleRemove = (selectedItems) => {
setSelectedItems(selectedItems);
focusOnInput()
};
Here is a link to a working sandbox
import { useState, useRef } from "react";
import Multiselect from "multiselect-react-dropdown";
import "./styles.css";
export default function App() {
const options = [
{ key: "Apple", email: "apple#test.com", id: 1 },
{ key: "Orange", email: "oranges#test.com", id: 2 },
{ key: "Mango", email: "mango#test.com", id: 3 },
{ key: "Grapes", email: "grapes#test.com", id: 4 }
];
const [option, setOption] = useState([]);
const [selectedOption, setSelectedOption] = useState([]);
const fruitmultiselect = useRef(null);
options.forEach((option) => {
option.displayValue = option.key + "\t" + option.email;
});
return (
<div className="App">
<h1>Hello CodeSandbox3</h1>
<Multiselect
ref={fruitmultiselect}
onSelect={(selectedList, selectedItem) => {
if(selectedList.length === 3){
fruitmultiselect.current.toggelOptionList()
console.log("onSelect length: "+selectedList.length);
}
}}
options={options}
displayValue="displayValue"
showCheckbox={true}
selectionLimit={3}
closeOnSelect={true}
emptyRecordMsg={"Maximum fruits selected !"}
/>
</div>
);
}

Betton are not disappear when removing atomFamily recoil.js

Why when user click "remove button" in ListItem component, the item text are disappear but the button itself are still there, if possible how to get rid of that "remove button" too?
P.S The atom family item are got removed but the ui are not get updated ("remove button" are still there), is that a normal things?
import React, {useState} from "react";
import { atom, useRecoilState, useResetRecoilState, useRecoilCallback, atomFamily, useRecoilValue, useSetRecoilState } from "recoil";
import "./styles.css";
const idsState = atom({
key: "circleColor",
default: [],
});
const noteState = atomFamily({
key: "noteState",
default: []
})
const ListItem = ({ id }) => {
const [note, setNote] = useRecoilState(noteState(id));
const handleRemoveNote = useResetRecoilState(noteState(id));
return (
<div key={note.id} className="list-item">
<p>{note.text}</p>
<button onClick={handleRemoveNote}>Remove</button>
</div>
)
}
const App = () => {
const ids = useRecoilValue(idsState);
const nextId = ids.length;
const addNote = useRecoilCallback(({set}) => (newNote) => {
set(idsState, [...ids, nextId])
set(noteState(nextId), newNote);
})
const [text, setText] = useState("");
const handleAddNote = (e) => {
e.preventDefault();
const id = Math.round(Math.random() * 1000);
const newNote = {
text,
id,
subNote: [
{
label: "zero",
value: "0"
},
{
label: "one",
value: "1"
},
{
label: "two",
value: "two"
}
]
};
addNote(newNote);
}
return (
<div>
<form className="form-container" onSubmit={handleAddNote}>
<input onChange={e => setText(e.target.value)} />
<button>Add</button>
</form>
<div>
{ids.map(id => (
<ListItem id={id} />
))}
</div>
</div>
);
};
export default App;

Radio buttons not toggling, checked or highlighted

import React, { useState, useEffect, Fragment } from "react";
import Button from "../../../Resources/Forms/Button";
import Switch from "../../../Resources/Forms/Switch";
import { POST } from "../../../Utils/api";
import Radio from "../../../Resources/Forms/Radio";
import withAppData from "../../../HOC/withAppData";
const InventorySettings = (props) => {
const [state, setState] = useState({});
const [isSaving, setIsSaving] = useState();
const [isStockRequestChecked, setIsStockRequestChecked] = useState(false);
const getStatus = id => {
return props.context.isSettingsActivated(id) ? 1 : 0;
};
const setBusinessSettings = async () => {
const defaultSettings = [
{ state: "enableStockRequest", id: 53 },
{ state: "connectWarehousePickStock", id: 52 },
{ state: "approveRequestOtp", id: 51 },
{ state: "approveRequestManually", id: 50 }
];
for (const setting of defaultSettings) {
await setState({ [setting.state]: getStatus(setting.id) });
}
};
function chooseApprovalMethod(methodType) {
const currentValue = state[methodType];
setState({[methodType]
: currentValue === 1 ? 0: 1})
}
async function saveApprovalMethod() {
setIsSaving(true)
const approvalSettings = [{text:"approvalRequestManually", id: 51}, {text:"approveRequestOtp", id: 50}]
for(const el of approvalSettings) {
const currentValue = state[el.text];
const data = {
settingId: el.id,
status: currentValue
}
await POST(`Common/AddBusinessSetting`, data);
}
setIsSaving(false);
props.context.getBusinessSettings();
}
const updateBasicSettings = async (id, key) => {
setState({ [key]: !state[key] ? 1 : 0 });
const data = {
SettingId: id,
Status: state[key],
};
await POST(`Common/AddBusinessSetting`, data);
props.context.getBusinessSettings();
};
useEffect(() => {
setBusinessSettings();
}, []);
return (
<Fragment>
<div className="basic-settings-section">
<Switch
label={"Connect Warehouse Stock to pick stock"}
light={true}
checked={state && state.connectWarehousePickStock === 1}
onChange={() => updateBasicSettings(52, "connectWarehousePickStock")}
></Switch>
</div>
<div className="basic-settings-section">
<Switch
label={"Stock Request"}
light={true}
checked={isStockRequestChecked}
onChange={() => setIsStockRequestChecked(!isStockRequestChecked)}
></Switch>
{isStockRequestChecked && (
<div className="basic-settings-plan-generate">
<div
className="form__label"
style={{ padding: "2px", marginBottom: "20px" }}
>
<p>Please choose an approval method</p>
</div>
<Radio
label={"Manual Approval"}
name="approval"
value="50"
id="50"
checked={state && state.approveRequestManually === 1}
// onChange={() => (chooseApprovalMethod)}
/>
<Radio
label={"OTP Approval"}
name="approval"
value="51"
id="51"
checked={state && state.approveRequestOtp === 1}
// onChange={() => (chooseApprovalMethod)}
/>
<div className="password-settings-btn"
// onClick={props.context.showToast}
>
<Button
type={"outline"}
size={"medium"}
text={"Save"}
disabled={!state.approveRequestOtp && !state.approveRequestManually}
withMargin={false}
loading={isSaving}
onClick={saveApprovalMethod}
></Button>
</div>
</div>
)}
</div>
</Fragment>
);
}
export default withAppData(InventorySettings);
I added the chooseApprovalMethod function to the radio buttons but still I wasn't getting it well. So I had to call there state using state.text is equal to 1. Please help me out I don't think I know what I'm doing anymore.
Please above are my code, the radio buttons aren't checking or highlighting, so I want them to be checked when clicked on, and I want there ids to be saved when clicking on the save button.
So please guys help me out, as I don't understand it anymore.

ReactJS: Manage multiple checkbox inputs with useState

I have the following example component that uses multiple checkboxes for choosing what items to remove from a list of objects:
import React, { useState } from "react";
import "./styles.css";
const data = [
{
name: "test1",
result: "pass"
},
{
name: "test2",
result: "pass"
},
{
name: "test3",
result: "pass"
},
{
name: "test4",
result: "pass"
},
{
name: "test5",
result: "pass"
}
];
export default function App() {
const [allChecked, setAllChecked] = useState(false);
const [isChecked, setIsChecked] = useState({});
const [formData, setFormData] = useState(data);
const handleAllCheck = e => {
setAllChecked(e.target.checked);
};
const handleSingleCheck = e => {
setIsChecked({ ...isChecked, [e.target.name]: e.target.checked });
};
const onDelete = () => {
console.log(isChecked);
const newData = data.filter(
item => !Object.keys(isChecked).includes(item.name)
);
console.log(newData);
setFormData(newData);
};
return (
<div className="App">
<div>
<label>All</label>
<input
name="checkall"
type="checkbox"
checked={allChecked}
onChange={handleAllCheck}
/>
<label />
</div>
{formData.map((test, index) => (
<div key={index}>
<label>{test.name}</label>
<input
type="checkbox"
name={test.name}
checked={allChecked ? true : isChecked[test.name]}
onChange={handleSingleCheck}
/>
</div>
))}
<button onClick={() => onDelete()}>DELETE</button>
</div>
);
}
This is mostly working, except for check all. It seems onChange will not update while using useState. I need to be able to select all the objects or uncheck some to mark for deletion.
Any help is greatly appreciated.
CodeSandbox Example: https://codesandbox.io/s/modest-hodgkin-kryco
UPDATE:
Okay, after some help from Richard Matsen,
Here is a new solution without direct DOM manipulation:
import React, { useState, useEffect } from "react";
import "./styles.css";
const data = [
{
name: "test1",
result: "pass"
},
{
name: "test2",
result: "pass"
},
{
name: "test3",
result: "pass"
},
{
name: "test4",
result: "pass"
},
{
name: "test5",
result: "pass"
}
];
export default function App() {
const [allChecked, setAllChecked] = useState(false);
const [isChecked, setIsChecked] = useState();
const [loading, setLoading] = useState(true);
const [formData, setFormData] = useState(data);
const handleAllCheck = e => {
setAllChecked(e.target.checked);
};
const handleSingleCheck = e => {
setIsChecked({ ...isChecked, [e.target.name]: e.target.checked });
};
const onDelete = () => {
const itemList = Object.keys(isChecked).map((key:any) => {
if (isChecked[key] === true) {
return key
}
})
const result = formData.filter((item:any) => !itemList.includes(item.name))
console.log(result)
setFormData(result)
}
useEffect(() => {
if (!loading) {
setIsChecked(current => {
const nextIsChecked = {}
Object.keys(current).forEach(key => {
nextIsChecked[key] = allChecked;
})
return nextIsChecked;
});
}
}, [allChecked, loading]);
useEffect(() => {
const initialIsChecked = data.reduce((acc,d) => {
acc[d.name] = false;
return acc;
}, {})
setIsChecked(initialIsChecked)
setLoading(false)
}, [loading])
return (
<div className="App">
<div>
<label>All</label>
<input
name="checkall"
type="checkbox"
checked={allChecked}
onChange={handleAllCheck}
/>
<label />
</div>
{!loading ? formData.map((test, index) => (
<div key={index}>
<label>{test.name}</label>
<input
type="checkbox"
name={test.name}
checked={isChecked[test.name]}
onChange={handleSingleCheck}
/>
</div>
)): null}
<button onClick={() => onDelete()}>DELETE</button>
</div>
);
}
codesandbox of working solution:
https://codesandbox.io/s/happy-rubin-5zfv3
The basic problem is checked={allChecked ? true : isChecked[test.name]} stops the unchecking action from happening - once allChecked is true it does not matter what value isChecked[test.name] has, the expression is always going to be true.
You should rely only on isChecked for the value, and treat changing allChecked as a side-effect.
useEffect(() => {
setIsChecked(current => {
const nextIsChecked = {}
Object.keys(current).forEach(key => {
nextIsChecked[key] = allChecked;
})
return nextIsChecked;
});
}, [allChecked]);
...
{formData.map((test, index) => (
<div key={index}>
<label>{test.name}</label>
<input
type="checkbox"
name={test.name}
checked={isChecked[test.name]}
onChange={handleSingleCheck}
/>
</div>
))}
There's also this warning cropping up
Warning: A component is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
So that's basically saying don't initialize isChecked to {}, because the input's checked property is initially undefined. Use this instead,
{
test1: false,
test2: false,
test3: false,
test4: false,
test5: false,
}
or this way
const data = { ... }
const initialIsChecked = data.reduce((acc,d) => {
acc[d.name] = false;
return acc;
}, {})
export default function App() {
const [allChecked, setAllChecked] = useState(false);
const [isChecked, setIsChecked] = useState(initialIsChecked);
...
The problem with your code was how you were handling allChecked. I have made some changes to your code and it works now.
const data = [
{
name: "test1",
result: "pass"
},
{
name: "test2",
result: "pass"
},
{
name: "test3",
result: "pass"
},
{
name: "test4",
result: "pass"
},
{
name: "test5",
result: "pass"
}
];
function App() {
const [allChecked, setAllChecked] = useState(false);
// using an array to store the checked items
const [isChecked, setIsChecked] = useState([]);
const [formData, setFormData] = useState(data);
const handleAllCheck = e => {
if (allChecked) {
setAllChecked(false);
return setIsChecked([]);
}
setAllChecked(true);
return setIsChecked(formData.map(data => data.name));
};
const handleSingleCheck = e => {
const {name} = e.target;
if (isChecked.includes(name)) {
setIsChecked(isChecked.filter(checked_name => checked_name !== name));
return setAllChecked(false);
}
isChecked.push(name);
setIsChecked([...isChecked]);
setAllChecked(isChecked.length === formData.length)
};
const onDelete = () => {
const data_copy = [...formData];
isChecked.forEach( (checkedItem) => {
let index = formData.findIndex(d => d.name === checkedItem)
delete data_copy[index]
}
)
setIsChecked([])
// filtering out the empty elements from the array
setFormData(data_copy.filter(item => item));
setAllChecked(isChecked.length && isChecked.length === data.length);
};
return (
<div className="App">
<form>
<label>All</label>
<input
name="checkall"
type="checkbox"
checked={allChecked}
onChange={handleAllCheck}
/>
{ formData.map((test, index) => (
<div
key={index}
>
<label>{test.name}</label>
<input
type="checkbox"
name={test.name}
checked={isChecked.includes(test.name)}
onChange={handleSingleCheck}
/>
</div>
))
}
<label />
</form>
<button onClick={onDelete}>DELETE</button>
</div>
);
}
I think you should merge allChecked and isChecked state vars, because they represent the same thing, but your denormalizing it by creating two different vars! I suggest to keep isChecked, and modify all its entries when you press the allChecked input. Then, you can use a derived var allChecked (defined in your component or by using useMemo hook) to know if all your checks are checked or not.
Well, after some time working I came up with:
import React, { useState } from "react";
import "./styles.css";
import { useFormInputs } from "./checkHooks";
const data = [
{
name: "test1",
result: "pass"
},
{
name: "test2",
result: "pass"
},
{
name: "test3",
result: "pass"
},
{
name: "test4",
result: "pass"
},
{
name: "test5",
result: "pass"
}
];
export default function App() {
const [fields, handleFieldChange] = useFormInputs({
checkedAll: false
});
const allcheck = () => {
const checkdata = document.querySelectorAll(".checkers").length;
const numChecks = Array.from(new Array(checkdata), (x, i) => i);
numChecks.map(item => {
console.log(item);
async function checkThem() {
let element = await document.getElementsByClassName("checkers")[item];
element.click();
}
return checkThem();
});
};
return (
<div className="App">
<div>
<label>All</label>
<input name="checkall" type="checkbox" onChange={allcheck} />
<label />
</div>
{data.map((test, index) => (
<div key={index}>
<label>{test.name}</label>
<input
className="checkers"
type="checkbox"
name={test.name}
onChange={handleFieldChange}
/>
</div>
))}
</div>
);
}
Relevent codesandbox: https://codesandbox.io/s/admiring-waterfall-0vupo
Any suggestions welcomed. Also, thanks for the help guys!

Resources