I want to select a group based checkbox. The problem is that when I click on the group, the entire checkbox is selected. I don't want to select the entire checkbox. this is my Initial State.
const plainOptions = ["can_view", "can_create", "can_update"];
state = {
checkedList: [],
indeterminate: true,
checkAll: false
};
Method: onchange method basically works each individual checkbox.
onChange = checkedList => {
console.log(checkedList);
this.setState({
checkedList,
indeterminate:
!!checkedList.length && checkedList.length < plainOptions.length,
checkAll: checkedList.length === plainOptions.length
});
};
This method works for selected all checkbox
onCheckAllChange = e => {
console.log(e.target.checked);
this.setState({
checkedList: e.target.checked ? plainOptions : [],
indeterminate: false,
checkAll: e.target.checked
});
};
{
["group", "topGroup"].map(item => (
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.state.indeterminate}
onChange={this.onCheckAllChange}
checked={this.state.checkAll}
>
{item}
</Checkbox>
<CheckboxGroup
options={plainOptions}
value={this.state.checkedList}
onChange={this.onChange}
/>
</div>
));
}
However, my accepted Data format is
{group:["can_view","can_create"],topGroup:["can_view","can_create"}
I want to get this format output when user selected on the checkbox
Here is the code sandbox : https://codesandbox.io/s/agitated-sea-1ygqu
The reason both groups change when you click something in one of them is because both groups use the same internal state.
["group", "topGroup"].map(item => (
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.state.indeterminate}
onChange={this.onCheckAllChange}
checked={this.state.checkAll}
>
{item}
</Checkbox>
<CheckboxGroup
options={plainOptions}
value={this.state.checkedList}
onChange={this.onChange}
/>
</div>
));
Both the group and topGroup use the same this.state.checkList state.
The easiest way to solve this is by extracting each group into its own component. This way they have their own state separate of each other.
You could also opt to keep one component, but you must manage multiple internal states. You could for example use state = { checkList: [[], []] } where the first sub-array is to store the group state and the second sub-array is to store the topGroup state.
If groups are dynamic you can simply map over the groups and create your states that way:
state = { checkList: groups.map(() => []) };
You would also need to manage multiple indeterminate and checkAll states. This can be avoided when you deduce those from the checkList state. For example:
isIndeterminate(index) {
const checkList = this.state.checkList[index];
return checkList.length > 0 && checkList.length < plainOptions.length;
}
This would also avoid conflicting state, since there is one source of truth.
Related
I'm working on making multiple choice component that has props such as number of options, number of Answers and callback fn in order to get data of selected options given by users. And when a button is clicked, a checkmark is shown on the button.
This component is supposed to work like this.
If the number of answers is 1, as soon as user clicks one button, all of other buttons should be disabled. But once the button that user clicked at first is clicked again, then the other buttons should not be disabled anymore.
if the number of answers is more than 1(meaning multiple), as soon as user clicks buttons as same number of the times as answers, it should work like the first case above, but the difference is when any button out of clicked ones is clicked again, the rest of buttons should not be disabled.
// Parent Component, where the multipleChoice component is called
import MultipleChoice from 'components/MultipleChoice';
const ParentComponent = () => {
const [curSelectedOptions, setCurSelectedOptions] = useState<number[]>([]);
<MultipleChoice
numOfOptions={5}
numOfAnswers={1}
onClick={(selectedOption) => {
setCurSelectedOptions((prev) =>
prev.includes(selectedOption)
? prev.filter((option) => option !== selectedOption)
: [...prev, selectedOption]
);
}}
/>
// MultipleChoice Component
import { useState } from 'react';
import styles from './MultipleChoice.module.scss';
import _ from 'lodash';
import { CheckOutlined } from '#ant-design/icons';
interface IMultipleChoice {
numOfOptions: number;
numOfAnswers: number;
onClick: (selectedOption: number) => void;
}
const MultipleChoice: React.FC<IMultipleChoice> = ({
numOfOptions,
numOfAnswers,
onClick,
}) => {
const [checkedOption, setCheckedOption] = useState<any>({});
const onHandleClick = (index: any) => {
setCheckedOption((prev: any) => ({ ...prev, [index]: !prev[index] }));
}
return (
<div className={styles.container}>
{numOfOptions && _.range(1, numOfOptions + 1).map((option, index) => {
return (
<button
key={option}
value={option}
onClick={(e) => {
onHandleClick(e, index);
onClick(e.currentTarget.value);
}}
// disabled={}
className={`${styles.choiceButton} ${styles.center}`}
>
<div
className={
`${styles.circleOfNumber} ${styles.center}`
}
>
<span className={styles.numberText}>{option}</span>
{checkedOption[index] ? ( // this condition determines whether checkmark is displayed
<div className={styles.checkMarkWrapper}>
<CheckOutlined
style={{
fontSize: 68,
color: 'red',
}}
/>
</div>
) : null}
</div>
</button>
);
})}
</div>
)
'checkedOption' state goes like this. if user clicks 1,2 buttons out of 5, then it will be {0: true, 1: true}. and if user clicks same buttons again, then it turns into {0: false, 1: false}, NOT like this -> {}. And checkMark is shown when index key of checkedOption state has 'true' value.
It's tricky to put disabling certain buttons conditionally with the way of check mark working with code 'checkedOption[index] ? ...' and 'onHandleClick' function together.
How can I make those two described above working?
Any help would be appreciated.
We do it by enforcing the condition:
Checking the current option is not one that's already selected. And...
The total number of true values in checkedOption is the same as the number of answers allowed (for "defensive coding" sake, we also check if its more than the answers allowed)
<button
key={option}
value={option}
onClick={(e) => {
onHandleClick(e, index);
onClick(e.currentTarget.value);
}}
disabled={!checkedOption[index] && Object.values(checkedOption).filter(selected => selected === true).length >= numOfAnswers}
className={`${styles.choiceButton} ${styles.center}`}
>
Given I have this code (I removed a lot of the select items so it wasn't a massive list but there would be an extra 20 or so):
import * as React from "react";
import { Form, Card, Grid } from "tabler-react";
import { Button } from "semantic-ui-react";
class Skills extends React.Component {
constructor(props) {
super(props);
this.state = {
showSaveButton: false,
showCancelButton: false,
};
}
onChange = (event) => {
this.setState(
{
showSaveButton: true,
showCancelButton: true,
});
}
cancelChanges = () => {
this.setState(
{
showSaveButton: false,
showCancelButton: false,
});
}
render() {
return (
<div className="card" name="skills">
<Card.Body>
<Card.Title>Top skills</Card.Title>
<Grid.Row>
<Grid.Col offset={1} md={10}>
<Form.Group name="softskills" label="Soft Skills">
<Form.SelectGroup canSelectMultiple pills onChange={this.onChange}>
<Form.SelectGroupItem
label="Communication"
name="communication"
value="Communication"
/>
<Form.SelectGroupItem
label="Teamwork"
name="teamwork"
value="Teamwork"
/>
</Form.SelectGroup>
</Form.Group>
</Grid.Col>
</Grid.Row>
<Button content='Cancel changes' floated='left' color='red' basic hidden={this.state.showCancelButton ? '' : 'hidden'} onClick={this.cancelChanges}/>
<Button content='Save changes' floated='right' color='green' basic hidden={this.state.showSaveButton ? '' : 'hidden'}/>
</Card.Body>
</div>
);
}
}
export default Skills;
The current functionality is that on change, 2 buttons will appear that are cancel or accept.
I need the below functionality but I can't work out how to do it unless I have like 60+ states (an initial and a working state for each option) which seems ridiculous:
The initial state is pulled from a database in a JSON array whereby everything that appears in that array should start out as selected (checked=true). For example, if the array is ["communication", "timemanagement"] I need to set the Communication and Time Management options to checked=true.
The initial state needs to be saved so that if anything changes and then the user clicks cancel, the checked boolean for each option is reset to what it was originally
If accept is clicked, the information needs to be sent to the database and so it needs to know what options have checked=true and be able to grab their names
So is there a way to do this without having a massive amount of states?
What you can do is create a mapping in state for all 60. When you get the results from the database, store them in state with fields to track checked and changed statuses:
// isDirty is a flag to say there are pending changes on the option
const options = arrayFromDatabase.map(arrayElement => ({ name: arrayElement, checked: true, isDirty: false })
then store that array in your state, e.g.,
this.setState({...this.state, options })
When a change is made, mark the specific option as dirty -> isDirty = true. If it's cancelled, flip the flag back to false -> isDirty = false.
Should look something like,
this.setState({
...state,
options: this.state.map(
option => option.name === valueToChange ? {
...option,
isDirty: true | false } :
option
)
})
Handle your check-changed in the same way.
I'm new to React and I'm trying to select an option from the list and click the button to confirm the selection. I save the selected option in the "pickedUser" object. But when I change the state of the "pickedUser", I think the render is start again and the list is like at the beginning. I just want to select the option and click on the button without restarting select list. If you can help solve the problem and point out the mistakes I need to correct in order to get better. Thanks!
//pickedUser saving selected option from list.
constructor(props){
super(props);
this.state = {
users: [],
pickedUser:{
name:"",
email:"",
uloga:""
},
isLoading: true,
errors: null
};
this.handleChange = this.handleChange.bind(this);
this.routeChange = this.routeChange.bind(this);
}
//In handleChange I'm trying to set a value I get from select to pickedUser.
async handleChange(event) {
const eName = event.target.value.split(',')[0];
const eEmail = event.target.value.split(',')[1];
const eUloga = event.target.value.split(',')[2];
await this.setState({
pickedUser:{
name : eName,
email: eEmail,
role: eUloga
}
});
}
//And this is a part of my render.
render() {
const { isLoading, users, pickedUser } = this.state;
return (
<div>
<select
id="list"
value={pickedUser}
onChange={event => this.handleChange(event)}
>
{!isLoading ? (
users.map((user, key) => (
<option key={key}>
{user.name}, {user.email}, {user.role}
</option>
))
) : (
<p>Loading...</p>
)}
</select>
<button id="b1" type="button"
value={pickedUser}
onClick={event => {
this.handleSubmit(event);
this.routeChange();
}}>
Sign in
</button>
I wanted to function normally, that when I select the option, it stays selected, but it happens that when I select it, it is refreshed again.
I just have to tell that the value is good when the option is selected but the display is not.
Technically you only have to correct this line
<select
id="list"
value={pickedUser.name + "," + pickedUser.email + "," + pickedUser.role}
onChange={event => this.handleChange(event)}
>
value should not be object (pickedUser), but it should be string.
This is working example
But I can suggest following:
Make state.users object (not array). Email should be unique, so it can be used as key. For example:
this.state = { users: { "jack#mail.com": {name: "Jack", uloga: "aaa"},
"mark#mail.com": {name: "Mark", uloga: "aaa"} } }
In this case you'll be able to extract user from users by it email.
Object also support iteration like arrays useng Object.keys(users) or Object.values(users)
Use email as key for <option>. Index as keys is not good practice in React.
Add id to each <option> to easily identify selected option in event handler
Suggested version is here
I'm using react-native-elements checkbox, i have 2 checkboxes and i want to select one of them only and i managed to do that, but I'm trying to console.log the checked box only but it's not working because they have two different states, so how can i determine which box was checked in my app using the state? here is the code:
Initial state:
state: {
single: false,
married: false
}
Checkboxes:
<CheckBox title="Single"
checked={this.state.single}
onPress={() => this.setState({ single: !this.state.single,
married: false})}/>
<CheckBox title="Married"
checked={this.state.married}
onPress={() => this.setState({ married: !this.state.married,
single: false})}/>
I have an api and i want to post data in it, it has maritalStatus attribute and i want to send either married or single as a string value based on the checked box
There are really three conditions that exist.
The user hasn’t selected a box
The user selects single
The user selects married.
If you have validation, meaning that the user must check a box then you can discount the first condition. From that it is then possible to tell, once a user has selected a box, what their choice was by knowing the state of only one of the boxes. So if you have a button that checks validation you could do something like this.
<Button
title={'check married status'}
onPress={() => {
if (!this.state.single && !this.state.married) {
alert('Please check a box)
} else {
// we only need to check one of them
let marriedStatus = this.state.married ? 'married' : 'single';
alert(`You are ${marriedStatus}`)
// then you can do what you want with the marriedStatus here
}
}}
/>
Looks like they are xor operation. You'll need to set the current state of everyone by looking the past state of the clicked button.
http://www.howtocreate.co.uk/xor.html
single:
{single: !this.state.single, married: this.state.single}
married
{single:this.state.married, married: !this.state.married}
I dont think you need to manage two states for this. A person can either married or single in this case.
So you need to do something like this
If you wanna have both the checkbox as unchecked on load then
state: {
single: void 0,
}
<CheckBox title="Single"
checked={this.state.single}
onPress={() => this.setState({ single: !this.state.single})}/>
<CheckBox title="Married"
checked={this.state.single !== undefined && !this.state.single}
onPress={() => this.setState({ married: !this.state.single})}/>
or if one checked then
state: {
single: true,
}
<CheckBox title="Single"
checked={this.state.single}
onPress={() => this.setState({ single: !this.state.single})}/>
<CheckBox title="Married"
checked={!this.state.single}
onPress={() => this.setState({ married: !this.state.single})}/>
Hope this works for you. :)
I created select option using ant design .But I need create editable cell inside the select option.
This my select option code
<Select
showSearch
style={{ width: 400 }}
placeholder="Select a Bank"
optionFilterProp="children"
onChange={this.handleChange.bind(this)}
>
<option value="1">Bank1</option>
<option value="2"> Bank2</option>
<option value="3"> Bank3</option>
</Select>
And onChange functions is
handleChange(value) {
console.log(`selected ${value}`);
this.setState({
bank:value,
});
}
Can you help me?
I suppose the question is whether or not this is an editable list.
The Select component has a mode prop that can be used to change the functionality with the following options:
'default' | 'multiple' | 'tags' | 'combobox'
Using the tags mode would allow you to add and remove items and generate a tokenized list when the form is submitted.
If you are looking at a fixed list and then wanting to create new items to add to the list:
If you want to be able to add new items to the list, this doesn't exist currently, as far as I am aware.
You may be able to refashion something from the Ant Design Pro components, or otherwise come up with a solution where:
when "create" is selected, you toggle the Select for an Input
when the input is submitted/blurred update the Options list, toggle the Select/Input once more and submit the value to the back-end.
I hope this helps.
You don't need to do that actually. All you need to do is to use component state and two simple callback functions ant design provides for select.
So let's assume you need to allow users not to also search for existing values inside a Select but if it didn't exist they can choose a new one. So here's what I'd do:
Inside render() method:
<Select
showSearch
value={this.title}
filterOption={true}
onSearch={this.handleSearch}
onFocus={this.handleFocus}
style={{ width: "100%" }}>
{this.titles.map((title) => (
<Select.Option key={title}>{title}</Select.Option>
))}
</Select>
Where this.titles = ["something", "else"].
Then Inside this.handleSearchand this.handleFocus I'd write:
protected handleSearch = (value: string) => {
this.setState({ titles: value && value !== "" ? [...this.titles, value] : fileTitles });
};
protected handleFocus = () => {
this.setState({ this.titles });
};
What we're basically doing is to populate the options we're iterating over inside the Select with this.titles in the state of the component itself (don't confuse it with Redux or MobX) when user opens the selector and once user searches for anything that would be added to options as well. With this approach you won't need an input or a switch to show/hide inputs. Hope it helps.
You could use another modal to input the additional value.
Check this : https://codesandbox.io/s/antdselectaddoption-7fov7
Code from mamsoudi throws Errors, so i took his idea and made my own component that i'm sharing with you.
import React from 'react';
import {Select} from "antd";
class FieldSelectAndCustomText extends React.Component {
constructor(props) {
super(props);
this.initialTitles = ["something", "else"];
this.state = {
titles: this.initialTitles,
currentValue: null,
};
}
handleSearch = (value) => {
const titles = this.state.titles;
for (let i = 0; i < titles.length; i++) {
const isSearchValueInState = new RegExp(value).test(titles[i]);
if (!isSearchValueInState) {
this.setState({
titles: [...this.initialTitles, value],
currentValue: value
});
break;
}
}
};
handleChange = (value) => {
this.setState(prev => ({...prev, currentValue: value}));
}
render () {
return (
<div>
<Select
showSearch
value={this.state.currentValue}
filterOption={true}
onSearch={this.handleSearch}
onChange={this.handleChange}
onFocus={this.handleFocus}
style={{ width: "100%" }}>
{this.state.titles.map((title) => (
<Select.Option value={title} key={title}>{title}</Select.Option>
))}
</Select>
</div>
);
}
}