ReactJS: Why are the constructor called when setState changes the state - javascript

I'm a newbie to ReactJS and I have made an app where you can submit a name and email. The name and mail should be displayed in a list at the bottom of the page. It is displayed for a short period, but then the constructor gets called and clears the state and the list.
Why is the constructor called after the state change? I thought the constructor only runs once and then the render-method runs after setState() changes the state.
class App extends React.Component {
constructor(props) {
super(props);
console.log("App constructor");
this.state = {
signedUpPeople: []
};
this.signUp = this.signUp.bind(this);
}
signUp(person) {
this.setState({
signedUpPeople: this.state.signedUpPeople.concat(person)
});
}
render() {
return (
<div>
<SignUpForm signUp={this.signUp} />
<SignedUpList list={this.state.signedUpPeople} />
</div>
);
}
}
class SignUpForm extends React.Component {
constructor(props) {
super(props);
console.log("SignUpForm constructor");
this.state = {
name: "",
email: ""
};
this.changeValue = this.changeValue.bind(this);
this.onSubmitForm = this.onSubmitForm.bind(this);
}
changeValue(event) {
const value = event.target.value;
const name = event.target.name;
this.setState({
name: value
});
}
onSubmitForm() {
this.props.signUp(this.state);
this.setState({
name: "",
email: ""
});
}
render() {
console.log("SignUpForm render");
return (
<div>
<h2>Sign up</h2>
<form onSubmit={this.onSubmitForm}>
<label htmlFor="name">Name:</label>
<input id="name" name="name" onChange={this.changeValue} />
<br />
<label htmlFor="email">E-mail:</label>
<input id="email" name="name" onChange={this.changeValue} />
<input type="submit" value="Sign up" />
</form>
</div>
);
}
}
class SignedUpList extends React.Component {
render() {
console.log("SignedUpList render");
return (
<div>
<h2>Already signed up</h2>
<ul>
{this.props.list.map(({ name, email }, index) => (
<li key={index}>
{name}, {email}
</li>
))}
</ul>
</div>
);
}
}
ReactDOM.render(<App />, window.document.getElementById("app"));
See CodePen example

The default behavior of a form with input of type submit is to post back to the server.
<input> elements of type "submit" are rendered as buttons. When the
click event occurs (typically because the user clicked the button),
the user agent attempts to submit the form to the server.
You can pass the event object of the submit handler and use the event.preventDefault method to prevent the form from posting back:
onSubmitForm(e) {
e.preventDefault();
this.props.signUp(this.state);
this.setState({
name: "",
email: ""
});
}
Here is a running snippet of your code:
class App extends React.Component {
constructor(props) {
super(props);
console.log("App constructor");
this.state = {
signedUpPeople: []
};
this.signUp = this.signUp.bind(this);
}
signUp(person) {
this.setState({
signedUpPeople: this.state.signedUpPeople.concat(person)
});
}
render() {
return (
<div>
<SignUpForm signUp={this.signUp} />
<SignedUpList list={this.state.signedUpPeople} />
</div>
);
}
}
class SignUpForm extends React.Component {
constructor(props) {
super(props);
console.log("SignUpForm constructor");
this.state = {
name: "",
email: ""
};
this.changeValue = this.changeValue.bind(this);
this.onSubmitForm = this.onSubmitForm.bind(this);
}
changeValue(event) {
const value = event.target.value;
const name = event.target.name;
this.setState({
name: value
});
}
onSubmitForm(e) {
e.preventDefault();
this.props.signUp(this.state);
this.setState({
name: "",
email: ""
});
}
render() {
//console.log('SignUpForm render');
return (
<div>
<h2>Sign up</h2>
<form onSubmit={this.onSubmitForm}>
<label htmlFor="name">Name:</label>
<input id="name" name="name" onChange={this.changeValue} />
<br />
<label htmlFor="email">E-mail:</label>
<input id="email" name="name" onChange={this.changeValue} />
<input type="submit" value="Sign up" />
</form>
</div>
);
}
}
class SignedUpList extends React.Component {
render() {
//console.log('SignedUpList render');
return (
<div>
<h2>Already signed up</h2>
<ul>
{this.props.list.map(({ name, email }, index) => (
<li key={index}>
{name}, {email}
</li>
))}
</ul>
</div>
);
}
}
ReactDOM.render(<App />, window.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>

onSubmitForm(e) {
e.preventDefault(); // prevent the form from refreshing the page
this.props.signUp(this.state);
this.setState({
name: "",
email: ""
});
}
It is working with your codepen link :)
Have a deep look at this :
React - Preventing Form Submission
or
https://medium.com/#ericclemmons/react-event-preventdefault-78c28c950e46

