I have a form that is supposed to update the initial state, I've followed many tutorials and my code looks the same as them but for some reason it doesn't update the state.
import React, { Component } from 'react';
class Create extends Component{
constructor(props){
super(props);
this.state = {
title: "",
body: "",
error: ""
}
}
onTitleChange(e) {
const title = e.target.value;
this.setState({title})
}
onBodyChange(e) {
const body = e.target.value;
this.setState({body})
}
onSubmit(e) {
e.preventDefault();
if(!this.state.title || !this.state.body){
this.setState(() => ({ error: "Please fill in all gaps"}))
} else {
this.setState(() => ({ error: "" }))
// Send info to the main page
alert(this.state.title);
}
}
render(){
return(
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit = {this.onSubmit}>
<label>Put a title for your note</label>
<input
placeholder="Title"
type="text"
value={this.state.title}
autoFocus
onChange= {this.onTitleChange}
/>
<label>Write your note</label>
<textarea
placeholder="Note"
value={this.state.body}
autoFocus
onChange = {this.onBodyChange}
/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default Create;
When I check the current state in the React developer tools it shows that the state remains the same and I don't know why because there are not errors in the log.
I'm working with webpack, babel and react.
////////////////////
EDITED
////////////////////
I edited my code following the suggestions you guys gave me but still it doesn't work. An alert is supposed to appear when submitted the form but that doesn't get fired either, so I believe that non of the functions are getting fired.
This is my edited code:
import React, { Component } from 'react';
class Create extends Component{
constructor(props){
super(props);
this.onTitleChange = this.onTitleChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
title: "",
body: "",
error: ""
}
}
onTitleChange(e) {
const title = e.target.value;
this.setState((prevState) => {
return {
...prevState,
title
};
});
}
onBodyChange(e) {
const body = e.target.value;
this.setState((prevState) => {
return {
...prevState,
body
};
});
}
onSubmit(e) {
e.preventDefault();
if(!this.state.title || !this.state.body){
this.setState((prevState) => {
return {
...prevState,
error: "Please fill in all gaps"
};
});
} else {
this.setState((prevState) => {
return {
...prevState,
error: ""
};
});
// Send info to the main page
alert(this.state.title);
}
}
render(){
return(
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit = {this.onSubmit}>
<label>Put a title for your note</label>
<input
placeholder="Title"
type="text"
value={this.state.title}
autoFocus
onChange= {this.onTitleChange}
/>
<label>Write your note</label>
<textarea
placeholder="Note"
value={this.state.body}
autoFocus
onChange = {this.onBodyChange}
/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default Create;
You should try binding the event handlers in the constructor, because it seems like this within those event handling functions could be undefined. The React documentation outlines why the binding is necessary, and here's another useful page on handling forms in React.
constructor(props) {
super(props);
...
this.onTitleChange = this.onTitleChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this);
this.onSubmitChange = this.onSubmitChange.bind(this);
}
An alternative to binding in the constructor would also be to use arrow functions for your event handlers which implicitly bind this.
class Create extends Component {
...
onTitleChange = () => { ... }
onBodyChange = () => { ... }
onSubmitChange = () => { ... }
}
EDIT: Can't comment on your post since my reputation is too low, but it seems like there's a typo in the changes you just made.
this.onSubmitC = this.onSubmit.bind(this) should be changed to
this.onSubmit = this.onSubmit.bind(this)
React's setState() accepts an object, not a function. So change your onSubmit() to this:
if(!this.state.title || !this.state.body){
this.setState({ error: "Please fill in all gaps"})
} else {
this.setState({ error: "" })
// Send info to the main page
alert(this.state.title);
}
It better to use the previous state and only update the required (input value) values.
in your case you are replacing the existing state (whole object) just with the title and the same goes for body onBodyChange
onTitleChange () {
const title = e.target.value;
this.setState((prevState) => {
return {
...prevState,
title
};
});
};
<
Related
I am trying to track a form in this.state with event handlers on the inputs, but for some reason this.state is being reset back to its default state when the event handler tries to update it. This is an example of what the Component looks like.
class ExampleReport extends React.Component {
constructor(props) {
super(props)
this.state = {
reportDetails: null,
report: this.props.report,
form: {}
}
this.textInputHandler = this.textInputHandler.bind(this)
}
textInputHandler(e) {
var reportForm = this.state.form;
var target = e.target;
var name = target.className;
var value = target.value;
reportForm[name] = value;
this.setState({ form: reportForm })
}
render(){
return(
<form>
<input className="example" type="text" onChange={(e) => {this.textInputHandler(e)}} />
</form>
)
}
}
Before textInputHandler is called, this.state has an object and array stored in it, but once setState is called they are reset back to the default in the constructor. On subsequent updates to the text input this.state.form persists but everything else is reset. How can I prevent this from happening?
UPDATE
After trying some of the solutions suggested below I went back and logged this.state at just about every possible point and found that it is reset even before setState() is being called in the input handler.
You forget about input value and update state by ref (make mutation).
class ExampleReport extends React.Component {
constructor(props) {
super(props)
this.state = {
reportDetails: null,
report: this.props.report,
form: { example: '', },
}
this.textInputHandler = this.textInputHandler.bind(this)
}
textInputHandler(e) {
const { target: { name, value } } = e;
this.setState(prevState => ({
...prevState,
form: {
...prevState.form,
[name]: value,
},
}));
}
render() {
const { form: { example } } = this.state;
return (
<form>
<input
className="example"
type="text"
name="example"
value={example}
onChange={this.textInputHandler}
/>
</form>
)
}
}
Once way to solve this would be like below, spreading reportForm in a new object
textInputHandler(e) {
var reportForm = this.state.form;
var target = e.target;
var name = target.className;
var value = target.value;
reportForm[name] = value;
this.setState({ form: { ...reportForm } }) // <----------------------
}
However you may want to use more declarative solution as provided by Kirill but without unnecessary state changes
textInputHandler(e) {
const { className: name, value } = e.target;
this.setState(prevState => { form: { ...prevState.form, [name]: value } }) // <----------------------
}
try changing your onChange to this
onChange={this.textInputHandler}
like how they have it in the react documentation
EX)
<input type="text" value={this.state.value} onChange={this.handleChange} />
then do the spreading stuff
don't add, e.preventDefault because that only works for submitting
Look at Krill answer he has everything you need on there
compare it to yours from the top, you have a lot of stuff missing
I'm working on this component and I want to validate its fields before every render. If they are not valid I want to disable a button in the parent.
Here is what I've got thus far:
export default function Input(props) {
const [inputs] = useState(props.inputs);
const didChangeRef = useRef([]);
useEffect(() => {
if (!_.isEqual(didMountRef.current, props.inputs)) {
validateInput(input);
didChangeRef.current = props.inputs;
}
});
const validateInput = input => {
const errors = useValidation(input);
if(Object.keys(errors).length !== 0) {
props.setIsValid(false);
}
}
return (
<input
onChange={e=> props.setProperty(e)}>
</input>
<input
onChange={e=> props.setProperty(e)}>
</input>
)
}
If an input is changed, it sets a property in the parent and this component is re-rendered. Inputs is an array of objects and I wish to validate it's contents on each render (or componentDidMount). I either manage to get it to loop infinitely or run the validation only once.
I appreciate your help.
P.S.:
I tried another approach as well, but it still ends up looping infinately:
export default function Input(props) {
const didMountRef = useRef(true);
useLayoutEffect(() => {
if (didMountRef.current) {
didMountRef.current = false;
return;
}
validate(input);
});
const validate = input => {
// validation...
}
}
Here is the parent component:
class CreateShoppingItem extends React.Component {
constructor(props) {
super(props);
this.state = {
storage: {},
isValid: true,
};
}
setIsValid = (isValid) => {
this.setState({ isValid });
};
render() {
return (
<div>
<Input setIsValid={this.setIsValid} inputs={this.storage.inputs} />
<Button disable={!isValid}></Button>
</div>
);
}
}
I am struggling with a situation where I need to grab the value of an input field to make an API call.
So What I have is this:
import React, { Component } from 'react'
import axios from 'axios';
const fetchWeather = function (e) {
e.preventDefault();
axios.get('https://api.openweathermap.org/data/2.5/weather?q=Zeist&appid=MYID')
.then(res => {
console.log(res.data);
});
console.log();
}
export default class App extends Component {
render() {
return (
<div>
<form onSubmit = {fetchWeather}>
<input type = 'text' placeholder = 'Your city' ref="city"></input>
<input type = 'submit' placeholder='Submit' value = 'Submit'></input>
</form>
</div>
);
}
}
A form that runs a function on submit, I used preventDefault to stop the page from loading.
The function runs and the page doesn't reload. Now I'd like to grab the text from the input field and log it in that function. The reason why I want this is so I can use it in my API call as a query parameter. I tried many things. I tried logging e to see what is inside. It's a very big object and I couldn't find the value I'm looking for. I tried using ref, this didn't work either. Any idea how I could solve this issue?
You are using Uncontrolled Components.
You need to move your fetchWeather function inside of your component,
export default class App extends Component {
fetchWeather = (e) => {
e.preventDefault();
console.log(this.refs.city.value)//You will get vlue here
axios.get('https://api.openweathermap.org/data/2.5/weather?q=Zeist&appid=MYID')
.then(res => {
console.log(res.data);
});
console.log();
}
render() {
return (
<div>
<form onSubmit = {this.fetchWeather}> //refer your function using `this`
<input type = 'text' placeholder = 'Your city' ref="city"></input>
<input type = 'submit' placeholder='Submit' value = 'Submit'></input>
</form>
</div>
);
}
}
Better way is, using state. This is called Controlled Components.
export default class App extends Component {
state={
city: ''
}
handleChange = (e) => {
this.setState({[e.target.name]:e.target.value})
}
fetchWeather = (e) => {
e.preventDefault();
console.log(this.state.city)//You will get vlue here
axios.get('https://api.openweathermap.org/data/2.5/weather?q=Zeist&appid=MYID')
.then(res => {
console.log(res.data);
});
console.log();
}
render() {
return (
<div>
<form onSubmit = {this.fetchWeather}> //refer your function using `this`
<input type = 'text' placeholder = 'Your city' name="city" value={this.state.city} onChange={this.handleChange} ></input>
<input type = 'submit' placeholder='Submit' value = 'Submit'></input>
</form>
</div>
);
}
}
You can use a controlled component: https://reactjs.org/docs/forms.html.
I simulated the fetch operation in the snippet.
const fetchWeather = function (data) {
// simulating fetch
setTimeout(function() {
console.log('GETTING DATA ' + JSON.stringify(data))
}, 500)
}
// Controlled Component: https://reactjs.org/docs/forms.html
class App extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
city: ''
}
this._handleInputChange = this._handleInputChange.bind(this)
this._handleSubmit = this._handleSubmit.bind(this)
}
_handleInputChange (event) {
const { value, name } = event.target
this.setState({
[name]: value
})
}
_handleSubmit (event) {
event.preventDefault()
fetchWeather(this.state)
}
render() {
const { city } = this.state
return (
<div>
<form onSubmit={this._handleSubmit}>
<input
type='text'
name='city'
placeholder='Your city'
value={city}
onChange={this._handleInputChange}
/>
<input type='submit' placeholder='Submit' value='Submit' />
</form>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I was developing a react component to get a value inside a input and automatically show it in a tag, using refs.
All works fine, but the value shown is the previous value.
I really don't now how to fix this. I using the onChange event in the input to change the state of what will be shown, it is clear that the present value is not taken, but rather the previous value
class Conversor extends Component {
constructor(props){
super(props)
this.state = {
value: null
}
this.output = this.output.bind(this)
}
output(){
console.log(this.state)
this.refs.output.innerHTML = this.state.value
}
render() {
return (
<div>
<h2>{this.state.inputValue}</h2>
<input ref="input" type="text" onChange={() => {this.setState({ value: this.refs.input.value }); this.output()}}/>
<label ref="output"></label>
</div>
);
}
}
If i put the value "Hello World" in the input, the value shown is "Hello Worl", when it's have to be the "Hello World"
You can use event to do this and no need of output() function.
class Conversor extends Component {
constructor(props){
super(props)
this.state = {
value: null
}
}
render() {
return (
<div>
<h2>{this.state.inputValue}</h2>
<input ref="input" type="text" onChange={(e) => {this.setState({ value: e.target.value });}}/>
<label ref="output">{this.state.value}</label>
</div>
);
}
}
The best way to achieve your goal is not using the refs. Here is how you do it
class Conversor extends Component {
constructor(props){
super(props)
this.state = {};
}
handleChange = (e) => {
const { id, value } = e.target;
this.setState({
[id]: value
})
}
render() {
const { name, anotherName } = this.state;
return (
<div>
<h2>{name}</h2>
<input id="name" name="name" type="text" onChange={this.handleChange}/>
<h2>{anotherName}</h2>
<input id="anotherName" name="anotherName" type="text" onChange={this.handleChange}/>
</div>
);
}
}
If you still want to use the refs then do the following,
class Conversor extends Component {
constructor(props){
super(props)
this.state = {
value: null
}
}
output = (e) =>{
this.setState({value: e.target.value }, () => {
this.refs.output.innerHTML = this.state.value
})
}
render() {
return (
<div>
<input ref="input" type="text" onChange={this.output}/>
<label ref="output"></label>
</div>
);
}
}
You don't need to bind your input handler function at all. Instead of doing that, just use an arrow function like _handleInputTextChange . Check this out:
import React, { Component } from 'react';
class InputTextHandler extends Component {
constructor(props){
super(props)
this.state = {
inputValue: ''
}
}
_handleInputTextChange = e => {
const inputValue = e.target.value;
this.setState({inputValue})
console.log(inputValue)
}
render() {
return (
<div>
<input
type="text"
onChange={this._handleInputTextChange}/>
</div>
);
}
}
export default InputTextHandler;
Two things: grab the event value in the onChange method, and pass the this.output method as the second argument to setState which fires after the state has been updated which is not a synchronous operation.
render() {
return (
<div>
<h2>{this.state.inputValue}</h2>
<input ref="input" type="text" onChange={event => {this.setState({ value:event.target.value }, this.output)}}/>
<label ref="output"></label>
</div>
);
}
Try it here!
I want to check the values of this.state.cityCodeval and this.state.idVal using an if statement inside the displayName() method so it can display what's inside the return() if the values inputted by the user are correct.
In my Webstorm IDE, I get a warning that says:
Binary operation argument type string is not compatible with type string
Which makes me believe I'm checking for their values the wrong way.
I know I could just do console.log(this.state.cityCodeval); or console.log(this.state.idVal);, but I need to check for what the user input is.
Here's my code
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actionType from '../../store/actions/actions';
class SearchArticle extends Component {
constructor(props) {
super(props);
this.state = {
flag: false,
idVal: '',
cityCodeval: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleArticleId = this.handleArticleId.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
console.log("IDValue --> " + this.state.idVal);
this.props.articleIdValueRedux(this.state.idVal);
this.setState({flag: true});
}
handleChange = event => {
this.setState({value: event.target.value});
this.props.cityCodeReducerRedux(event.target.value);
}
handleArticleId = event => {
event.preventDefault();
this.setState({idVal: event.target.value});
}
displayName = () => {
if(this.state.cityCodeval === 'nyc' && this.state.idVal === '1') {
return (
<div>
<p>author name: {this.state.authorNameValue}</p>
<p>article text: {this.state.storyTextValue}</p>
</div>
);
}
}
render() {
return(
<div>
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.cityCodeValue} type="text" placeholder="city code"/>
<input onChange={this.handleArticleId} value={this.state.idVal} placeholder="article id"/>
<button type="submit" value="Search">Submit</button>
{this.state.flag ? this.displayName() : null}
</form>
</div>
);
}
}
const mapStateToProps = state => {
return {
cityCodeValue: state.cityCodeValue.cityCodeValue,
authorNameValue: state.authorNameValue.authorNameValue,
articleIdValue: state.articleIdValue.articleIdValue,
storyTextValue: state.storyTextValue.storyTextValue
};
};
const mapDispatchToProps = dispatch => {
return {
cityCodeReducerRedux: (value) => dispatch({type: actionType.CITY_CODE_VALUE, value}),
articleIdValueRedux: (value) => dispatch({type: actionType.ARTICLE_ID_VALUE, value})
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SearchArticle);
You should still return null; as a safe clause just in case your condition doesn't match.
displayName = () => {
if(this.state.cityCodeval === 'nyc' && this.state.idVal === '1') {
console.log(this.state.cityCodeval); // console it here
console.log(this.state.idVal); // console it here
return (
<div>
<p>author name: {this.state.authorNameValue}</p>
<p>article text: {this.state.storyTextValue}</p>
</div>
);
}
return null;
}
Also now, in your render method you can do this.
{this.state.flag && this.displayName()}
This means that if the flag variable is true, call displayName it then executes the function. If you first condition matches it will return that otherwise it will return null;
Previously in your current code if the flag variable was true and it executed the function displayName where the if condition didn't meet. This caused an error because it had nothing to return.