Why can't i check Formik checkboxes? - reactjs

I'm trying to create a simple Formik form. First time working with checkboxes. I can render the checkboxes, but am unable to check any of them. What's the problem?
export default function App() {
var lights = [{id:1, name:"a"}, {id:2, name:"a"}]
return (<div>
<h1>Select Lights</h1>
<Formik
initialValues={{
checked: [],
}}
onSubmit={(eve) => this.submit(eve)}
>
{({values}) => (
<form>
<div id="checkbox-group">Checked</div>
<div role="group" aria-labelledby="checkbox-group">
{lights.map((light) =>
<div>
<label>
<Field type="checkbox" key={light.id} name="checked" value={light.id}/>
{light.name}
</label>
</div>
)}
</div>
<button type="submit" onClick={(event => this.submit(event))}>Submit</button>
</form>
)}
</Formik>
</div>)
}

Checkbox value being a number seems to be the problem. Because you initialise it as a number but internally it's converted to a string when formik is tracking the checkbox value. Change
<Field type="checkbox" key={light.id} name="checked" value={light.id}/>
to:
<Field type="checkbox" key={light.id} name="checked" value={light.id.toString()}/>

Related

react hook form: how can i validate a group of radio buttons or checkboxes to ensure at least one is selected

I have a group of checkboxes and a group if radios and I want to validate with react hook form to ensure that an error message is generated if none have been selected on submit.
I have tried experimenting with the form builder on their website but I cant work out how to validate a group of items as a single unit of validation.
<div>
<span>Option A <input type="checkbox" value="A" /></span>
<span>Option B <input type="checkbox" value="B" /></span>
<span>Option C <input type="checkbox" value="C" /></span>
</div>
<...output a validation error if one or more checkboxes hasnt been checked within the group>
<div>
<span>Option A <input type="radio" value="A" /></span>
<span>Option B <input type="radio" value="B" /></span>
<span>Option C <input type="radio" value="C" /></span>
</div>
<...output a validation error if one or more radios hasnt been checked within the group>
Is this possible and is there a correct way to do it?
Thank you for your time and attention.
You added the react-hook-form tag to your question but there is nothing in your code related to it. If indeed you're using React Hook Form a way to accomplish what you want is using schema validation through yup:
const schema = yup.object().shape({
checkbox: yup.array().min(1),
radio: yup.string().required(),
});
export default function App() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
const onSubmit = (data) => {
alert(JSON.stringify(data));
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<span>
Checkbox 1
<input type="checkbox" {...register('checkbox')} value="A" />
</span>
<span>
Checkbox 1
<input type="checkbox" {...register('checkbox')} value="B" />
</span>
<span>
Checkbox 3
<input type="checkbox" {...register('checkbox')} value="C" />
</span>
<p style={{ color: 'red' }}>
{errors.checkbox && 'At least one checkobox must be selected'}
</p>
<span>
<label>Radio 1</label>
<input type="radio" {...register('radio')} value="A" />
</span>
<span>
<label>Radio 2</label>
<input type="radio" {...register('radio')} value="B" />
</span>
<span>
<label>Radio 3</label>
<input type="radio" {...register('radio')} value="C" />
</span>
<p style={{ color: 'red' }}>{errors.radio && 'Radio is required'}</p>
<input type="submit" />
</form>
);
}
See a working stackblitz.
Note that as radio button options are exclusive (only one can be selected) you're just sayng that the field is required.
Check out this guide and the documentation for radio buttons
In short, giving the same name attribute to your radio buttons ensures that only one of them can be selected at a time. Adding an onChange function linked to a useState within your component allows you to track the selected value and react if none has been selected.
The resulting code should be something like this:
const YourComponent = () => {
const [myValue, setMyValue] = useState(null);
const onValueChange = e => {
setMyValue(e.target.value);
};
return (
<div>
<div onChange={onValueChange}>
<span>
Option A <input type="checkbox" value="A" name="myValue" />
</span>
<span>
Option B <input type="checkbox" value="B" name="myValue" />
</span>
<span>
Option C <input type="checkbox" value="C" name="myValue" />
</span>
</div>
{!myValue && <span>Please select a value</span>}
</div>
);
};

How to create a RadioButtonGroup with "pure" react?