Thank you all for your help! :-)
e.preventDefault();
Did fix the problem. Thanks!

Related

Can't edit value of input element in React

I have a problem with my React project, I have an input element like below but I can't edit the text of the input. I just can edit the input text only when I delete the value attribute, but I want a default value for it.
<div className="form-group">
<label>Email:</label>
<input
className="form-input"
type="text"
value="l.h.thuong181#gmail.com"
name="email">
</input>
</div>
If you've an uncontrolled component, you might want to use the defaultValue property instead of value (See this reference)
You can use useRef or defaultValue
import React, { useRef, useEffect } from "react";
const input = useRef();
useEffect(() => {
input.current.value = "l.h.thuong181#gmail.com";
}, []);
<input ref={input} className="form-input" type="text" name="email" />`
Here is a sample code how to use inputs in react
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
Set the default value inside the state object and attach a change handler to the input to capture the changes:
Sample codesandbox: https://codesandbox.io/s/react-basic-class-component-22fl7
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: 'l.h.thuong181#gmail.com'
};
}
handleChange = event => {
this.setState({ inputValue: event.target.value }, () =>
console.log(this.state.inputValue)
);
};
render() {
return (
<div className="form-group">
<label>Email:</label>
<input
className="form-input"
type="text"
value={this.state.inputValue}
onChange={this.handleChange}
name="email">
</input>
</div>
);
}
}

My component (app) not rendering input from a child component(InputForm)

I have a parent component App rendering a list of new restaurants from a child component InputForm.
I have a addedrestaurant state inside App and a handler to save the input from a callback props passed to the child component InputForm.
I have console.log the restaurant attribute in the InputForm component, it is always empty even if I have user input in the form and have handlers for change in the component.
class App extends React.Component {
constructor(props) {
super(props)
this.handlerestaurantclick = this.handlerestaurantclick.bind(this)
this.saveRating = this.saveRating.bind(this)
this.saveRestaurant = this.saveRestaurant.bind(this)
this.state = {
restaurants: restaurants,
addedrestaurants: [],
showcomponent: false,
restaurantClicked: -1,
}
}
saveRestaurant(restaurant) {
this.setState(
(prevState) => {
let resto = prevState.addedrestaurants;
resto.push(restaurant);
return { resto };
})
console.log("after:" + this.state.addedrestaurants)
}
render() {
return <Inputform onSave={this.saveRestaurant} />
}
}
class Inputform extends React.Component {
constructor(props) {
super(props);
this.handlechange = this.handlechange.bind(this);
this.handleSave = this.handleSave.bind(this);
this.state = {
restaurant: Object.assign({}, Init_value),
error: {}
}
}
handlechange(e) {
const name = e.target.name;
const value = e.target.value;
this.setState((prevState) => {
prevState.restaurant[name] = value;
return { restaurant prevState.restaurant };
})
}
handleSave = (e) => {
this.props.onSave(this.state.restaurant);
this.setState({
restaurant: Object.assign({}, Init_value),
error: {}
})
e.preventDefault()
}
render() {
return (
<form className="inputform">
<div>
<h4>
Add new Restaurant
<FontAwesomeIcon icon={faBars} />
</h4>
</div>
<div className="inputcontainer ">
<input className="InputItem restaurant-name" type="text" name="restaurantName" placeholder="Name" value={this.state.restaurant.restaurantName} onChange={this.handlechange} />
</div>
<div className="inputcontainer adress">
<textarea className="InputItem" name="address" rows="4" cols="18" placeholder="Adress" value={this.state.restaurant.address} onChange={this.handlechange}>
</textarea>
</div>
<div className="inputcontainer rating">
Rating
<select >
<option> </option>
<option >0 </option>
<option >1</option>
<option>2</option>
<option>3</option>
<option>4</option><option>5</option>
</select>
</div>
<div className="inputcontainer">
<textarea className="InputItem" rows="4" cols="18" placeholder="comment"> </textarea>
</div>
<input className="inputitem submitbutton" type="submit" value="submit" onClick={this.handleSave} />
</form>
)
}
}
You made couple of mistakes. I have mention it in the below code as #cluemediator Fixed.
import React, { Component } from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import "./style.css";
class App extends React.Component {
constructor(props) {
super(props)
// #cluemediator Fixed: Remove both line because method is not exist.
// this.handlerestaurantclick = this.handlerestaurantclick.bind(this)
// this.saveRating = this.saveRating.bind(this)
this.saveRestaurant = this.saveRestaurant.bind(this)
this.state = {
restaurants: [], // #cluemediator Fixed: remove "restaurants" because "restaurants" is not defined.
addedrestaurants: [],
showcomponent: false,
restaurantClicked: -1,
}
}
saveRestaurant(restaurant) {
this.setState(
(prevState) => {
let resto = prevState.addedrestaurants; resto.push(restaurant); return { resto };
})
console.log("after:" + this.state.addedrestaurants)
}
render() {
return <Inputform onSave={this.saveRestaurant} />
}
}
class Inputform extends React.Component {
constructor(props) {
super(props);
this.handlechange = this.handlechange.bind(this);
this.handleSave = this.handleSave.bind(this);
const Init_value = {}; // #cluemediator Fixed: define variable because it's used at below
this.state = {
restaurant: Object.assign({}, Init_value),
error: {}
}
}
handlechange(e) {
const name = e.target.name;
const value = e.target.value;
this.setState((prevState) => {
prevState.restaurant[name] = value;
return { restaurant: prevState.restaurant }; // #cluemediator Fixed: Add colon symbol
})
}
handleSave = (e) => {
this.props.onSave(this.state.restaurant);
this.setState({
restaurant: Object.assign({}, Init_value),
error: {}
})
e.preventDefault()
}
render() {
return (
<form className="inputform">
<div>
<h4>
Add new Restaurant
<FontAwesomeIcon icon={faBars} />
</h4>
</div>
<div className="inputcontainer ">
<input className="InputItem restaurant-name" type="text" name="restaurantName" placeholder="Name" value={this.state.restaurant.restaurantName} onChange={this.handlechange} />
</div>
<div className="inputcontainer adress">
<textarea className="InputItem" name="address" rows="4" cols="18" placeholder="Adress" value={this.state.restaurant.address} onChange={this.handlechange}>
</textarea>
</div>
<div className="inputcontainer rating">
Rating
<select >
<option> </option>
<option >0 </option>
<option >1</option>
<option>2</option>
<option>3</option>
<option>4</option><option>5</option>
</select>
</div>
<div className="inputcontainer">
<textarea className="InputItem" rows="4" cols="18" placeholder="comment"> </textarea>
</div>
<input className="inputitem submitbutton" type="submit" value="submit" onClick={this.handleSave} />
</form>
)
}
}
render(<App />, document.getElementById("root"));
Hope this will work for you!

Not able to type in input field

Hi I write a small component
export default class TextInput extends React.Component {
constructor(props) {
super(props);
this.onKeyPress = this.onKeyPress.bind(this);
this.state = { tags: [], value: "" };
}
onKeyPress(e) {
if (e.key === "Enter") {
this.setState({ tags: [...this.state.tags, e.target.value], value: "" });
}
}
render() {
return (
<div>
<div styleName="fieldset width-95">
<label>Tags</label>
<div>
<div>
{this.state.tags.map(tag => (
<span>{tag}</span>
))}
</div>
<input
type="text"
onKeyPress={this.onKeyPress}
value={this.state.value}
/>
</div>
</div>
</div>
);
}
}
after writing 1st time when I enter.it creates a tag but after that, I can't type anything in the input field. can anyone please explain how to enable typing so that I will be able to create another tag.
You also need an onChange handler as well to update state on user-input. Otherwise this.state.value will never update.
class TextInput extends React.Component {
constructor(props) {
super(props);
this.onKeyPress = this.onKeyPress.bind(this);
this.state = { tags: [], value: "" };
}
onKeyPress(e) {
if (e.key === "Enter") {
this.setState({ tags: [...this.state.tags, e.target.value], value: "" });
}
}
handleOnChange = e => {
this.setState({
value: e.target.value
});
};
render() {
return (
<div>
<div styleName="fieldset width-95">
<label>Tags</label>
<div>
<div>
{this.state.tags.map(tag => (
<span>{tag}</span>
))}
</div>
<input
type="text"
onChange={this.handleOnChange}
onKeyPress={this.onKeyPress}
value={this.state.value}
/>
</div>
</div>
</div>
);
}
}
See working sandbox: https://codesandbox.io/s/serene-https-t02h2
The problem is that you forgot to add an onClick event for the input, this will handle when you type something in it (You only need to update the state every time the onChange event is fire), like this (I've also made an update to a more ES6 syntax):
import React, { Component } from 'react';
export default class TextInput extends Component {
state = { tags: [], value: "" };
onKeyPress = e => {
if (e.key === "Enter") {
this.setState({ tags: [...this.state.tags, e.target.value], value: "" });
}
};
onClick = e => {
if (e && e.target && e.target.value) {
this.setState({ value: e.target.value });
}
};
render() {
const { tags, value } = this.state;
return (
<div>
<div styleName="fieldset width-95">
<label>Tags</label>
<div>
<div>
{tags.map(tag => (
<span>{tag}</span>
))}
</div>
<input
type="text"
onChange={this.onClick}
onKeyPress={this.onKeyPress}
value={value}
/>
</div>
</div>
</div>
);
}
}
You can try below code.And you remove value which you are set on state.And use onChange method.
import React, { Component } from 'react';
export default class TextInput extends React.Component {
constructor(props) {
super(props);
this.onKeyPress = this.onKeyPress.bind(this);
this.state = { tags: [] };
}
onKeyPress(e) {
if (e.key === "Enter") {
this.setState({ tags: [...this.state.tags, e.target.value], value:
e.target.value });
console.log("tag:",this.state.tags)
}
}
handleInputChange = (event) => {
this.setState({ [event.target.name]: event.target.value });
};
render() {
return (
<div>
<div styleName="fieldset width-95">
<label>Tags</label>
<div>
<div>
{this.state.tags.map(tag => (
<span >{tag}</span>
))}
</div>
<input
type="text"
onKeyPress={this.onKeyPress}
name="demo"
value={this.state.value}
onChange={this.handleInputChange}
/>
</div>
</div>
</div>
);
}
}

react state is not updated/passed to child component when child component is passed as a property

I am trying to create dynamic form, so I pass some jsx elements to a child component as a property. Even though the state is being updated, the updated state is not passed to the element. Below is the code:
This is the child component which maps over the passed controls and outputs them.
class Form extends Component {
render() {
return (
<div>
{this.props.controls.map((c, i) => {
return <React.Fragment key={i}>{c}</React.Fragment>;
})}
</div>
);
}
}
This is the App that calls the child component:
class App extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: ''
};
this.controls = [
<input
type="text"
onChange={this.onChangeUsername}
value={this.state.username}
/>
];
}
componentDidUpdate() {
console.log(this.state);
}
render() {
return (
<div className="App">
<div className="app__group">
<h1>This is not working</h1>
<Form controls={this.controls} />
</div>
<div className="app__group">
<h1>This is working</h1>
<input
type="text"
onChange={this.onChangePassword}
value={this.state.password}
/>
</div>
</div>
);
}
onChangeUsername = e => {
console.log('onChangeUsername', e.target.value);
this.setState({ username: e.target.value });
};
onChangePassword = e => {
console.log('onChangePassword');
this.setState({ password: e.target.value });
};
}
As an example of the unexpected behaviour, when an input passed as a property to the child component, I cannot type in the input. The state gets updated but it's is not passed to the child, thus the text does not show.
On the other hand, a standard input element works, I can type and see the output.
What am I doing wrong?
the problem is that you are trying to make something "fixed" as something dynamic. lets go with a more functional approach and it will refresh each one of the inputs like if they are dynamic.
class App extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: ''
};
}
componentDidUpdate() {
console.log(this.state);
}
render() {
return (
<div className="App">
<div className="app__group">
<h1>This is not working</h1>
<Form controls={this.controls()} />
</div>
</div>
);
}
controls = () => {
return [<input
type="text"
onChange={this.onChangeUsername}
value={this.state.username}
/>]
}
onChangeUsername = e => {
console.log('onChangeUsername', e.target.value);
this.setState({ username: e.target.value });
};
onChangePassword = e => {
console.log('onChangePassword');
this.setState({ password: e.target.value });
};
}

enable button to submit only when all the fields are filled

I have a form which has 3 field. First two fields are email_from and email_subject and the last field is an editor. I have used draftjs for editor. If there is no editor i could enable and disable the submit button but due to the inclusion of the editor i did not know how can i enable the button only when all the fields are filled with no error.
Here is my code.
AdminEditor is the component which has draftjs editor
class EmailTemplate extends React.Component {
state = {
emailSubject: '',
emailFrom: ''
};
handleChange = event =>
this.setState({ [event.target.name]: event.target.value });
handleSubmit = event => {
event.preventDefault();
};
render() {
const { emailSubject, emailFrom } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<FieldGroup
id="formControlsText"
name="emailSubject"
type="text"
label="Email Subject"
placeholder="Enter Email Form"
onChange={this.handleChange}
validationState={emailSubject ? 'success' : 'error'}
required
/>
<FieldGroup
id="formControlsText"
name="emailFrom"
type="email"
label="Email From"
placeholder="Enter Email From"
onChange={this.handleChange}
validationState={emailFrom ? 'success' : 'error'}
required
/>
<AdminEditor />
<Button type="submit">
Submit
</Button>
</form>
);
}
}
export default EmailTemplate;
class AdminEditor extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
isEmpty: true
};
}
onEditorStateChange = editorState => {
this.setState({
editorState
});
};
onEditorChange = editorContent => {
this.setState({
editorContent
});
};
handleChange = event =>
this.state.editorState.getCurrentContent().hasText()
? this.setState({ isEmpty: false })
: this.setState({ isEmpty: true });
render() {
const { editorState } = this.state;
return (
<div>
<Editor
editorState={this.state.editorState}
initialContentState={rawContentState}
toolbarClassName="home-toolbar"
wrapperClassName="home-wrapper"
editorClassName="home-editor"
onEditorStateChange={this.onEditorStateChange}
toolbar={{
textAlign: { inDropdown: true },
}}
onContentStateChange={this.onEditorChange}
placeholder="write text here..."
onChange={this.handleChange}
/>
</div>
);
}
}
export default AdminEditor;
Can anyone please help me?
There are many ways to do this, mostly based around passing some callbacks down into the controlled component which updates the state in the parent.
A simple way would be to pass a handler that sets a flag if the editor is empty or not:
class AdminEditor extends React.PureComponent {
...
handleChange = event =>
this.props.setEditorIsEmpty(
this.state.editorState.getCurrentContent().hasText()
);
Then in EmailTemplate:
setEditorIsEmpty(editorIsEmpty) {
this.setState({
editorIsEmpty
});
}
render() {
const { emailSubject, emailFrom } = this.state;
return (
<form onSubmit={this.handleSubmit}>
...
<AdminEditor
setEditorIsEmpty={this.setEditorIsEmpty}
/>
<Button
type="submit"
disabled={!emailSubject || !emailFrom || editorIsEmpty}>
Submit
</Button>
</form>
);
}

Categories

Resources