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}
/>
Related
Like below simple react component code:
class Test extends React.Component {
constructor(props){
super(props)
this.c1 = this.c1.bind(this);
this.c2 = this.c2.bind(this);
this.state = {
a:false,
b:false
}
}
c1(e) {
this.setState({a:true, b:false})
}
c2(e) {
this.setState({a:false, b:true})
}
render() {
return (
<div>
<div>
<input name="n" type="radio" onChange={this.c1} />
<input name="n" type="radio" onChange={this.c2} />
</div>
<div>
{
this.state.a && "aa"
}
{
this.state.b && "bb"
}
</div>
</div>
)
}
}
The code simply switch displaying 'aa' or 'bb' while click the radio button. But if I add a new radio button showing 'cc' to achieve the same function. I should:
Add new state like 'c'
Add new input HTML and implement its callback
setState 'c' in this callback
All of those is ok, But I have to change the 'c1','c2' function that make my code coupling like:
class Test extends React.Component {
constructor(props){
super(props)
this.c1 = this.c1.bind(this);
this.c2 = this.c2.bind(this);
this.c3 = this.c3.bind(this);
this.state = {
a:false,
b:false,
c:false,
}
}
c1(e) {
this.setState({a:true, b:false, c:false}) // add 'c:false' which coupled
}
c2(e) {
this.setState({a:false, b:true, c:false}) // add 'c:false' which coupled
}
c3(e) {
this.setState({a:false, b:false, c:true})
}
render() {
return (
<div>
<div>
<input name="n" type="radio" onChange={this.c1} />
<input name="n" type="radio" onChange={this.c2} />
<input name="n" type="radio" onChange={this.c3} />
</div>
<div>
{
this.state.a && "aa"
}
{
this.state.b && "bb"
}
{
this.state.c && "cc"
}
</div>
</div>
)
}
}
I think this situation is very common in React. So I want decoupling my code no matter how many radio buttons I add. I do not need to change the code just add code to satisfy the 'Open Closed Principle'.
Do you have any recommendation? Thanks in advance.
I think you can do like this
class Test extends React.Component {
constructor(props){
super(props)
this.change = this.change.bind(this);
this.state = {
a:false,
b:false,
c:false,
}
}
change = statename => e => {
this.setdefault();
this.setState({
[statename]: true
});
};
setdefault(){
this.setState({
a:false,
b:false,
c:false,
});
}
render() {
return (
<div>
<div>
<input name="n" type="radio" onChange={this.change("a")} />
<input name="n" type="radio" onChange={this.change("b")} />
<input name="n" type="radio" onChange={this.change("c")} />
</div>
<div>
{
this.state.a && "aa"
}
{
this.state.b && "bb"
}
{
this.state.c && "cc"
}
</div>
</div>
)
}
}
The way that you are storing the state as keyed boolean values doesn't feel right to me given that the properties are codependent rather than independent.
Right now, you have four options for state:
{ a:false, b:false, c:false } // initial state from constructor
{ a:true, b:false, c:false } // from c1
{ a:false, b:true, c:false } // from c2
{ a:false, b:false, c:true } // from c3
Based on your setState callbacks, no more than 1 property can be true at a time.
If each property was independent, you would have 8 options (2^3) and this setup would make sense.
As it is, I recommend that you instead just store which of the values is the one that is true. You can check if an individual option is true by seeing if it matches the stored selected value.
You want your Component to work no matter how many buttons you have, so let's pass the button options as props. In terms of design patterns, this is "dependency injection". We can create a SelectOne that doesn't need to know what its options are. Here I am just expecting the options to be a string like "aa" or "bb" but you can refactor this to take an object with properties label and value.
We want to loop through the array of options and render each one. Sometimes you see this extracted into a method of the component, like this.renderOption(i), but you can also do the mapping inline inside your render().
You aren't actually using the event e in your callbacks. You could use the event to get e.target.value, assuming that you are setting the value property on your input (see MDN docs regarding the HTML markup). You could also pass the value to the callback by creating an anonymous arrow function for onChange which calls it with the correct value. I'm doing that.
Here it as a class component, since that's what you were using previously.
class SelectOne extends React.Component {
constructor(props) {
super(props);
this.state = {
selected: undefined // this.state.selected will initially be undefined
}
}
render() {
return (
<div>
<div>
{this.props.options.map((optionName) => (
<label htmlFor={optionName}>
{optionName}
<input
type="radio"
id={optionName}
name="n"
value={optionName}
checked={this.state.selected === optionName}
onChange={() => this.setState({selected: optionName})}
/>
</label>
))}
</div>
{this.state.selected !== undefined && (
<h2>Selected: {this.state.selected}</h2>
)}
</div>
);
}
}
Here it is as a function Component. This is the newer syntax, so if you are just learning then I recommend learning with function components and hooks. Destructring the props makes it really easy to accept optional props and set their default value. I'm allowing the name property of the input to be passed in here, but defaulting to your "n" if not provided.
const SelectOne = ({options, name = "n"}) => {
// will be either a string or undefined
const [selected, setSelected] = React.useState(undefined);
return (
<div>
<div>
{options.map((optionName) => (
<label htmlFor={optionName}>
{optionName}
<input
type="radio"
id={optionName}
name={name} // from props
value={optionName}
checked={selected === optionName}
onChange={() => setSelected(optionName)}
/>
</label>
))}
</div>
{selected !== undefined && (
<h2>Selected: {selected}</h2>
)}
</div>
);
}
Either way, you would call it like this:
<SelectOne options={["aa", "bb", "cc"]} />
You could also create a specific components for a given set of options, which you can now call with no props.
const SelectABC = () => <SelectOne options={["aa", "bb", "cc"]} />;
<SelectABC/>
I have two files which work together to render things. The first is App.js, which first renders Form.js. The form will then collect information, which on submission, changes the Form state and calls a function from App.js. This function is called "createProject." Calling "createProject" in Form.js "handleSubmit" makes the page auto-refresh. However, if I remove "createProject" from handleSubmit, the page does not auto-refresh. Here are the two files.
import React, { Component } from "react";
import Project from "./components/Project.js"
import Form from "./Form.js";
class App extends Component {
constructor(props) {
super(props);
this.state = {
projectList: [],
myProjects: [],
userList: [],
submitted: false
};
this.createProject = this.createProject.bind(this);
}
createProject(title, desc, langs, len, exp) {
this.setState({
projectList: this.state.projectList.push([
{
title : title,
description : desc,
language : langs,
length : len,
experience : exp
}
]),
submitted : true
});
}
deleteProject(title) {
const projects = this.state.projectList.filter(
p => p.title !== title
);
this.setState({projects});
}
render() {
let info;
if (this.state.submitted) {
info = (
<div>
<p>cccc</p>
</div>
);
} else {
info = (
<br/>
);
}
return(
<div>
<Form/>
{info}
{this.state.projectList.map((params) =>
<Project {...params}/>)}
</div>
);
}
}
export default App;
import React from "react";
import createProject from "./App.js"
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
description: "",
language: "",
length: 0,
experience: "",
submitted: false
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleSubmit(event) {
this.setState({
submitted: true
})
createProject(
this.state.title,
this.state.description,
this.state.language,
this.state.length,
this.state.experience
)
event.preventDefault();
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
let info;
if (this.state.submitted) {
info = (
<div>
<h1>{this.state.title}</h1>
<p>{this.state.description}</p>
<p>{this.state.language}</p>
<p>{this.state.length}</p>
<p>{this.state.experience}</p>
</div>
);
} else {
info = <br/>;
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
Title:
<input
name="title"
type="textbox"
checked={this.state.title}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Description:
<input
name="description"
type="textbox"
checked={this.state.description}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Language:
<input
name="language"
type="textbox"
checked={this.state.language}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Length:
<input
name="length"
type="number"
checked={this.state.length}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Experience:
<input
name="experience"
type="textbox"
checked={this.state.experience}
onChange={this.handleInputChange} />
</label>
<br />
<input type="submit" value="Submit" />
</form>
{info}
</div>
);
}
}
export default Form;
I've also tried adding "new" to the "createProject" in handleSubmit, and while that does stop the auto-refresh, it will not call the createProject function. (Or maybe it does, but none of the code in the createProject function seems to be run.) Can anyone help with preventing this auto refresh while also allowing App's createProject function to run properly?
The page auto refreshes because execution never gets to your event.PreventDefault() line. This is due to an error encountered when react tries to evaluate createProject. To fix this, correct handleSubmit like so.
handleSubmit(event) {
event.preventDefault(); // moved up in execution.
this.setState({
submitted: true
})
createProject(
this.state.title,
this.state.description,
this.state.language,
this.state.length,
this.state.experience
)
}
Notice that moving event.PreventDefault() to the top of your handleSubmit(event) function just before this.setState line prevents default form behaviour on submit.
You however get an error because App.js doesn't export a function called createProject. To maintain the createProject within App instance, you need to pass it as a prop which you can then reference as this.props.createProject.
See this answer on how to do call a Parent method in ReactJS.
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 !
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 have a dynamically created from in React, and I'd like to be able to submit the values of all the input fields, but I can't add seperate on change handlers for each input elment, as they are created dynamically:
extract from the form js:
const FormElements = ({formFields}) => ( <div> {
formFields.map(formField => ( <FormElement name={formField.name} type={formField.fieldType} />)
)} </div> );
console.log(formFields);
return (
<div class="col-md-12">
<div class="panel panel-primary">
<div class="panel-heading">
<h4 class="panel-title">
{title} - {id}
</h4>
</div>
<div class="panel-body">
<form >
<FormElements formFields={formFields} />
<a
class="btn btn-primary"
onClick={this.handleSubmitButton}//what do I do with this function?
href="#">Submit</a>
</form>
</div>
</div>
</div>
);
form element js:
export default class FormElement extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div class="form-group">
<label for="{this.props.name}">{this.props.name}</label>
<input type="{this.props.type}}" class="form-control" id="{this.props.name}" placeholder="blah blah" />
</div>
);
}
}
Since they are controlled inputs there is not a react way to that, and even if there is I would not recommend it, React is all about declarative code.
There are two ways to solve this, one is to use make a property onChange on your FormElement and pass a function with ids, something like this:
<FormElements onChange={(key, value) => this.setState({ [key]: value })
The other way is to send give all the not defined props to the input:
export default class FormElement extends React.Component {
constructor(props) {
super(props);
}
render() {
const { name, type, ...other } = this.props
return (
<div class="form-group">
<label for="{name}">{name}</label>
<input type="{type}}" class="form-control" {...other} id="{this.props.name}" placeholder="blah blah" />
</div>
);
}
}
(the { [key]: value } and {...other} is ES6)
I actually managed this in a quite convoluted, and probably not recommended way, but it works! I've also never seen this done elsewhere...probably for good reason:
Form element:
export default class FormElement extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
}
onChange(e) {
this.props.handleChange(e.target.id, e.target.value);
}
render() {
return (
<div class="form-group">
<label for={this.props.id}>{this.props.name}</label>
<input type="{this.props.type}}" class="form-control" id={this.props.id} value={this.props.value} placeholder="blah blah" onChange={this.onChange}/>
</div>
);
}
}
form:
handleFormElementChange(id, value) {
console.log("changing: " + id + " = "+ value);
var frm = this.state.formData;
var index=-1;
for(var i=0;i<frm.length;i++) {
if(frm[i].id==id) {
index=i;
break;
}
}
frm[index].value = value;
this.setState({formData: frm});
}
const FormElements = ({formFields}) => ( <div> {
formFields.map(formField => ( <FormElement name={formField.name} key={formField.id} value={formField.value} id={formField.id} type={formField.fieldType} handleChange={this.handleFormElementChange.bind(this)}/>)
)} </div> );
What's happening is the actual full form data is being updated in the form component, and each time a change is made to one of the form elements, it passes it back to the parent form, update's the form's sate and then re-renders the whole form.
The complication here was actually finding the correct form element in the overall form status, by searching through the array for the key, and updating the value.
While I see this working with small forms, I can see how it would start to significantly slow down rendering on large form applications.