Why all datepicker values become null when I change one of this? - javascript

I get values from database and save it in state enteredEvent:
class FormEditPage extends Component {
constructor(props) {
super(props);
this.state = {
enteredEvent: {
name: '',
date: new Date(),
time: '',
place: '',
datepub: new Date()
},
};
...
}
componentDidMount() {
this.onHandleEventOneFetch(idEvent);
}
handleChangeInputName(newValue) {
this.setState({ enteredEvent: { name: newValue } });
}
handleChangeInputDate(newValue) {
this.setState({ enteredEvent: { date: newValue } });
}
handleChangeInputTime(newValue) {
this.setState({ enteredEvent: { time: newValue } });
}
handleChangeInputPlace(newValue) {
this.setState({ enteredEvent: { place: newValue } });
}
handleChangeInputDatepub(newValue) {
this.setState({ enteredEvent: { datepub: newValue } });
}
onHandleEventOneFetch(id) {
fetch(..., {
method: 'GET'
})
...
.then(data =>
this.setState({
enteredEvent: {
name: data[0].name,
date: new Date(data[0].date),
time: data[0].time,
place: data[0].place,
datepub: new Date(data[0].datepub)
}
})
);
}
render() {
return (
<div>
<FormEvent
enteredEvent={this.state.enteredEvent}
onHandleChangeInputName={this.handleChangeInputName}
onHandleChangeInputDate={this.handleChangeInputDate}
onHandleChangeInputTime={this.handleChangeInputTime}
onHandleChangeInputPlace={this.handleChangeInputPlace}
onHandleChangeInputDatepub={this.handleChangeInputDatepub}
/>
</div>
);
}
}
In this component I added datapicker and timepicker:
import DatePicker from 'react-date-picker';
import TimePicker from 'react-time-picker';
class FormEvent extends Component {
constructor(props) {
super(props);
this.handleNameChange = this.handleNameChange.bind(this);
this.handleDateChange = this.handleDateChange.bind(this);
this.handleTimeChange = this.handleTimeChange.bind(this);
this.handlePlaceChange = this.handlePlaceChange.bind(this);
this.handleDatepubChange = this.handleDatepubChange.bind(this);
}
handleNameChange(event) {
this.props.onHandleChangeInputName(event.target.value);
}
handleDateChange(newDate) {
this.props.onHandleChangeInputDate(newDate);
}
handleTimeChange(newTime) {
this.props.onHandleChangeInputTime(newTime);
}
handlePlaceChange(event) {
this.props.onHandleChangeInputPlace(event.target.value);
}
handleDatepubChange(newDatepub) {
this.props.onHandleChangeInputDatepub(newDatepub);
}
render() {
return (
<div>
<input type='text' required value={this.props.enteredEvent.name} onChange={this.handleNameChange}/>
<DatePicker onChange={this.handleDateChange} value={this.props.enteredEvent.date}/>
<TimePicker onChange={this.handleTimeChange} value={this.props.enteredEvent.time}
<input type='text' value={this.props.enteredEvent.place} onChange={this.handlePlaceChange}/>
<DatePicker onChange={this.handleDatepubChange} value={this.props.enteredEvent.datepub}/>
</div>
);
}
FormEvent.propTypes = {
enteredEvent: PropTypes.object,
onHandleChangeInputName: PropTypes.func,
onHandleChangeInputDate: PropTypes.func,
onHandleChangeInputTime: PropTypes.func,
onHandleChangeInputPlace: PropTypes.func,
onHandleChangeInputDatepub: PropTypes.func
};
}
In result all datepickers and timepicker get values from enteredEvent. When I change value in one of datepicker/timepicker, values in other datepickers and timepicker became null. How can I fix it?

