How to handle multiple radio button groups in one component in reactjs? - javascript

I'm trying to send list of selected radio button ids from multiple radio button groups on clicking send button,
My problem:
I am getting selected radio button from backend , then I should be able to change the radio button and send back to backend. but when I try to change the radio button it is not working.
What I did not understand:
How to handle the on change function, normally on change we can change the state but to change the state on load we should grab the values radio buttons. Finally I got struck here, not understanding how to move forward.
Here is the wireframe and code snippet:
function CardsList(props) {
const cards = props.cards;
return (
<div>
{cards.map((card, idx) => (
<div>
{card.cardName}
{
card.options.map((lo,idx) => (
<li key={idx}>
<input
className="default"
type="radio"
name={card.cardName}
checked={lo.selected}
/>))
}
<div>
))}
</div>
);
}
//array of cards coming from the backend
const cards = [
{cardName:'card1',options:[{radioName:'card1-radio1',selected:'true'},
{radioName:'card1-radio2',selected:'false'}]},
{cardName:'card2',options:[{radioName:'card2-radio1',selected:'true'},
{radioName:'card2-radio2',selected:'false'}]}
];
ReactDOM.render(
<CardsList cards={cards} />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

You can use an object as a lookup table that holds the group names as keys.
On each change you will need to find the relevant group with the relevant option and set the new state accordingly.
Important! - one thing to notice here, is that i changed the type of the selected property from a String to a Boolean. this will let me handle the conditions like this:
<input checked={option.selected} />
If you can't change it to a Boolean then you will need to handle the condition like this:
<input checked={option.selected === 'true'} />
Here is a running example:
//array of cards coming from the backend
const data = [
{
cardName: 'card1', options: [{ radioName: 'card1-radio1', selected: true },
{ radioName: 'card1-radio2', selected: false }]
},
{
cardName: 'card2', options: [{ radioName: 'card2-radio1', selected: true },
{ radioName: 'card2-radio2', selected: false }]
}
];
class CardsList extends React.Component {
constructor(props) {
super(props);
this.state = {
cards: []
};
}
componentDidMount() {
setTimeout(() => {
// mimic an async server call
this.setState({ cards: data });
}, 1000);
}
onInputChange = ({ target }) => {
const { cards } = this.state;
const nexState = cards.map(card => {
if (card.cardName !== target.name) return card;
return {
...card,
options: card.options.map(opt => {
const checked = opt.radioName === target.value;
return {
...opt,
selected: checked
}
})
}
});
this.setState({ cards: nexState })
}
onSubmit = () => { console.log(this.state.cards) };
render() {
const { cards } = this.state;
return (
<div>
{
cards.length < 1 ? "Loading..." :
<div>
{cards.map((card, idx) => (
<ul>
{card.cardName}
{
card.options.map((lo, idx) => {
return <input
key={idx}
type="radio"
name={card.cardName}
value={lo.radioName}
checked={!!lo.selected}
onChange={this.onInputChange}
/>
})
}
</ul>
))
}
< button onClick={this.onSubmit}>Print Cards</button>
</div>
}
</div>
);
}
}
ReactDOM.render(<CardsList />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

The reason why you can't change them is because of their current checked state which you are setting here:
<input
className="default"
type="radio"
name={card.cardName}
checked={lo.selected}
/>
An approach I have used for this exact scenario is storing the component's state (from the server) in my component's state (this.state), passing the state to the element: checked={this.state.isChecked}, and updating the element's state onClick.
Example:
class CardsList extends Component {
constructor(props){
super(props);
this.state = {isChecked: false};
this.inputOnClick = this.inputOnClick.bind(this);
}
//fetch data from server
fetchData(){
fetch('/api')
.then(res => res.json())
//this will be our initial state
.then(res => this.setState(res))
}
componentDidMount(){
this.fetchData();
}
//change radio button state on click
inputOnClick(e){
e.preventDefault();
//invert state value
this.setState((prevState, props) => {isChecked: !prevState.isChecked});
}
render(){
return (
<input
type="radio"
checked={this.state.isChecked}
onClick={this.inputOnClick}
/>
)
}
}
this answer may work with single radio button group , but i am facing
problem with multiple radio buttons with in multiple radio button
groups.if you see the array of cards , how does it know which radio
button group it belongs to.
We can modify the state based on the radio button's name.
Let's save all of your cards in your component's state. I know the cards are retrieved from the server and will be saved using setState but I am writing it like this for visual purposes.
this.state = {cards: [
{ cardName:'card1',
options:[
{radioName:'card1-radio1',selected:true},
{radioName:'card1-radio2',selected:false}
]
},
{ cardName:'card2',
options:[
{radioName:'card2-radio1',selected:true},
{radioName:'card2-radio2',selected:false}
]
}
]}
Now when we click on a radio button, we will use that radio button's name to update the state where it needs to be updated. Since React state needs to be immutable, we will create a deep copy of the state, modify it, and then set the state with it.
inputOnClick(e){
e.preventDefault();
var thisRadioBtn = e.target.name;
//make a deep copy of the state
const stateCopy = JSON.parse(JSON.stringify(this.state.cards));
//go through state copy and update it
stateCopy.forEach(card => {
card.options.forEach(option => {
if(option.radioName === thisRadioBtn){
//invert value
//make sure the values are booleans
option.selected = !option.selected;
}
});
});
//update the components state
this.setState({cards: stateCopy});
}

In June 2022, I'm facing a similar issue with you. My solution is just add tag <form> on both sections with no OnSubmit or action on it.

Related

create multiple react checkboxes from JS object

I have an Object in state which stores the current value of four 'Risk Type' checkboxes
riskTypes: {"Fraud": true, "Steal": true, "Scam": true, "Theft": true},
on the subcomponent to render them I use:
Object.keys(this.props.riskTypes).forEach(key => {
<li>
<label>
<input
type="checkbox"
value={key}
checked={this.props.riskTypes[key].value}
onChange={this.handleRiskTypeChange}
/> {key}
</label>
</li>
})
}
but this doesn't work, nothing is rendered, however if i console.log them instead of create checkboxes, it prints them fine. Any help much appreicated!
React expects JSX in some form, whether it's some HTML, or an array of HTML. forEach doesn't deliver that as it mutates the array rather than returning a new array.
map over the Object.entries to produce some JSX based on the information in state, and when you come to update the new state make sure that you keep the retain the old state properties.
I'm also using a name attribute on the input elements.
const { Component } = React;
class Example extends Component {
constructor(props) {
super();
this.state = {
riskTypes: props.riskTypes,
tempProp: 'temp',
tempProp2: 'temp2'
};
}
handleRiskTypeChange = (e) => {
// Get the name of the input, and its checked value
const { name, checked } = e.target;
// Because state is a nested object we
// 1) need to keep the state
// 2) update `riskTypes` using the existing
// riskTypes values, and updating only the one we
// need to update based on the name of the input
this.setState({
...this.state,
riskTypes: {
...this.state.riskTypes,
[name]: checked
}
}, () => console.log(JSON.stringify(this.state)));
}
getRiskTypes = () => {
const { riskTypes } = this.state;
const entries = Object.entries(riskTypes);
return entries.map(([key, value]) => {
return (
<li>
<label>
<input
name={key}
type="checkbox"
value={key}
checked={value}
onChange={this.handleRiskTypeChange}
/>{key}
</label>
</li>
);
});
}
render() {
return (
<ul>{this.getRiskTypes()}</ul>
);
}
};
const riskTypes = {'Fraud': true, 'Steal': true, 'Scam': true, 'Theft': true };
ReactDOM.render(
<Example riskTypes={riskTypes} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

How to make my checkbox to be clicked only once and other are unchecked in React.js

I followed a tutorial and created a form with as many checkboxes to be clicked. But, in another case, I need only one box to be checked. The values of checkboxes are dynamic and you never know, how many checkboxes will be created. But, only one can be clicked. Can you please help me in finding the solution thankyou.
import React, { Component } from 'react';
import Checkbox from "./Checkbox.component";
class PatientSelectTiming extends Component {
state = {
options: [...this.props.props],
checkboxes: [...this.props.props].reduce(
(options, option) => ({
...options,
[option]: false
}),
{}
),
appointmentSlots: null
};
handleCheckboxChange = e => {
const { name } = e.target;
this.setState(prevState => ({
checkboxes: {
...prevState.checkboxes,
[name]: !prevState.checkboxes[name]
}
}))
}
handleFormSubmit = formSubmitEvent => {
formSubmitEvent.preventDefault();
Object.keys(this.state.checkboxes)
.filter(checkbox => this.state.checkboxes[checkbox])
.forEach(checkbox => {
let appointmentSlot = [];
appointmentSlot.push(checkbox);
console.log(appointmentSlot);
this.setState({appointmentSlots: appointmentSlot})
localStorage.setItem('appointmentSlots', JSON.stringify(appointmentSlot))
});
};
createCheckbox = option => (
<Checkbox
label={option}
isSelected={this.state.checkboxes[option]}
onCheckboxChange={this.handleCheckboxChange}
key={option}
/>
);
createCheckboxes = () => this.state.options.map(this.createCheckbox);
render() {
return (
<div>
<p>Only select one item and only first date clicked will be your time</p>
<form onSubmit={this.handleFormSubmit}>
{this.createCheckboxes()}
<button type="submit">
Save
</button>
</form>
{this.state.appointmentSlots === null ? <p>Click on any slot to get your time.</p> : <p>Your time is {JSON.parse(localStorage.getItem("appointmentSlots"))}</p>}
</div>
)
}
}
export default PatientSelectTiming;
You can use a radio button
https://www.w3schools.com/tags/att_input_type_radio.asp
Radio button is the same as checkbox but only allows users to check only 1 option.

Handling state with multiple checkboxes in React Native

I have a simple form with two checkboxes for someone to choose one or the other i.e Yes or No not both. Am using the React-native-element toolkit as shown below.
export default class CheckerForm extends React.Component {
state = {
checked: false,
}
handleYesCheck =() => {
this.setState({checked: !this.state.checked})
}
handleNoCheck =() => {
this.setState({checked: !this.state.checked})
}
render(){
const { checked } = this.state
return (
<View>
<CheckBox
center
title='Yes'
checked={checked}
onPress={this.handleYesCheck}
/>
<CheckBox
center
title='No'
checked={checked}
onPress={this.handleNoCheck}
/>
<View>
I want to capture and modify the state of the checkboxes but when I click one of the checkboxes I modify the state of the other i.e both will be checked and unchecked. How can I modify the states of the checkboxes independently such that when I click on Yes, No is unchecked and vice versa? Generally what is the best way to capture the state so that I can use it.
What you can do is have a array of checkboxes, and save in the state the index of the checked one.
state = {
checkedId: -1,
checkboxes: [{
id: "yes",
title: "Yes"
}, {
id: "no",
title: "No"
}]
}
handleCheck = (checkedId) => {
this.setState({
checkedId
})
}
render() {
const {
checkboxes,
checkedId
} = this.state
return ( <
View > {
checkboxes.map(checkbox => ( <
CheckBox center key = {
checkbox.id
}
title = {
checkbox.title
}
checked = {
checkbox.id == checkedId
}
onPress = {
() => this.handleCheck(checkbox.id)
}
/>
)
} <
View >
)
}
This way you can also handle more than two checkboxes and also know which one is checked by the index.

how to populate options for a react select by fetching values from an api to be visible when you click on the select box?

I am using react-select for drop downs in my application. Below is my requirement.
Select a value from the drop down for the first Select component(second Select is not rendered yet).
Basing on the selected value fetch the options for second Select component and render the second Select box.
Click in the text area of the second Select.
What is happening : I see No Options as the default drop down. I can
see the values from the API only when I type something in the box and
that matches the default filter criteria.
What I want to happen : It
should display the values that we fetched from the API call.
const options = [{ label: "first", value: "first" }];
let options1 = [];
async function copyOptionsForAsync() {
let response = await fetch("https://jsonplaceholder.typicode.com/todos");
let data = await response.json();
data.forEach(element => {
let dropDownEle = { label: element["title"], value: element };
options1.push(dropDownEle);
});
}
class App extends React.Component {
constructor() {
super();
this.state = {
isSelected: false
};
}
handleOnchange = () => {
this.setState({ isSelected: true });
copyOptionsForAsync();
console.log(options1);
};
render() {
return (
<div className="App">
<Select
name="option"
options={options}
onChange={this.handleOnchange}
/>
{this.state.isSelected ? <App1 /> : null}
</div>
);
}
}
class App1 extends React.Component {
render() {
return (
<div className="App">
<Select name="options2" options={options1} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
This is the link to codesandbox page. Can any one tell me how will I be able to display the options once I click on the select box.
'App1' is rendering before you actually get the data. One way to fix this is to wait for the data to be fetched then render 'App1', like this:
handleOnchange = async () => {
await copyOptionsForAsync();
this.setState({ isSelected: true });
};
Working example in codesandbox: https://codesandbox.io/s/m6wr8zvjj

Redux-form get event in submit handler function

Next to my redux forms I've a small sidebar with form names to switch between different forms. To keep the app state updated anytime when a user switch between these forms via the sidebar I do a submission of the current form state via the redux-form handleSubmit function. The submission is fired with a dispatch which updates the app state. This works fine but to make the clicked form visible I need to change the 'active' value in my state to the name of the form, this name is set on the list item in the sidebar items loop as a classname.
I tried to receive the clicked element classname via event.target.className but it seems to have no results as event seems not to be the event I expected. How can I access the event in a handleSubmit mission to change the active form with the clicked className of list item of the sidebar?
p.s. I will create a separated function, if / else statement or something else for this saveData function as I know the event.target.className won't be correct if the form would be submitted by the form button itself.
Example code
... some form field code
class ComponentName extends React.Component {
constructor(props) {
super(props);
this.state = {
someName : this.props.someName,
}
this.saveData = this.saveData.bind(this);
}
saveData(values, dispatch) {
let newState = {
SomeName : update(this.state.someName, {
active : {
$set : this.state.active <======= event.target.className ?
},
rows : {
data : {
$set : values.rows
}
}
})
}
this.setState(newState);
dispatch(someActionNAme(newState.someName));
}
render() {
const { handleSubmit, submitting } = this.props;
var items = {}
Object.values(this.state.someName).map(function(item, i) {
if (typeof item == 'object' && item !== 'undefined' && item !== null) {
items[i] = item;
}
})
return (
<div className="someName__form">
<form onSubmit={handleSubmit(this.saveData)}>
<ul className="someName__sidebar">
{ Object.keys(items).map((item, i) => (
<li
key={i}
data-id={i}
onClick={handleSubmit(this.saveData)}
id={items[item].meta.name}
className={items[item].meta.name}
data-sort={items[item].meta.sort}
>
{items[item].meta.label}
{this.state.someName.active == items[item].meta.name &&
<b>
[Active]
</b>
}
</li>
)
)}
</ul>
<FieldArray name="rows" component={renderRows} />
<div>
<button id="button" type="submit" disabled={submitting} onClick={handleSubmit(this.saveData)}>
Save
</button>
</div>
</form>
</div>
)
}
}
const mapStateToProps = (state) => ({
initialValues : state.someName.rows,
someName : state.someName,
});
const mapDispatchToProps = (dispatch) => ({
dispatch,
});
ComponentName = reduxForm({
form : 'formName',
destroyOnUnmount: false
})(ComponentName);
export default connect(mapStateToProps, mapDispatchToProps)(ComponentName);
To help others out, I found myself the solution by diving deep into the github issue articles of redux-form.
It's easy and possible to add some additional data to the form values via the handleSubmit function. By adding data as below, the additional data will be merged into the object with the values as follow.
...onClick={handleSubmit(values => this.saveData({...values, active: 'componentName' }))}
Values object will now contain an extra key value pair:
{
"languages": [
{
"name": "Spanish",
"level": "3"
}
],
"active": "componentName"
}

Categories

Resources