I am trying to create a radio group that can be handled with the onChange function provided to the parent div:
const handleAgeGroupOnChange = (e) => {
console.log(e.target.value)
};
<div onChange={handleAgeGroupOnChange} className="flex flex-col">
<label>
<input type="radio" value="18-34" name="18-34" />
18-34
</label>
<label>
<input type="radio" value="35-49" name="35-49" />
35-49
</label>
<label>
<input type="radio" value="50+" name="50+" />
50+
</label>
<label>
<input type="radio" value="all" name="Mix all ages" />
Mix all ages
</label>
</div>
The logging is working, I am getting all of the correct values, however the checked state of the buttons is not updated correctly, if I click all of the buttons, all of them will stay checked.
Do I have to do the checked property for each button, and is there a way to do this, without having to manually do checked on each radio button?
To make things easier, I would put the options into an array and just map them like the below and give each radio button the same name and just make them readOnly.
const options = ["18-34", "35-49", "50+", "Mix all ages"];
function App() {
const [age, setAge] = React.useState("");
const handleOnChange = (evt) => {
const { value } = evt.target;
setAge(value);
};
return (
<div className="App">
{options.map((option) => (
<div key={option}>
<label>
<input
type="radio"
name="age"
value={option}
readOnly={true}
checked={age === option}
onChange={handleOnChange}
/>
{option}
</label>
</div>
))}
<pre>Selected option: {age}</pre>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Trying to have multiple input elements

But when i use handle input change function it erases the entire object and replaces it with one property
Also If someone can help me reset the form data? because I'm setting state to the initial state value but the text fields arent erasing.
const initialState = {
name: '',
number: '',
message: '',
email: '',
messageSent: false,
};
//State After typingState
{email: "2"}
I was using a class-based component and it was working fine I switched over and now I am getting one property on submit instead of 4
I would like for the handle change to change a particular property and not the entire object
stepped away from react for a while an d not sure what to google fo this fix. Tried
Handling Multiple inputs Functional Components React etc..
let handleInputChange = (event) => {
const target = event.target;
const value = target.value;
const name = target.name;
setstate({
[name]: value,
});
}
return (
<section
id="contact-form"
className={
GrayBg === true
? 'contact-form-area_3'
: 'contact-form-area_3 contact-page-version'
}
>
<div className="container">
<div className="section-title mb45 headline text-center">
<span className="subtitle text-uppercase">Send us a message</span>
<h2>
Send Us A<span> Message.</span>
</h2>
</div>
<div className="contact_third_form">
<form
className="contact_form"
encType="multipart/form-data"
onSubmit={ sendEmail}
>
<div className="row">
<div className="col-md-4">
<div className="contact-info">
<input
className="name"
name="name"
type="text"
value={state.value}
onChange={handleInputChange}
placeholder="Your Name."
/>
</div>
</div>
<div className="col-md-4">
<div className="contact-info">
<input
className="email"
name="email"
type="email"
value={state.value}
onChange={handleInputChange}
placeholder="Your Email"
/>
</div>
</div>
<div className="col-md-4">
<div className="contact-info">
<input
className="number"
name="number"
type="number"
value={state.value}
onChange={handleInputChange}
placeholder="Phone Number"
/>
</div>
</div>
</div>
<textarea
name="message"
placeholder="Message."
value={state.value}
onChange={handleInputChange}
></textarea>
<div className="nws-button text-center gradient-bg text-uppercase">
<button id="contact-button" type="submit">
{state.messageSent ? 'Sent!' : 'Send'}{' '}
<i
className={
state.messageSent
? 'fas fa-check'
: 'fas fa-caret-right'
}
></i>
</button>
</div>
</form>
</div>
</div>
State updates are not merged with hooks unlike in class components, you need to do so yourself.
Update your state using a functional approach to setState and by spreading the rest of the values within the returned object like
setstate(prev => ({
...prev,
[name]: value,
}));

selected radio dropdown value in the inputfield instead of placeholder and show/hide dropdown when I click input field using reactjs

I want dropdown with radio button which needs to show when I click field and hide when I click outside and get the selected value in the inputfield.
I need the selected value in the input field instead of placeholder value which I have and with show/hide dropdown when I click input field
<div className="inputWithIcon" onClick={this.RadioDdOnclick} id="radiobtn">
<input className="inputBlock" id="radioSelect" type="text" placeholder="choose one" />
<i className="fa fa-angle-down" />
</div>
<div className={ "BudgetRadioDd"} id={ "RadioDd"} style={{display: 'none'}}>
<fieldset>
<h4>options to choose</h4>
<div>
<label><input type="radio" id="1"/>option 1</label>
</div>
<div>
<label> <input type="radio" id="2" />option 2</label>
</div>
<div>
<label><input type="radio" id="3"/>option 3</label>
</div>
</fieldset>
</div>
Not sure how to get this done using reactjs.
How to do this in React:
You can attach a ref to the <input> to access it from the parent component, then check if the <input> is focused. If it is focused render the radio buttons, else don't.
To show the value of the selected option in the input tag, store that value in the component's state then it's easy to track its value and share that value between the input and the radios.
Also I've added the name attribute to the radio inputs so that they work properly as radio buttons.
import React, { Component } from 'react';
class FormComponent extends Component {
constructor() {
super();
this.state = {
option: '',
inputIsFocused: false
}
}
handleChange = event => {
const { value } = event.target;
this.setState({option: value});
}
toggleRadio = event => {
const { inputIsFocused } = this.state;
this.setState({inputIsFocused: !inputIsFocused});
}
render() {
const { option, inputIsFocused } = this.state;
return (
<div>
<div className="inputWithIcon" id="radiobtn">
<input ref='input' value={ option } className="inputBlock" id="radioSelect" type="text" placeholder="choose one" onFocus={ this.toggleRadio } onFocusOut={ this.toggleRadio }/>
<i className="fa fa-angle-down" />
</div>
{ inputIsFocused && (
<div className={ "BudgetRadioDd"} id={ "RadioDd"}>
<fieldset>
<h4>options to choose</h4>
<div>
<label><input name='option' type="radio" id="1" value="1" checked={ option === '1' } onChange={ this.handleChange }/>option 1</label>
</div>
<div>
<label> <input name='option' type="radio" id="2" value="2" checked={ option === '2' } onChange={ this.handleChange }/>option 2</label>
</div>
<div>
<label><input name='option' type="radio" id="3" value="3" checked={ option === '3' } onChange={ this.handleChange }/>option 3</label>
</div>
</fieldset>
</div>
)}
</div>
)
}
}
A note on the above: I think there's an issue with onFocusOut, so that doesn't actually get fired. Also of note is that the inputIsFocused value used in render is not the one from state. This is because of the issue with onFocusOut so the value of inputIsFocused needs to come from referencing the DOM, but in order to trigger a rerender, the component's state needs to update.
Here's a snippet to show what it might look like (note that the value in the snippet does not update when when a button is selected, but that functionality is present in the React code):
function hideRadio() {
const radioDd = document.querySelector('.BudgetRadioDd');
radioDd.classList.add('hidden');
}
function showRadio() {
const radioDd = document.querySelector('.BudgetRadioDd');
radioDd.classList.remove('hidden');
}
.hidden {
display: none;
}
<div class="inputWithIcon" onClick={this.RadioDdOnclick} id="radiobtn">
<input class="inputBlock" id="radioSelect" type="text" placeholder="choose one" onfocus="showRadio();" onfocusout="hideRadio();"/>
<i className="fa fa-angle-down" />
</div>
<div class="BudgetRadioDd hidden" id="RadioDd">
<fieldset>
<h4>options to choose</h4>
<div>
<label><input name='attributeName' type="radio" id="1"/>option 1</label>
</div>
<div>
<label> <input name='attributeName' type="radio" id="2" />option 2</label>
</div>
<div>
<label><input name='attributeName' type="radio" id="3"/>option 3</label>
</div>
</fieldset>
</div>
However this seems like pretty much the functionality of a <select>, so maybe consider using one of those?

React Materialize checkbox

Disclaimer: New to development and this is my first s/o post.
I'm using materialize on a react project with a filter that has checkboxes. I currently have the checkbox component returning as:
return (
<div className={this.props.type} >
<form>
<input
type={this.props.type}
value={label}
checked={isChecked}
onChange={(e) => this.toggleCheckboxChange(e)}
/>
<label>
<input
type={this.props.type}
value={label}
checked={isChecked}
onChange={(e) => this.toggleCheckboxChange(e)}
/>
{label}
</label>
</form>
</div>
)
It renders with the checkbox and the label next to it. However, if I remove the input tag that's outside of the label tag like so:
return (
<div className={this.props.type} >
<form>
<label>
<input
type={this.props.type}
value={label}
checked={isChecked}
onChange={(e) => this.toggleCheckboxChange(e)}
/>
{label}
</label>
</form>
</div>
)
the checkbox disappears but the text is still clickable. The component is class based so it maintains its state of being checked or unchecked.
Any ideas as to why it behaves this way?
Problem fixed! Turns out I needed an ID on my input field that matched and htmlFor property on my label field. See below code for the fix:
<div className={this.props.type} >
<form>
<input
id={label[0]}
type={this.props.type}
value={label}
checked={isChecked}
onChange={(e) => this.toggleCheckboxChange(e)}
/>
<label htmlFor={label[0]}>
{label}
</label>
</form>
</div>

Resources