You've made your state a nested object, and when you set state, you're overwriting the whole object. You will either need to merge the object with the previous state, or stop using a nested object. React will do a shallow merge of state, but not a deep merge.
Doing the merge yourself would look like this, if you can use object spread syntax:
this.setState(oldState => ({
enteredEvent: {
...oldState.enteredEvent,
name: newValue
}
});
If object spread syntax is not at your disposal, then the same thing can be done like this:
this.setState(oldState => ({
enteredEvent: Object.assign({}, oldState.enteredEvent, {name: newValue})
});
If instead you want to go with the approach of flattening the state, it would look like this:
this.state = {
name: '',
date: new Date(),
time: '',
place: '',
datepub: new Date()
};
// ...
this.setState({ name: newValue });

When you have a nested object in state you must make sure to create a copy of the object currently in state or it will be overwritten with a new object with just the given property.
Example
handleChangeInputName(newValue) {
this.setState(previousState => ({
enteredEvent: { ...previousState.enteredEvent, name: newValue }
}));
}

Related

Pass value from one component to another values in array ReactJS

I'm trying to pass value from one component to another but when I do that, I get route in my http address as undefined instead of value. I have response from server in this form:
I'm trying to pass id values, and based on them make some actions. I get the error that GET request cannot be done due to value undefined.
Here is my code:
class StationService {
getStationById(id) {
return axios.get(STATION_API + '/station/' + id);
}
updateStation(station, id) {
return axios.put(STATION_API + '/station/' + id, station);
}
}
import React, { Component } from 'react';
import StationService from '../services/StationService';
class CreateStationComponent extends Component {
constructor(props) {
super(props)
this.state = {
station: {
id: this.props.match.params.id,
city: '',
name: '',
trains: [
{
number: '',
numberOfCarriages: ''
}
]
}
}
}
componentDidMount() {
if (this.state.station.id === '_add') {
return;
} else {
StationService.getStationById(this.state.id).then((res) => {
let station = res.data;
this.setState({ name: this.state.station[0].name, city: station[0].city })
});
}
console.log(this.state.station.name + 'dfddddd');
}
saveStation = (e) => {
e.preventDefault();
let station = { city: this.state[0].city, name: this.state[0].name }
if (this.state.id === '_add') {
StationService.createStation(station).then(res => {
this.props.history.push('/stations');
});
}
}
}
}
render() {
return (
<div>
...
</div >
);
}
}
From this component I want to pass id value to CreateStationComponent. But I don't know what I'm doing wrong.
import React, { Component } from 'react';
import StationService from '../services/StationService';
class ListStation extends Component {
constructor(props) {
super(props)
this.state = {
stations: []
}
this.addStation = this.addStation.bind(this);
this.editStation = this.editStation.bind(this);
this.deleteStation = this.deleteStation.bind(this);
this.showTrains = this.showTrains.bind(this);
}
deleteStation(id) {
StationService.deleteStation(id).then(res => {
this.setState({ stations: this.state.stations.filter(station => station.id !== id) });
})
}
editStation(id) {
this.props.history.push(`/add-station/${id}`);
}
componentDidMount() {
StationService.getStations().then((res) => {
this.setState({ stations: res.data });
})
}
render() {
return (
<div>
</div>
<div className="row">
<tbody>
{this.state.stations.map(
station =>
<tr key={station.id}>
<td>{station.city}</td>
<td>{station.name}</td>
<td>
<button onClick={() => this.editStation(station.id)} className="btn btn-info">Modify</button>
</tr>
)}
</tbody>
</table>
</div>
</div>
);
}
}
Any help would be appreciated.
Inside the constructor this.prop doesn't exist yet. Just access props.
constructor(props) {
super(props)
this.state = {
station: {
id: props.match.params.id,
city: '',
name: '',
trains: [
{
number: '',
numberOfCarriages: ''
}
]
}
}
}
Also pointed out in a comment, this.state.id isn't defined
StationService.getStationById(this.state.id)
but this.state.station.id is. Change the reference.
StationService.getStationById(this.state.station.id)
Since this.state.station is an object and not an array, this.setState({ name: this.state.station[0].name, city: station[0].city }) is also incorrect. this.state.station[0] is undefined and should throw error when attempting to access name. Update the reference.
this.setState({
name: this.state.station.name,
city: station[0].city,
})
And same for saveStation, update the state references.
saveStation = (e) => {
e.preventDefault();
let station = {
city: this.state.station.city,
name: this.state.station.name }
if (this.state.station.id === '_add') {
StationService.createStation(station).then(res => {
this.props.history.push('/stations');
});
}
}

Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate

