How to update state inside componentDidMount? - reactjs

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>
)
}
}

Related

How to update component when state in parent class changes?

Parent class (removed some irrelevant code):
class AddCategory extends React.Component{
constructor() {
super();
this.state = {
update: '',
category_name: ''
}
}
update(changed) {
this.setState({
update: changed,
})
}
render() {
const create_category = () => {
Axios.post('/createCategory', {
category_name: this.state.category_name,
}).then((response) => {
})
}
return (
<div>
//changes the update state to 1 because there was an update
<button className="btn" onClick={this.update('1'); create_category()}}>Add</button>
</div>
)
}
}
export default AddCategory;
Child class (removed some irrelevant code):
class AddSubcategory extends AddCategory {
constructor() {
super();
this.state = {
subcategory_name: '',
category_id: '',
result: [],
is_loading: true
}
}
set_fetched_data(data, is_fetched) {
this.setState({
result: data,
is_loading: is_fetched
})
}
//fills the select box with db entries
//need to update the result array every time 'update' state changes
componentDidMount() {
Axios.get('/categories').then((response) => {
const category_list = response.data.result;
this.set_fetched_data(category_list.map(category => <option value={category.id}>{ category.category_name }</option>), false);
})
}
render() {
const create_subcategory = () => {
Axios.post('/createSubcategory', {
subcategory_name: this.state.subcategory_name,
category_id: this.state.category_id
}).then((response) => {
})
}
return (
<div>
<select name="categories" onChange={(e) => {this.set_category_id(e.target.value)}}>
<option defaultValue>-</option>
{ !this.state.is_loading && this.state.result }
</select>
<input type="text" onChange={(e) => {this.set_subcategory_name(e.target.value)}}/>
{!this.state.is_loading && <button className="btn" onClick={create_subcategory}>Add</button>}
</div>
)
}
}
export default AddSubcategory
Need to figure out how to access the 'update' state in the child class + how to listen for changes in the state to keep updating my selectbox - initially I was going to do this with useEffect(), but after reworking both functions into classes I found out that that's not possible.
If you're using classes instead of functions than you cannot use hooks such as useEffect or useContext.
I highly suggest using react-redux for a cross application state management.
You'll need to do some setup but you'll get a shared state accessible by all components - no matter the level.
Here's a step by step for a basic setup on a react project.

React How to Get Value of a SelectList from a Child Component

I'm working on a form in which one of the input items will be a select list. I am trying to implement the form by using reusable components. The hierarchy is this:
<SignupForm>
<CountrySelectList>
<SelectList>
I am able to get the select list to render, but I am not sure how to pass the needed props to the child componets in order to bind the selection from the drop down list? Here are the components (with some form fields removed for brevity).
SignupForm
class SignupForm extends React.Component {
constructor(props) {
super(props);
this.state = {
country: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
handleSubmit(event) {
event.preventDefault();
let formData = new FormData();
formData.append("Country", this.state.country);;
fetch("api/account/signup", {
method: "POST",
body: formData })
.then(response => response.json())
.then(data => console.log(data));
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<CountrySelectList name="country" value={this.state.country} onChange={this.handleChange} />
<SubmitButton btnText="Submit"/>
</form>
);
}
}
export default SignupForm;
CountrySelectList
class CountrySelectList extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
data: []
};
}
async componentDidMount() {
try {
const response = await fetch('api/location/countries');
const json = await response.json();
this.setState({
data: json,
isLoaded: true
});
} catch (error) {
console.log(error);
}
}
render() {
const { error, isLoaded, data } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <LoadingSpinner/>;
} else {
return (
<SelectListGroup>
<Label label="country" title="Country"/>
<SelectList data={data} value={this.props.value} onChange={this.props.onChange} />
</SelectListGroup>
);
}
}
}
export default CountrySelectList;
SelectList
export default function SelectList({ data, value, onChange, addClass="" }) {
return (
<select
value={value}
className={`form-select ${addClass}`}
onChange={onChange}>
{data.map((data, index) => (
<option key={index} value={data.value}>
{data.text}
</option>
))}
</select>
);
}
This answer has a good explanation of getting data from forms and their component parts.
Get form data in ReactJS
You're kind of doing the same job twice by saving the selection as state and using form -> submit.
If you give your select a name attribute, then in the handleSubmit function, the event parameter will contain a named element that you can get the value from.
<select name='country' ...
Then the function can use
const handleSubmit = (event) => {
console.log(event.target.country.value);
}
The onSubmit event of the form carries all the info, you just have to name the elements.

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;

how to change value of an object using reactjs

My objective is to change the value of an object and pass the modified object.
Here is the object:
{
id: '12497wewrf5144',
name: 'ABC',
isVisible: 'false'
}
Here is the code:
class Demo extends Component {
constructor(props) {
super(props)
this.state = {
demo: []
}
}
componentDidMount() {
axios
.get('/api/random')
.then(res => {
this.setState({ demo: res.data})
})
.catch(error => {
console.log(error)
})
}
render() {
return (
<div>
{this.state.demo.map((user)=>
<h1>{user.name}</h1>
<input type="checkbox" value={user.value} />
)}
</div>
)
}
}
export default Demo
I don't know what to write in onchange method for checkbox to only change the value within the object.
Note: value is string isVisible (we need to change value as boolean)
Can anyone help me in this query?
In order to change a certain key of an object you can use the following
this.setState({
...this.state,
demo: {
...this.state.demo,
isVisible: <NEW VALUE>
}
})

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,
});
};

Resources