React - controlled component issue - reactjs

So I'm working on a controlled component for a unit converter - the problem is the output is a step behind what I want it to be (e.g if I type "100" it will give the value of 10kg in lbs, "1000" -> 100 etc.). I'm sure the fix is obvious but I can't see it myself - I'm still a React newbie!
import React, { Component } from 'react';
class SearchBar extends Component {
constructor(){
super();
this.state = {
kg: '',
lbs: '',
value: 'kgtolbs',
userInput: ''
}
this.convertKgToLbs.bind(this);
this.convertLbsToKg.bind(this);
this.handleOption.bind(this);
this.handleChange.bind(this);
}
handleOption(event){
const selectedValue = event.target.value;
this.setState({
value: selectedValue
});
}
handleChange(event){
this.setState({
userInput: event.target.value
})
if(this.state.value === 'kgtolbs'){
this.convertKgToLbs();
} else {
this.convertLbsToKg();
}
}
convertKgToLbs () {
const kg = this.state.userInput;
const lbsConversion = kg * 2.205;
this.setState({
lbs: lbsConversion.toFixed(1)
});
}
render(
){
return(
<div className="search-bar">
<input className="input" type="number" onChange={this.handleChange.bind(this)} />
<select onChange={this.handleOption.bind(this)}>
<option value="kgtolbs">Kg to Lbs</option>
<option value="lbstokg">Lbs to kg</option>
</select>
<p>{this.state.lbs} </p>
<p>{this.state.kg} </p>
</div>
);
}
}
export default SearchBar;
Any help appreciated - thanks !

setState is an asynchronous function. This means that in your handleChange function when you use this.state.value, the state has not actually been updated yet. setState has a callback handler to ensure do stuff after it has ran. In your case, you can do this:
handleChange(event){
this.setState({
userInput: event.target.value
}, function() {
if(this.state.value === 'kgtolbs'){
this.convertKgToLbs();
} else {
this.convertLbsToKg();
}
});
}

Related

How to update component state that is connected by componentDidUpdate()