There's already some people asking this question but they are almost caused by the same reason (fire the callback in situ e.g. <div onClick={this.handleClick()}></div>). However this doesn't happen to me but I got the same error.
P.S. Some util functions are defined behind the scene.
// Parent
export default class SearchArea extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {
update: '',
confirmed: '',
recovered: '',
deaths: ''
},
showDashboard: false
}
this.setCountryData = this.setCountryData.bind(this);
this.showDashboard = this.showDashboard.bind(this);
}
setCountryData(data) {
console.log(data);
this.setState({
data: {
update: data.lastUpdate,
confirmed: data.confirmed,
recovered: data.recovered,
deaths: data.deaths
}
});
}
showDashboard(toggle) {
this.setState({ showDashboard: toggle })
}
render() {
return (
<div>
<Dropdown onSetCountryData={this.setCountryData} onShowDashboard={this.showDashboard} />
<Dashboard data={this.state.data} visible={this.state.showDashboard} />
</div>
)
}
}
// Sibling1
class Dropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
countries: [],
currentCountry: ''
};
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick() {
const showCountryData = Fetch(newFetchCountry).showJSONData;
showCountryData().then(res => {
const data = res[0];
this.passCountryData(data);
})
this.passToggleDashboard(true);
}
handleChange(e) {
this.setState({ currentCountry: e.target.value, });
this.passToggleDashboard(false);
}
passCountryData(data) {
this.props.onSetCountryData(data);
}
passToggleDashboard(toggle) {
this.props.onShowDashboard(toggle);
}
componentDidMount() {
let timer = setTimeout(() => {
const showCountryList = Fetch(fetchCountryList).showJSONData;
showCountryList().then(res => {
const data = res.map(country => country);
this.setState({ countries: data })
});
clearTimeout(timer);
}, 2000)
}
render() {
return (
<section className="dropdown-area">
<div className="dropdown">
<label htmlFor="country">Select country:</label>
<input list="countries" name="country" id="country" onChange={this.handleChange} />
<datalist id="countries" required>
{this.state.countries.length ?
this.state.countries.map(country => <option key={country.name}>{country.name}</option>) :
<option disabled>Loading</option>}
</datalist>
</div>
<button className="comp" onClick={this.handleClick}>Search</button>
</section>
)
}
}
// Sibling2
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {
update: '',
confirmed: '',
recovered: '',
deaths: ''
}
};
}
componentDidMount() {
if (!('data' in this.props)) {
const showWorldData = Fetch(fetchWorld).showJSONData;
showWorldData().then(res => {
const data = res[0];
this.setState({
data: {
update: data.lastUpdate,
confirmed: data.confirmed,
recovered: data.recovered,
deaths: data.deaths
}
});
});
}
}
componentDidUpdate() { // Error here!!!
if ('data' in this.props) {
const data = this.props.data;
this.setState({
data: {
update: data.lastUpdate,
confirmed: data.confirmed,
recovered: data.recovered,
deaths: data.deaths
}
});
}
}
render() {
const visibility = {
visibility: 'visible' in this.props && !this.props.visible ? 'hidden' : 'visible'
};
return (
<section className="dashboard-area" style={visibility}>
<span>Last Update: {this.state.data.update || 'Loading...'}</span>
<div className="dashboards">
<DashboardItem num={this.state.data.confirmed} type="Confirmed" />
<DashboardItem num={this.state.data.recovered} type="Recovered" />
<DashboardItem num={this.state.data.deaths} type="Deaths" />
</div>
</section>
)
}
}
class DashboardItem extends React.Component {
render() {
return (
<div className="dashboard">{this.props.type}: <br /><span>{this.props.num || 'Loading...'}</span></div>
)
}
}
Error is in the componentDidMount() in the Dashboard component. I can't find where I fired the re-render infinitely.
The setState method is repeatedly updating the component because every time the 'data' in this.props equals to true you're calling setState and calling setState will by default update the component and componentDidUpdate will check again if 'data' in this.props equals to true and so
You should make strict conditions for if statement
try this
componentDidUpdate(prevProps, prevState) {
if ('data' in this.props && this.props.data !== prevProps.data) {
const data = this.props.data;
this.setState({
data: {
update: data.lastUpdate,
confirmed: data.confirmed,
recovered: data.recovered,
deaths: data.deaths
}
});
}
}
Your issue stems from derived state: state which is made dependent on props and is an anti-pattern in react.
This will tell you more about it:
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
There are some work arounds, but its recommended you instead restructure your data flow.

