I have a form that has more than 1 textarea(3 to exact). The issue is that, if I type something in 1 textarea then the other textareas are also filled and updated. And I dont' want that. I'm using an onChange handler to make them controlled components but they're not behaving as expected. I've looked online but couldn't find a solution! How do I make them update individually on change/type?
React Textarea component
const Textarea = ({ labelText, placeholder, value, name, onChange }) => {
return (
<div className="textarea-panel">
<label>{labelText}</label>
<textarea
style={{ display: 'block' }}
cols="60"
rows="5"
placeholder={placeholder}
value={value}
name={name}
onChange={onChange}
/>
</div>
);
}
export default Textarea;
React Form component
import Textbox from './Textbox';
export class Form extends Component {
constructor(props) {
super(props);
this.state = {
data: {
career: '',
experience: '',
additionalInformation: '',
},
};
this.handleTextareaChange = this.handleTextareaChange.bind(this);
}
handleTextareaChange(event) {
this.setState({
data: {
[event.target.name]: event.target.value,
},
});
}
render() {
return (
<form>
<ul>
<li>
<Textbox
labelText="Write your career "
value={this.state.data.career}
placeholder="e.g. extra information"
name="career"
onChange={this.handleTextareaChange}
/>
<span>{this.state.data.career}</span>
</li>
<li>
<Textbox
labelText="Write your experience"
value={this.state.data.experience}
placeholder="e.g. extra information"
name="experience"
onChange={this.handleTextareaChange}
/>
<span>{this.state.data.experience}</span>
</li>
<li>
<Textbox
labelText="Additional information"
value={this.state.data.additionalInformation}
placeholder="e.g. extra information"
name="additionalInformation"
onChange={this.handleTextareaChange}
/>
<span>{this.state.data.additionalInformation}</span>
</li>
</ul>
</form>
);
}
}
export default Form;
You're overriding the entire state with a new key on the data object. Every time the onChange handler is called the entire state gets overriden with a new data object with the current textbox's name as the key. Instead just update the particluar key on the data like this
Try it here : Codesandbox
handleTextareaChange(event) {
const { data } = this.state
data[event.target.name] = event.target.value
this.setState({
data
});
}
Hope this helps !
Related
I have a Main.js page that has one button: when you click it it adds a Block component to an array and to the page. You can add as many Block components as you want. Each Block component has a "delete" button, that will remove the Block from the array and from the page.
Menu.js:
import React from 'react';
import './Menu.css';
import Block from './Block.js';
import './Block.css';
export default class Menu extends React.Component {
constructor(props) {
super(props);
this.state = { value: '', blocksArray: [] };
this.addBlock = this.addBlock.bind(this);
this.removeBlock = this.removeBlock.bind(this);
this.blocks = [];
}
addBlock() {
this.blocks.push({ title: 'Section title' + this.blocks.length, content: 'Content' + this.blocks.length });
this.setState({ value: '', blocksArray: this.blocks });
}
removeBlock(index) {
this.blocks.splice(index, 1);
this.setState({ value: '', blocksArray: this.blocks })
}
renderBlocks = () => {
return (
this.state.blocksArray.map((block, index) =>
<Block
remove={() => this.removeBlock(index)}
key={index}
title={block.title}
content={block.content}
/>
)
)
}
render() {
return (
<div>
<div className="Menu">
<header className="Menu-header">
<button className="Menu-button" onClick={ () => this.addBlock() }>Add block</button>
</header>
</div>
<div>
{ this.renderBlocks() }
</div>
</div>
);
}
}
Block.js (version 1)
import React from 'react';
import './Block.css';
class Block extends React.Component {
constructor(props) {
super(props);
this.state = {
title: props.title,
content: props.content,
remove: props.remove
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({[event.target.name]: event.target.value});
}
handleSubmit(event) {
//alert('A name was submitted: ' + this.state.title);
event.preventDefault();
}
render() {
return (
<div className="Block-container">
<form onSubmit={this.handleSubmit}>
<div className="Block-title">
<label>
Title:
<input type="text" name="title" value={this.props.title} onChange={this.handleChange} />
</label>
</div>
<div className="Block-content">
<label>
Content:
<input type="text" name="content" value={this.props.content} onChange={this.handleChange} />
</label>
</div>
<input type="submit" value="Save" />
<input type="button" value="Delete" onClick= { () => this.state.remove() } />
</form>
</div>
);
}
}
export default Block;
The issue: I found myself stuck with 2 situations and neither works properly.
First non working solution for Block.js:
<input type="text" name="title" value={this.props.title} onChange={this.handleChange} />
<input type="text" name="content" value={this.props.content} onChange={this.handleChange} />
If I use value={this.props.content} and value={this.props.title} when I push the delete button on the Block it works but I can't edit the text in the field since its value is always retrieved from the props.
Second non working solution for Block.js:
<input type="text" name="title" value={this.state.title} onChange={this.handleChange} />
<input type="text" name="content" value={this.state.content} onChange={this.handleChange} />
If I use value={this.state.content} and value={this.state.title} I can edit the text fields and when I push the delete button on the Block it removes properly the component from the array, but the text displayed in the fields is wrong (it's as if it's always popping only the last component from the array). Let me explain with a few screenshots.
Let's say I added 4 Block components, as follow:
Then I click on the delete button of the Block with "Section title1" / "Content1", as this screenshot:
It apparently removes the right element in the array, but for some reason I get the wrong text in the component:
Array console.log:
0: Object { title: "Section title0", content: "Content0" }
1: Object { title: "Section title2", content: "Content2" }
2: Object { title: "Section title3", content: "Content3" }
Displayed text:
I'm obviously missing something and I have been stuck for a while. Can someone explain what is wrong?
I think the problem is you are setting index as the key for each Block.
Origin keys are [0, 1, 2, 3]. When you remove Section title1, new render will produce keys [0, 1, 2]. So React assumes that element with keys [0, 1, 2] are not changed and key 3 is removed. So it removed the last one.
Try to use an unique property for the key.
You can read more here: https://reactjs.org/docs/reconciliation.html#keys
Your change handler needs to be operating on the state in the parent component where the title/content are coming from. The values shown in the Blocks are being read from the Menu's state, so while editing the block data changes its own internal state, the values coming from the menu to the block as props are staying the same because the internal state is not being fed back.
You could write a function to edit the arrays in the Menu state in place:
this.editBlock = this.editBlock.bind(this);
...
editBlock(index, newBlock) {
let blocks = Array.from(this.state.blocksArray);
blocks[index] = newBlock;
this.setState({
blocksArray: blocks
})
}
and then pass that to the Block as props and call it when the change event fires:
<Block
remove={() => this.removeBlock(index)}
key={index}
title={block.title}
content={block.content}
index={index}
editBlock={this.editBlock}
/>
handleChange(event) {
this.setState({[event.target.name]: event.target.value}, () => {
this.props.editBlock(this.props.index, { title: this.state.title, content: this.state.content})
});
}
Working demo here.
I have a little app that has an input and based on the search value, displays weather for a particular city. I'm stuck at a certain point though. The idea is that once you search a city, it hides the text input and search button and displays some weather info and another search button to search a new city. My issue is that I want to focus on the search box once I click to search again. I hope that makes sense. I read that the ideal way to do this is with refs. I wired it up like such:
class WeatherForm extends React.Component {
constructor(props) {
super(props);
this.city = React.createRef();
}
componentDidMount() {
this.props.passRefUpward(this.city);
this.city.current.focus();
}
render() {
if (this.props.isOpen) {
return (
<div className={style.weatherForm}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
)
} else {
return (
<div className={style.resetButton}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
);
}
}
}
With this I can pass that ref up to the parent to use in my search by using this.state.myRefs.current.value; It works great, but when I try to reference this.state.myRefs.current in a different function to use .focus(), it returns null.
resetSearch = () => {
console.log(this.state.myRefs.current); // <- returns null
this.setState({
isOpen: !this.state.isOpen,
details: [],
video: []
});
}
Is this because I'm hiding and showing different components based on the search click? I've read numerous posts on SO, but I still can't crack this. Any help is appreciated. I'll include the full code below. To see it in full here is the git repo: https://github.com/DanDeller/tinyWeather/blob/master/src/components/WeatherMain.js
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = {
recentCities: [],
details: [],
isOpen: true,
myRefs: '',
video: '',
city: ''
};
this.updateInputValue = this.updateInputValue.bind(this);
this.getRefsFromChild = this.getRefsFromChild.bind(this);
this.resetSearch = this.resetSearch.bind(this);
this.getWeather = this.getWeather.bind(this);
}
updateInputValue = (e) => {
...
}
resetSearch = () => {
console.log(this.state.myRefs.current);
this.setState({
isOpen: !this.state.isOpen,
details: [],
video: []
});
}
getWeather = (e) => {
...
}
getRefsFromChild = (childRefs) => {
...
}
render() {
return (
<section className={style.container}>
<div className={style.weatherMain + ' ' + style.bodyText}>
<video key={this.state.video} className={style.video} loop autoPlay muted>
<source src={this.state.video} type="video/mp4">
</source>
Your browser does not support the video tag.
</video>
<div className={style.hold}>
<div className={style.weatherLeft}>
<WeatherForm
updateInputValue={this.updateInputValue}
getWeather={this.getWeather}
passRefUpward={this.getRefsFromChild}
resetSearch={this.resetSearch}
isOpen={this.state.isOpen}
/>
<WeatherList
details={this.state.details}
city={this.state.city}
isOpen={this.state.isOpen}
/>
</div>
<div className={style.weatherRight}>
<Sidebar
recentCities={this.state.recentCities}
/>
</div>
<div className={style.clear}></div>
</div>
</div>
</section>
);
}
}
class WeatherForm extends React.Component {
constructor(props) {
super(props);
this.city = React.createRef();
}
componentDidMount() {
this.props.passRefUpward(this.city);
this.city.current.focus();
}
render() {
if (this.props.isOpen) {
return (
<div className={style.weatherForm}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
)
} else {
return (
<div className={style.resetButton}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
);
}
}
}
export default Weather;
You try to achieve unmounted component from DOM, because of this you can not catch the reference. If you put this code your instead of render function of WeatherForm component, you can catch the reference. Because i just hide it, not remove from DOM.
render() {
return (
<div>
<div className={style.weatherForm}
style={this.props.isOpen ? {visibility:"initial"} :{visibility:"hidden"}}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
<div className={style.resetButton} style={this.props.isOpen ? {visibility:"hidden"} :{visibility:"initial"}}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
</div>
)
}
console.log(this.state.myRefs.current) returns null , because it's a reference to an input dom element which does not exists as currently Weather form is displaying Search another city along with a reset button.
In reset function state changes, which results in change of prop isOpen for WeatherForm component. Now, screen would be displaying the input field along with search button.
After component is updated ComponentDidUpdate lifecycle method is called.
Please add ComponentDidUpdate lifecycle method in WeatherForm and add ,
this.city.current.focus() in the body of method.
There is no need to pass reference of a dom element to the parent element as it is not consider as a good practise.
Edit 1 :-
Need to set input field in focus only if prop ( isOpen ) is true as we will get reference to the input field only if its mounted.
ComponentDidUpdate(){
if(this props.isOpen)
this.city.current.focus
}
Link to Lifecycle method :-
https://reactjs.org/docs/react-component.html#componentdidupdate
Hope this helps,
Cheers !!
I am trying to include a select tag in react while using form. But the dropdown is not getting rendered.
The thing is that the drop down menu for select is not even appearing on the screen.This is also happening with radio boxes.
The only thing that is appearing in the browser are the two input fields and after that the label for the select tag.Afterwards the drop down menu is absent
here is my code-
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
class UpdateBillForm extends Component {
constructor(props) {
super(props);
this.state = {
billername: this.props.bill.billername,
userid: this.props.bill.userid,
value: "sid"
};
this.onbillerNameUpdate = this.onbillerNameUpdate.bind(this);
this.onbillerDescriptionUpdate =
this.onbillerDescriptionUpdate.bind(this);
this.handleChange = this.handleChange.bind(this);
}
onSubmit(e) {
e.preventDefault;
this.props.onSubmit({
billerName: this.state.billerName,
billerDescription: this.state.billerDescription
});
this.props.history.push("/viewbiller");
}
onbillerNameUpdate(target) {
this.setState(() => ({ billername: target }));
}
onbillerDescriptionUpdate(target) {
this.setState(() => ({ billerDescription: target }));
}
handleChange(event) {
this.setState({ value: event.target.value });
}
render() {
return (
<div>
<form onSubmit={e => this.onSubmit(e)}>
<label htmlFor="billerName">BillerName</label>{" "}
<input
type="text"
value={this.state.billername}
onChange={e => this.onbillerNameUpdate(e.target.value)}
/>
<label htmlFor="billerDescription">BillerDescription</label>{" "}
<input
type="text"
value={this.state.billerDescription}
onChange={e => {
this.onbillerDescriptionUpdate(e.target.value);
}}
/>
<label>
Name
<select value={this.state.value} onChange={e=>this.handleChange(e)}>
<option value="sid">Sid</option>
<option value="jam">jam</option>
</select>
</label>
<br />
<br />
<button className="btn-flat white-text teal">Update</button>
<Link className="btn-flat white-text right red" to="/viewbiller">
Cancel
</Link>
</form>
</div>
);
}
}
export default withRouter(connect()(UpdateBillForm));
If you're using materializecss, it seems to override the default behavior so it doesn't show up at all unless you initialize with materialize code.
https://materializecss.com/select.html
If you don't have dynamically rendered select elements you can use M.AutoInit() to initialize everything at once.
https://materializecss.com/auto-init.html
Lastly, if you don't want to muck with initialization, and don't mind losing the slightly nicer (arguable for mobile experience?) looking interface, you can add className="browser-default" to your select element(s) and it avoids the issue.
Problem
I have a list of people. I want to:
Select a user to edit by clicking on their name.
Edit that user's information, so I can click the submit button and update the list.
If I click on a different name, I want to switch to that person's information without having to deliberately close the form first.
Everything works until #3. When I click on another person, the form, itself, does NOT update.
My Code
Update Component for the update form:
const UpdateForm = ({ updatePerson, personToUpdate, handleInputChange }) => {
let _name, _city, _age, _id;
const submit = (e) => {
e.preventDefault();
updatePerson({
name: _name.value,
city: _city.value,
age: _age.value,
_id: _id.value
});
};
return (
<div>
<form onSubmit={submit}>
<h3>Update Person</h3>
<label htmlFor="_id">Some Unique ID: </label>
<input type="text" name="_id" ref={input => _id = input} id="_id" defaultValue={personToUpdate._id} onChange={input => handleInputChange(personToUpdate)} required />
<br />
<label htmlFor="name">Name: </label>
<input type="text" name="name" ref={input => _name = input} id="name" defaultValue={personToUpdate.name} onChange={input => handleInputChange(personToUpdate)} />
<br />
<label htmlFor="city">City: </label>
<input type="text" name="city" ref={input => _city = input} id="city" defaultValue={personToUpdate.city} onChange={input => handleInputChange(personToUpdate)} />
<br />
<label htmlFor="age">Age: </label>
<input type="text" name="age" ref={input => _age = input} id="age" defaultValue={personToUpdate.age} onChange={input => handleInputChange(personToUpdate)} />
<br />
<input type="submit" value="Submit" />
</form>
</div>
);
};
export default UpdateForm;
Relevant parts of Person Component:
class Person extends Component {
nameClick() {
if (this.props.person._id !== this.props.personToUpdate._id) {
this.props.setForUpdate(this.props.person);
this.forceUpdate();
}
else {
this.props.toggleUpdatePersonPanel();
}
}
render() {
return (
<div>
<span onClick={this.nameClick}>
{this.props.person.name} ({this.props.person.age})
</span>
</div>
);
}
}
export default Person;
Relevant parts of PeopleList, which holds Persons:
class PeopleList extends Component {
render() {
return(
<div>
{this.props.people.map((person) => {
return <Person
key={person._id}
person={person}
updatePersonPanel={this.props.updatePersonPanel}
setForUpdate={this.props.setForUpdate}
personToUpdate={this.props.personToUpdate}
/>;
})}
</div>
);
}
} // end class
export default PeopleList;
Form Reducer, with just the relevant actions:
export default function formReducer(state = initialState.form, action) {
let filteredPeople;
switch (action.type) {
case TOGGLE_UPDATE_PANEL:
return Object.assign({}, state, { updatePersonPanel: false }, { personToUpdate: {
_id: "",
name: "",
city: "",
age: ""
}});
case SET_FOR_UPDATE:
return Object.assign({}, state, { personToUpdate: action.person }, { updatePersonPanel: true });
case UPDATE_RECORD:
filteredPeople = state.people.filter((person) => {
return person._id === action.person._id ? false : true;
}); // end filter
return Object.assign({}, state, { people: [ ...filteredPeople, action.person] }, { personToUpdate: {
_id: "",
name: "",
city: "",
age: ""
}}, { updatePersonPanel: false });
case HANDLE_INPUT_CHANGE:
return Object.assign({}, state, { personToUpdate: action.person });
default:
return state;
}
}
The relevant parts of my Initial State file:
form: {
people: [
{
_id: "adfpnu64",
name: "Johnny",
city: "Bobville",
age: 22
},
{
_id: "adf2pnu6",
name: "Renee",
city: "Juro",
age: 21
},
{
_id: "ad3fpnu",
name: "Lipstasch",
city: "Bobville",
age: 45
}
],
updatePersonPanel: false,
personToUpdate: {
_id: "",
name: "",
city: "",
age: ""
},
}
Attempts at a Solution( so far)
I have attempted to make the component a completely controlled component, by switching the form attribute to value instead of defaultValue. When I do this, the names switch just fine, but the form becomes unchangeable and useless.
My Questions
Almost all of the solutions to these kind of issues either recommend using redux-form or supply two-way binding solutions that work fine in React without reduce. I want to know how to do this with Redux without using redux-form or anything extra if possible. Is there a way to resolve this without touching lifecycle methods?
Conclusion (For now)
Well, for now, I settled for making my form uncontrolled and used some classic Js DOM methods and a lifecycle method to control the form. For whatever reason, once I employed some of the answer suggestions my browser ate up my CPU and crashed, presumably because there was some kind of infinite loop. If anyone has some further recommendations, I'd really appreciate it. For now I settle for this:
class UpdateForm extends Component {
constructor(props){
super(props);
this.submit = this.submit.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.personToUpdate._id !== this.props.personToUpdate._id) {
document.getElementById("name").value = nextProps.personToUpdate.name;
document.getElementById("age").value = nextProps.personToUpdate.age;
document.getElementById("city").value = nextProps.personToUpdate.city;
}
}
submit(e) {
e.preventDefault();
this.props.updatePerson({
name: document.getElementById("name").value,
city: document.getElementById("city").value,
age: document.getElementById("age").value,
_id: this.props.personToUpdate._id
});
}
render() {
return (
<div>
<form onSubmit={this.submit}>
<h3>Update Person</h3>
Unique ID: {this.props.personToUpdate._id}
<br />
<label htmlFor="name">Name: </label>
<input type="text" name="name" id="name" ref="name" defaultValue={this.props.personToUpdate.name} required />
<br />
<label htmlFor="city">City: </label>
<input type="text" name="city" id="city" ref="city" defaultValue={this.props.personToUpdate.city} required />
<br />
<label htmlFor="age">Age: </label>
<input type="text" name="age" id="age" ref="age" defaultValue={this.props.personToUpdate.age} required />
<br />
<input type="submit" value="Update" />
</form>
</div>
);
}
} // end class
export default UpdateForm;
I'll be soon exploring redux-form because it is evident that forms as inputs and outputs are a wonky business. For now, my little app works.
Yes there is and you are on the right path. The way is to use value instead of defaultValue but you have to read the value from a state and then use the onChange handler to modify the state.
Something like
this.state = {inputText:''}
Then in the input field
<input value={this.state.inputText} onChange={this.handleChange}/>
And the handleChange function will be
handleChange(event){
this.setState({inputText:event.target.value})
}
Remember to bind the handleChange event in the constructor so you can pass it as this.handleChange in the input field's onChange prop.
Something like this
this.handleChange = this.handleChange.bind(this)
https://facebook.github.io/react/docs/forms.html - Here are the official docs regarding it
Also if you want to do it in redux the same sort of logic applies where this will be the input field
<input value={this.props.inputText} onChange={this.props.handleChange}/>
where inputText and handleChange are redux state and action respectively passed to the component via props
For your case I guess it has to be something like where you are 'reading' values from the people array and the action bound to the onChange modifies that value in the people array in the state.
<--EDIT-->
How it can be done for the case in point. Pass the people in the redux state as a people prop to the component. Pass an action changePeople(newPeople) to the component as a prop which takes an argument newPeople and changes the people in the redux state to have the value newPeople. Since people is nested in form you'll have to do some Object.assign etc to modify the state.
Now in the component using the people props populate the checkboxes using a map function. The map function takes a second parameter index so for each checkbox have a function which sets the local state variable currentPerson to the value of the index
this.props.people.map((person,index) =>
return <Checkbox onClick={()=>this.setState(currentPerson:index)}/>
)
So everytime you click on a checkbox the currentPerson points to the corresponding index.
Now the input fields can be
<input value={this.props.people[this.state.currentPerson].name} onChange={this.handleChange.bind(this,'name')}/>
This is for the 'name' input field. It reads from the currentPerson index of the people array which has been passed down as a prop.
This is how the handleChange will be like
handleChange(property,event){
const newPeople = [
...this.props.people.slice(0, this.state.currentPerson),
Object.assign({}, this.props.people[this.state.currentPerson], {
[property]: event.target.value
}),
...this.props.people.slice(this.state.currentPerson + 1)
]
this.props.changePeople(newPeople)
}
The handleChange takes a property (so you don't have to write separate handlers for each input field). The newPeople basically modifies the element at current index this.state.currentPerson in the people passed from props (ES6 syntax being used here. If you have doubts do ask). Then it is dispatched using the changePeople action which was also passed as props.
I have main component as follows :
export default class RegistrationFormStepFour extends React.Component{
constructor(props){
super(props);
this.state = {
terms: false,
emailNotifications: false,
smsNotifications: false,
errors: {}
}
}
handleTerms(event){
event.preventDefault();
this.setState({terms: !this.state.terms});
}
render(){
const language = this.props.currentLanguage;
return (
<div>
<Terms
nameYes="chcTerms"
text={language.termsText}
state={this.state.terms}
onChange={this.handleTerms.bind(this)}
currentLanguage={language}
error={this.state.errors.terms}/>
</div>
);
}
}
And component term is as follows :
import React from 'react';
const Terms = ({nameYes, text, state, onChange, error}) => {
let hasError = error ? "hasError" : "";
return (
<div className="col-lg-12 text-center" style={{marginBottom: 30}}>
<form>
<label className="radio-inline">
<input
type="checkbox"
name={nameYes}
checked={state}
onChange={onChange}
value=""/>
</label>
</form>
<p className={`questionsText ${hasError}`} style={{marginTop: 10}}>{text}</p>
</div>
);
};
export default Terms;
But when I click on the checkbox, nothing happens. If I console log the state in the terms component it show right value. First time is false, when I click on the checkbox than is true, but the checkbox isn't checked.
Any advice?
The event.preventDefault() is causing your problems in the controlled component checkbox.
http://www.matthiaslienau.de/blog/tags/checkbox
Checkboxes (and Radio Buttons): Manually updating state fails for checkboxes (and without having tested it: I think for radio controls as well). Why? The problem one will face is that the checkbox onChange event behaves in a special way since the era of HTML (how could I forget!): You may not toggle the state of a checkbox manually via the .checked property. Nor does React. The onChange (onClick) event is fired after the element state changed internally. This may just be reverted based on the return value of the event handler. See this post for a comprehensive examination of this fact.
const Terms = ({ nameYes, text, state, onChange, error }) => {
let hasError = error ? "hasError" : "";
return (
<div>
<form>
<label className="radio-inline">
<input
type="checkbox"
name={nameYes}
checked={state}
onChange={onChange}
/>
</label>
</form>
<p className={`questionsText ${hasError}`}>{text}</p>
</div>
);
};
class RegistrationFormStepFour extends React.Component {
constructor(props) {
super(props);
this.state = {
terms: false,
emailNotifications: false,
smsNotifications: false,
errors: {}
}
}
handleTerms(event) {
this.setState({ terms: event.target.checked });
}
render() {
const language = { termsText: 'Some Language' };
return (
<div>
<Terms
nameYes="chcTerms"
text={language.termsText}
state={this.state.terms}
onChange={this.handleTerms.bind(this)}
currentLanguage={language}
error={this.state.errors.terms}/>
{JSON.stringify(this.state)}
</div>
);
}
}
ReactDOM.render(<RegistrationFormStepFour />, document.getElementById('app'))
<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="app"></div>
I think you should remove value attr from input tag. try this :
<input
type="checkbox"
name={nameYes}
checked={state}
onChange={onChange}
/>