I am passing selectedOrderState as props from parent and want to populate the state and that works but can't figure how to change the state for use in an input field with an onChange=(handleChange) function attached to manipulate the data. Seems as though componentDidUpdate() and getDerivedStateFromProps() both seem to lock the state so no change can occur. **componentDidMount also does not work because the selectedOrderState prop comes from an onClick event and so the component had already mounted.
Code below - Any thoughts would be helpful!
import React, { Component } from 'react'
export class addOrder extends Component {
state = {
AoOrder: false,
AoProgress: false,
AoChat: false,
visibility: "visible",
Order: {},
DeliveryDate:"",
};
//Functs
componentDidUpdate(prevProps){
if(this.props.selectedOrderState !== this.state.Order){
this.setState({
Order:this.props.selectedOrderState
});
}
}
handleChange = (e) => {
this.setState({
Order:{
...this.state.Order,
[e.target.id]: e.target.value,
}
})
};
handleSubmit = () => {
};
};
render() {
const order = this.props.selectedOrderState;
const { user: { credentials: { handle, imageUrl}}} = this.props;
return (
<form className='OrderInfo'onSubmit={this.handleSubmit}>
<div className='OrderInfoLbl'>Order Id:</div>
<div className="OrderInfoInput">{this.props.selectedOrderState.OrderId}</div>
<div className='OrderInfoLbl'>Delivery Date:</div>
<input className="OrderInfoInput" id="DeliveryDate" type="text" onChange=
{this.handleChange}></input>
<img className="ProfileBioSubmit" onClick={this.handleSubmit}
src="./images/svg/AcceptBtns.svg" alt="Edit"></img>
</form>
)
}
}
export default addOrder
Declare your state inside the constractor and bind your functions. I'm inviting you to take a look to forms docs with react
constructor(props) {
super(props);
this.state = {
AoOrder: false,
AoProgress: false,
AoChat: false,
visibility: "visible",
Order: {},
DeliveryDate:"",
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
Probably the most hackerish way to do something but it worked:)
What i did was keep the componentDidUpdate() feeding the state to the child component but from the parent i passed down a function called handleChangeUP() for which i was able to use pass the event of onChange data through to change the original state selectedOrderState. Have a look!
Child
import React, { Component } from 'react'
export class addOrder extends Component {
state = {
AoOrder: false,
AoProgress: false,
AoChat: false,
visibility: "visible",
Order: {},
};
//Functs
componentDidUpdate(prevProps){
if(this.props.selectedOrderState !== this.state.Order){
this.setState({
Order:this.props.selectedOrderState
});
}
}
handleChange = (e) => {
this.props.handleChangeUP(e)
};
render() {
const order = this.props.selectedOrderState;
const { user: { credentials: { handle, imageUrl}}} =
this.props;
return (
<form className='OrderInfo'onSubmit={this.handleSubmit}>
<div className='OrderInfoLbl'>Order Id:</div>
<div className="OrderInfoInput">
{this.props.selectedOrderState.OrderId}</div>
<div className='OrderInfoLbl'>Delivery Date:</div>
<input className="OrderInfoInput" id="DeliveryDate" type="text"
value={this.state.Order.DeliveryDate}
onChange={this.handleChange}></input>
<img className="ProfileBioSubmit" onClick={this.handleSubmit}
src="./images/svg/AcceptBtns.svg" alt="Edit"></img>
</form>
)
}
}
export default addOrder
Parent
import React, { Component } from 'react'
import Child from './child'
export class Parent extends Component {
state = {
creating: false,//creat order window toggle
profiling: false,//Profile window toggle
chatting: false,//Chat window toggle
searching: false,//Search inside Chat window
selectedOrder: {}
};
handleChangeUP = (e) => {
console.log(e.target.id);
this.setState({
// [e.target.id]: e.target.value
//Order: e.target.value
selectedOrder:{
...this.state.selectedOrder,
[e.target.id]: e.target.value
}
})
}
render() {
return (
<div className="Wrapper">
<Child handleChangeUP={this.handleChangeUP}
selectedOrderState={this.state.selectedOrder}/>
</div>
)
}
}
export default Parent;

calling function in React SetState gives error that userName is unlabelled why?

import React,{Component} from 'react'
class Formhandler extends Component {
constructor(props) {
super(props)
this.state = {
userName:""
}
}
changer=(event)=>{
this.setState(()=>{
userName : event.target.value
})
}
render()
{
return(
<div>
<label>UserName</label>
<input type="text" value={this.state.userName} onChange={this.changer}/>
</div>
)
}
}
export default Formhandler
You are getting the error because of invalid syntax.
Update changer function
changer = (event) => {
this.setState({ userName: event.target.value });
};
You need to return an object inside the setState function but you are not that's the source of issue(syntax error).
use a function inside setState when your new state value would depend on your previous state value, where the function passed inside the setState will receive previous state as argument
changer = (e) => {
this.setState((prevState) => ({
userName : e.target.value
})
);
}
pass an object to update the state, use this when it doesn't depend on your previous state value.
changer = (e) => {
this.setState({ userName: e.target.value });
};
import React from "react";
class Formhandler extends React.Component {
constructor(props) {
super(props);
this.state = {
userName: "",
};
}
changer(event) {
this.setState(() => ({
userName: event.target.value,
}));
}
render() {
return (
<div>
<label>UserName</label>
<input
type="text"
value={this.state.userName}
onChange={this.changer.bind(this)}
/>
</div>
);
}
}
export default Formhandler;
It will work, compare your version and this

React: how do I use onSubmit to change state?

I'm quite new to React, and have only completed a few projects with it. I'm currently trying to create a form that, using onSubmit, changes the state of "isSubmitted" from false to true. When "isSubmitted" is true, I'd like to render another component to show the results of the selection.
What's currently happening is that onChange is working and I can see the value of "selectedI" set as state in the console.log when I change it. However, when I click submit, this state of "isSubmitted" doesn't change.
My code is below. Any help is greatly appreciated!
import React, { Component } from "react";
import Results from "../components/Results";
export class Create extends Component {
constructor(props) {
super(props);
this.state = {
selectedI: { value: "" },
// selectedC: { value: "" },
// selectedG: { value: "" },
// selectedA: { value: "" },
isSubmitted: false,
};
}
handleChange = (event) => {
this.setState({
selectedI: { value: event.target.value },
});
};
handleSubmit = (event) => {
event.preventdefault();
this.setState({
isSubmitted: true,
});
};
render() {
console.log(this.state);
return (
<>
<form onSubmit={this.handleSubmit} onChange={this.handleChange}>
<select value={this.state.value}>
{this.props.ingredient.map((ingredient) => {
return (
<option value={ingredient.strIngredient1}>
{ingredient.strIngredient1}
</option>
);
})}
</select>
<input type="submit" value="Submit" />
</form>
{this.state.isSubmitted && <Results />}
</>
);
}
}
export default Create;
Inside your handleSubmit method correct the case on preventdefault. It should be preventDefault. Note the capital D. Once corrected it should stop your page from reloading and resetting your state along with it. See the code below.
handleSubmit = (event) => {
event.preventDefault();
this.setState({
isSubmitted: true,
});
};

How to update state inside componentDidMount?

I'm using fetch API and I want update the const called state inside the componentDidMount() (with onChange) which are being using in a template string. How do I update this value with onChange?
import React, {Component} from 'react'
class Data extends Component {
constructor() {
super();
this.state = {
items: {},
value: '',
isLoaded: false
}
}
handleChange(e) {
this.setState({value: e.target.value});
}
componentDidMount() {
const state = this.state.value
fetch(`http://api.timezonedb.com/v2.1/get-time-zone?key=J9X3EOT2EM8U&format=json&by=zone&zone=${state}`)
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
items: json,
})
});
}
render(){
const {isLoaded} = this.state;
if(!isLoaded) {
return <div>Loading...</div>
}
return(
<div>
<select onChange={this.handleChange}>
<option value="America/Chicago">Chicago</option>
<option value="America/Sao_Paulo">São Paulo</option>
</select>
</div>
)
}
}
So, how can I update the value of the const state with onChange?
componentDidMount() is called when the React component has mounted, and it happens only once.
If I understand correctly, you want to call fetch on each change of the value stored under value state property, so the componentDidMount method is not a perfect place to put that kind of logic. You can create a separate method called fetchData and pass the value to it as an argument. Then you can call that method on componentDidMount as well as on each value property change (in our case - onChange event).
import React, { Component } from "react";
class Data extends Component {
constructor(props) {
super(props);
this.state = {
items: {},
value: "America/Chicago",
isLoaded: false
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
const { value } = this.state;
this.fetchData(value);
}
handleChange(event) {
const value = event.target.value;
this.setState({
value
});
this.fetchData(value);
}
render() {
const { isLoaded, value, items } = this.state;
if (!isLoaded) {
return <div>Loading...</div>;
}
return (
<div>
<select onChange={this.handleChange} value={value}>
<option value="America/Chicago">Chicago</option>
<option value="America/Sao_Paulo">São Paulo</option>
</select>
{JSON.stringify(items)}
</div>
);
}
fetchData(value) {
fetch(
`https://api.timezonedb.com/v2.1/get-time-zone?key=J9X3EOT2EM8U&format=json&by=zone&zone=${value}`
)
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
items: json
});
});
}
}
Working demo: https://codesandbox.io/embed/728jnjprmq
Assuming you want to refresh the value of this.state.items when the user changes the value of the select, you can do this in the onChange. However, your code is in a few (incorrect) pieces. Let's start from the top.
First of all, you're setting the value property of state to '', so your componentDidMount function is going to see that value. I assume that's no good, so let's strip that out of componentDidMount entirely. We can move this code to the handleChange function instead, but it'll still need to be changed:
handleChange(e) {
this.setState({value: e.target.value});
fetch(`http://api.timezonedb.com/v2.1/get-time-zone?key=J9X3EOT2EM8U&format=json&by=zone&zone=${e.target.value}`)
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
items: json,
})
});
}
Notice my change - we can't access the value from the state, because setState is asynchronous, and so the value hasn't been updated by this point. We know the value comes from the select though.
The other thing you could do to improve this functionality is to turn the select into a controlled component. To do this, you just have to set the value of the field to be controlled by the state of this component. Since you're using an onChange listener for this, it makes the field a controlled component (if you weren't using an onChange, it would be a read-only field.
The loading variable in state appears to be being used incorrectly, I'm guessing you just need to check if there's data in 'items'. I'll remove this for now, but you could come back to this.
render(){
const {isLoaded} = this.state;
if(!isLoaded) {
return <div>Loading...</div>
}
return(
<div>
<select onChange={this.handleChange}>
<option value="America/Chicago">Chicago</option>
<option value="America/Sao_Paulo">São Paulo</option>
</select>
</div>
)
}
Tomasz's code has 2 mistakes: (1) it fetches resources w/o checking if the component has been unmounted; (2) it starts the request w/o updating the UI first.
I would do the following instead:
import React, {Component} from 'react'
class Data extends Component {
constructor() {
super();
this.state = {
items: {},
value: '',
isLoaded: false
}
this._isMounted = false;
// don't forget to bind your methods
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
handleChange(e) {
const value = e.target.value;
this.setState({ value }, () => {
if (!this._isMounted) return;
const url = `http://api.timezonedb.com/v2.1/get-time-zone?key=J9X3EOT2EM8U&format=json&by=zone&zone=${value}`
fetch(url).then((res) => {
if (!this._isMounted) return;
const data = res.json();
this.setState({ isLoaded: true, items: data });
})
});
}
render(){
const { isLoaded } = this.state;
if(!isLoaded) {
return <div>Loading...</div>
}
return(
<div>
<select onChange={this.handleChange}>
<option value="America/Chicago">Chicago</option>
<option value="America/Sao_Paulo">São Paulo</option>
</select>
</div>
)
}
}

onChange or onKeyUp event takes previous value in ReactJS

I've created form in ReactJS. I am fetching value in common method in {key : value} paired. but I am getting previous value in method.
constructor(props) {
super(props);
{
this.state = { name: "", age: 0 };
}
}
inputChange = (key, value) => {
this.setState({ [key] : value });
console.log(this.state);
}
render() {
return (
<form>
<div>
Name : <input type="text" name="name" onKeyUp={(e) => this.inputChange('name', e.target.value)}></input>
</div>
<div>
Age : <input type="text" name="age" onKeyUp={(e) => this.inputChange('age', e.target.value)}></input>
</div>
</form>
)
}
I've attached the screenshot for better understanding.
setState enqueues a change to the state, but it doesn't happen immediately. If you need to do something after the state has changed, you can pass a second callback argument to setState:
inputChange = (key, value) => {
this.setState({ [key] : value }, () => {
console.log(this.state);
});
}
This will do exactly what you need.
class App extends Component {
constructor(props){
super(props)
this.state = {
name: '',
age: ''
}
}
handleInput(option, event){
if (option === 'name') {
this.setState({
name: event.target.value
}, () => {
console.log("Name: ", this.state.name)
});
}
else if (option === 'age'){
this.setState({
age: event.target.value
}, () => {
console.log("Age: ", this.state.age)
});
}
}
render() {
return (
<div>
<div>
<header>Name: </header>
<input type="text" onChange={this.handleInput.bind(this, 'name')}/>
<header>Age: </header>
<input type="text" onChange={this.handleInput.bind(this, 'age')}/>
</div>
</div>
);
}
}
update 2022
with functional components using useState hook this does not work longer, you have to use it in useEffect to update after rendering.
State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().

Resources