Error rendering item from state array in React

I have a problem getting state data in the render function.
It works fine and returns the array when I use
console.log(items)
But trying to get first item from the array yields an error
console.log(items[0])
Full code is:
import React from "react";
import StatsSection from "./../components/StatsSection";
import { db } from "./../util/database";
class IndexPage extends React.Component {
constructor(props) {
console.log(props)
super(props);
this.state = {};
}
componentDidMount() {
var data = []
db.collection('test')
.get().then(snapshot => {
snapshot.forEach(doc => {
data.push(doc.data())
})
})
this.setState({
items: data
});
}
render() {
const { items } = this.state
console.log(items[0])
return (
<StatsSection
color="white"
size="medium"
backgroundImage=""
backgroundImageOpacity={1}
items={[
{
title: "Following",
stat: "123"
},
{
title: "Followers",
stat: "456k"
},
{
title: "Likes",
stat: "789"
}
]}
/>
);
}
}
export default IndexPage;
Where am I making the mistake?
You're only setting one item, so items is actually just one item and items[0] fails.
this.setState({
items: data
});
should be inside the .then() so that it only runs after all the items are populated with the .forEach().
Update your componentDidMount() like so:
componentDidMount() {
db.collection('test').get().then(snapshot => {
var data = []
snapshot.forEach(doc => {
data.push(doc.data())
})
this.setState({ items: data })
})
}

React pass from parent to child

I'm trying to pass data in React from parent to child , I already managed to set right value from one file to another, but same that information that I passed I need to pass once more again. I will show you some code so you can understand actual problem.
From List.js file I'm taking the right information like
<Products categoryid={item.id}/>
so that same item.id I passed to Products, as you see I have this.props.categoryid which is giving me right information as value to add this item as you see, and it looks like
import React, { Component } from 'react'
import { getProducts, addItem, deleteItem, updateItem } from './ProductFunctions'
class Products extends Component {
constructor() {
super()
this.state = {
id: '',
title: '',
price: '',
off_price: '',
category_id: '',
arttitle: '',
artbody: '',
editDisabled: false,
items: []
}
this.onSubmit = this.onSubmit.bind(this)
this.onChange = this.onChange.bind(this)
}
componentDidMount() {
this.getAll()
}
onChange = e => {
this.setState({
[e.target.name]: e.target.value
})
}
getAll = () => {
getProducts().then(data => {
this.setState(
{
title: '',
price: '',
off_price: '',
category_id: this.props.categoryid,
items: [...data]
},
() => {
console.log(this.state.items)
}
)
})
}
So the real problem is how to pass this this.props.categoryid as a category_id to getProducts function in ProductFunctions.js so I can get list from ?
export const getProducts = category_id => {
return axios
.get('/api/products/${category_id}', {
headers: { 'Content-Type': 'application/json' }
})
.then(res => {
return res.data
})
}
It seems you forgot to use `` and instead used '' in the getProducts function in ProductFunctions.js, so let's correct that.
export const getProducts = category_id => {
return axios
.get(`/api/products/${category_id}`, {
headers: { "Content-Type": "application/json" }
})
.then(res => {
return res.data;
});
};
Now, just pass the categoryid you obtained from props to the getProducts in the getAll method, when its invoked. (As per what the exported function expects in ProductFunctions.js
getAll = () => {
const { categoryid } = this.props;
getProducts(categoryid).then(data => {
this.setState(
{
title: "",
price: "",
off_price: "",
category_id: categoryid,
items: [...data]
},
() => {
console.log(this.state.items);
}
);
});
};
Access the prop within getAll function
getAll = () => {
getProducts(this.props.categoryid).then(data => {
this.setState({
title: '',
price: '',
off_price: '',
category_id: this.props.categoryid,
items: [...data]
},
() => {
console.log(this.state.items)
}
)
})
}

Toggling nested state object in React

I have a state object that contains an array of objects:
this.state = {
feeling: [
{ name: 'alert', status: false },
{ name: 'calm', status: false },
{ name: 'creative', status: false },
{ name: 'productive', status: false },
{ name: 'relaxed', status: false },
{ name: 'sleepy', status: false },
{ name: 'uplifted', status: false }
]
}
I want to toggle the boolean status from true to false on click event. I built this function as a click handler but it doesn't connect the event into the state change:
buttonToggle = (event) => {
event.persist();
const value = !event.target.value
this.setState( prevState => ({
status: !prevState.status
}))
}
I'm having a hard time following the control flow of the nested React state change, and how the active event makes the jump from the handler to the state object and vice versa.
The whole component:
export default class StatePractice extends React.Component {
constructor() {
super();
this.state = {
feeling: [
{ name: 'alert', status: false },
{ name: 'calm', status: false },
{ name: 'creative', status: false },
{ name: 'productive', status: false },
{ name: 'relaxed', status: false },
{ name: 'sleepy', status: false },
{ name: 'uplifted', status: false }
]
}
}
buttonToggle = (event) => {
event.persist();
const value = !event.target.value
this.setState( prevState => ({
status: !prevState.status
}))
}
render() {
return (
<div>
{ this.state.feeling.map(
(stateObj, index) => {
return <button
key={ index }
onClick={ this.buttonToggle }
value={ stateObj.status } >
{ stateObj.status.toString() }
</button>
}
)
}
</div>
)
}
}
In order to solve your problem, you should first send the index of the element that is going to be modified to your toggle function :
onClick = {this.buttonToggle(index)}
Then tweak the function to receive both the index and the event.
Now, to modify your state array, copy it, change the value you are looking for, and put it back in your state :
buttonToggle = index => event => {
event.persist();
const feeling = [...this.state.feeling]; //Copy your array
feeling[index] = !feeling[index];
this.setState({ feeling });
}
You can also use slice to copy your array, or even directly send a mapped array where only one value is changed.
Updating a nested object in a react state object is tricky. You have to get the entire object from the state in a temporary variable, update the value within that variable and then replace the state with the updated variable.
To do that, your buttonToggle function needs to know which button was pressed.
return <button
key={ index }
onClick={ (event) => this.buttonToggle(event, stateObj.name) }
value={ stateObj.status } >
{ stateObj.status.toString() }
</button>
And your buttonToggle function could look like this
buttonToggle = (event, name) => {
event.persist();
let { feeling } = this.state;
let newFeeling = [];
for (let index in feeling) {
let feel = feeling[index];
if (feel.name == name) {
feel = {name: feel.name, status: !feel.status};
}
newFeeling.push(feel);
}
this.setState({
feeling: newFeeling,
});
}
Here's a working JSFiddle.
Alternatively, if you don't need to store any more data per feeling than "name" and "status", you could rewrite your component state like this:
feeling: {
alert: false,
calm: false,
creative: false,
etc...
}
And buttonToggle:
buttonToggle = (event, name) => {
event.persist();
let { feeling } = this.state;
feeling[name] = !feeling[name];
this.setState({
feeling
});
}
I think you need to update the whole array when get the event. And it is better to not mutate the existing state. I would recommend the following code
export default class StatePractice extends React.Component {
constructor() {
super();
this.state = {
feeling: [
{ name: "alert", status: false },
{ name: "calm", status: false },
{ name: "creative", status: false },
{ name: "productive", status: false },
{ name: "relaxed", status: false },
{ name: "sleepy", status: false },
{ name: "uplifted", status: false },
],
};
}
buttonToggle = (index, value) => (event) => {
event.persist();
const toUpdate = { ...this.state.feeling[index], status: !value };
const feeling = [...this.state.feeling];
feeling.splice(index, 1, toUpdate);
this.setState({
feeling,
});
};
render() {
return (
<div>
{this.state.feeling.map((stateObj, index) => {
return (
<button
key={index}
onClick={this.buttonToggle(index, stateObj.status)}
value={stateObj.status}
>
{stateObj.status.toString()}
</button>
);
})}
</div>
);
}
}

Categories