I am trying to change the state in a class component by using setState.
More specific I have a table, and I want to edit/update one of its elements. For this case, I am passing the indeces to the handleTableFieldOnChange function for the position of the value in the array.
Since I know that I should not mutate the state, I used an external library to deep copy the tables array/list.
The deep copy and the new value assignment works. The deep copy worked also with the JSON.parse(JSON.stringify(this.state.tables)); alternative.
Problem: For some reason the this.setState(...) does not change the tables value.
I do know the setState is asynchronous, this is why I used the callback and within it, the console.log(...) to check the updated value.
console.log(...) still emits the old value.
private handleTableFieldOnChange(val: boolean | string | number | [number, string], tblRowIndex: number, tblIndex: number, tblColINdex: number) {
const cloneDeep = require('lodash.clonedeep');
const newTables = cloneDeep(this.state.tables);
if (newTables && newTables[tblIndex] && newTables[tblIndex].items ) {
newTables[tblIndex].items![tblRowIndex][tblColINdex].value = val;
}
this.setState( {tables: newTables}, () => {
console.log(this.state.tables)
})
}
state: State = {
tables: [],
report: this.props.report,
};
constructor(props: DetailProp, state: State) {
super(props, state);
this.initFieldsAndTabels();
}
private initFieldsAndTabels() {
if (this.state.report && this.state.report.extraction_items) {
this.state.tables = [];
this.state.report.extraction_items.forEach((extractionItems) => {
this.state.tables.push(extractionItems);
});
}
}
The code in handleTableFieldOnChange looks fine to me.
However in initFieldsAndTabels you are applying push on state directly instead of calling setState which may probably cause the issues:
this.state.report.extraction_items.forEach((extractionItems) => {
this.state.tables.push(extractionItems); //#HERE
});
Also as React.Component docs state you should not call setState in constructor (you are calling initFieldsAndTabels in constructor. Instead you could use componentDidMount.
P.S. If you want to add those extraction items in the constructor then you need something like this:
constructor(props) {
super(props);
// method should return a new array/object, but not modify state
const tables = this.initFieldsAndTabels();
this.state = {
tables,
}
}
Related
I have a stateful React Component, And i want to access a value of state form inside the state object, specifically number_rows.
class Contracts extends React.Component{
constructor(props) {
super(props);
this.state = {
number_rows: 30, //starting default
data: props.data
.sort((a, b) => this.high_to_low(a, b, "latestVolume"))
.slice(0, this.state.number_rows)
};
}
render(){
return(
...
)
}
}
export default Contracts;
I cant get my slice() to read the number of rows set in this.state.number_rows
example
b={a:'a', c:b.a}
{a: "a", c: "a"}
I tried state.number_rows and this.number_rows Is this even possible? Is there a work around??
Thanks for your help!!
numbers_rows is a constant or may be changed?
If it's a constant, consider moving it out of your state since it's technically not the state of the application.
Perhaps do something like
const NUMBERS_ROWS = 30; above the Contracts component or save that variable inside your constants file(s) and import that variable on the component.
If it can be changed.
You can make your initial state of data to empty array, then you have 2 choice depending on your circumstances.
If props.data is a fetched value from an API, and the value can either be null or empty array if the fetching still in progress, use componentDidUpdate to update your data state according to props.data value after the value is already fetched.
If you're pretty sure the props.data won't be a null/empty array and already populated before the component is mounted, you can use componentDidMount to set the data state, basically you just move the logic of both sorting and slicing to either componentDidMount or componentDidUpdate based on your circumstances.
Maybe you can use a default number rows variable to initialize your component's state first. After that, when you need to change your number_rows variable, just call the alterNumberRows function and calculate the data using the new number_rows value, and then update the state with the newly calculated number_rows and data.
class Contracts extends React.Component {
constructor(props) {
super(props);
const DEFAULT_NUMBER_ROWS = 30;
this.state = {
number_rows: DEFAULT_NUMBER_ROWS, // starting default
data: props.data
.sort((a, b) => this.high_to_low(a, b, "latestVolume"))
.slice(0, DEFAULT_NUMBER_ROWS),
};
}
alterNumberRows = () => {
// calculate the new number_rows
// I just add one to the previous number_rows for simplicity
const number_rows = this.state.number_rows + 1;
// use the newly calculated number_rows to slice a new data array
const data = this.props.data
.sort((a, b) => this.high_to_low(a, b, "latestVolume"))
.slice(0, number_rows);
this.setState({
number_rows,
data,
});
}
render() {
return (
...
);
}
}
Is it possible to pass all state except one property? I mean I have component which looks like that, and I wish to pass to redux function this.props.editAnimal() all state except property "submitted", is it possible to somehow exclude one state property and pass all the others?
export class EditAnimal extends Component {
constructor(props) {
super(props)
this.state = {
animalName: '',
animalNumber: '',
animalChip: '',
animalAge: '',
extraInfo: '',
submited: false
}
}
handleSubmit = (e) => {
const id = this.props.id
e.preventDefault();
console.log(this.state);
this.props.editAnimal(id, this.state)
}
There are many ways of doing this. Here is one:
const tempState = {...this.state};
delete tempState.submited;
this.props.editAnimal(id, tempState);
First, this creates a copy of the state by destructuring the state into a temporary variable tempState. Then, we remove the property we don't want from the temporary variable with delete.
Sidenote: you have misspelled "submitted", it's with double "t".
If you are transpiling your code with babel, you can destructure the state and copy the variables you only care about:
const { submited, ...editAnimalParams } = this.state;
this.props.editAnimal(id, editAnimalParams)
You can see here what babel transpiles down to, but basically skips any keys you don't want to copy.
If you're not using babel (probably unlikely), then you can do a bit more verbosely but wouldn't need babel:
const editAnimalParams = Object.assign({}, this.state);
delete editAnimalParams.submited;
this.props.editAnimal(id, editAnimalParams)
I've been working with new lifecycles of React v16. It works great when we compare only a single key. But when it comes to compare a large data structures like an Arrays of objects, the deep comparison will become very costly.
I have use case like this in which I have an array ob objects stored in redux,
const readings =[
{
id: ...,
name: ...',
unit:...,
value: ...,
timestamp: ...,
active: true,
},
...
]
Whenever active state of any objects get changed I dispatch an action to update redux state to be same for all connected components with that reducer.
class Readings extends Component {
state = {
readings:[],
};
static getDerivedStateFromProps(nextProps, prevState) {
if ( // comparsion of readings array with prevState) {
return {
readings: nextProps.readings,
};
}
return null;
}
componentDidUpdate(prevProps) {
if ( // comparsion of readings array with prevState) {
// perform an operation here to manipulate new props and setState to re render the comp
// this causes infinite loop
}
}
render() {
...
}
}
const mapStateToProps = state => ({
readings: state.readings.readings,
});
export default connect(
mapStateToProps,
)(Readings));
How can I avoid infinite loop on setState in componentDidUpdate, I don't want to do deep comparison of readings array. Is there a better solution to handle this case?
Your suggestions will be highly appreciated.
Ideally, you make immutable changes to your reducer and keep the
reducer state level low.
So if your array consists of many objects and you need to dispatch based on some attribute change, you should replace the whole readings array using spread operator or using some immutable library e.g immutablejs. Then in your componentDidupdate you can have something like :
componentDidUpdate(prevProps) {
const {
readings,
} = this.props
const {
readings: prevReadings,
} = prevProps
if (readings !== prevReadings) {
//dispatch something
}
}
Thanks feedbacks are welcome.
First read another answer, If that didn't work for you, then:
Make sure you're comparing two arrays with :
componentDidUpdate(prevProps){
if(JSON.stringify(prevProps.todos) !== JSON.stringify(this.props.todos){...}
}
Make sure you are changing the passed prop (state in parent) after deep cloning it:
let newtodos = JSON.parse(JSON.stringify(this.state.todos));
newtodos[0].text = 'changed text';
this.setState({ todos : newtodos });
(Note that Shallow clone doesn't work, since that way you change the objects directly)
Currently I get my data from an API in a JSON-format when running my saga. The fetching process begins, when the component did mount. That means the component renders two times.
Now, when the data is available as props. I can use it in order to render it.
My approach to this is like following, I have got a:
Constructor with the initial state
I fetch data in "componentDidMount"
I got a function that takes the JSON properties from props and puts it into new variables
I run this function in my render() function, when the props contain the fetched data
The Problem in this approach: Once the component runs the function where the data becomes "structured", the render-function loops and then after some time, the values of the properties get displayed with a warning message in the console.
My Questions:
How to prevent the looping when render() runs once?
How can I design this, so that particular properties of the fetched object merge into a new object and how to
I hope I described the most important things about my issue. Here is the code:
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
deviceInfo: {
name: "Initial Name",
batLevel: "78%",
}
}
}
componentDidMount() {
this.props.requestApiData();
}
updateDeviceInfoWithState (){
const devices = (this.props.data.data);
if(devices){
const newDeviceInfo = this.state.deviceInfo;
newDeviceInfo.name = devices[0].shadow.desired.payload.refAppData.name;
newDeviceInfo.batLevel = devices[0].shadow.reported.payload.refAppData.batteryState.level;
this.setState({
deviceInfo: newDeviceInfo,
});
}
}
render() {
this.updateDeviceInfoWithState()
return (
<div className='container'>
<p> {this.state.deviceInfo.name} </p>
<p> {this.state.deviceInfo.batLevel} </p>
</div>
)
}...
Updating the state in the render method is not a good practice, since it might cause an infinite loop.
In your case state is redundant, since you only take the data from props, or replace it with defaults. Instead of using the state return the name and batLevel in the updateDeviceInfoWithState method, and use it in the render method.
Example (not tested):
class Dashboard extends React.Component {
componentDidMount() {
this.props.requestApiData();
}
updateDeviceInfoWithState (){
const devices = this.props.data.data;
if(devices){
const device = devices[0].shadow;
return {
name: device.desired.payload.refAppData.name,
batLevel: device.reported.payload.refAppData.batteryState.level
};
}
return {
name: "Initial Name",
batLevel: "78%",
};
}
render() {
const { name, batLevel } = this.updateDeviceInfoWithState();
return (
<div className='container'>
<p> {name} </p>
<p> {batLevel} </p>
</div>
);
}...
Note 1: If you want to decouple your component from the state, it's better to enforce simple properties as input for the data. For example, this component needs as properties the name and batLevel. It doesn't need to be aware of the array of devices, shadow, payload, etc... You can prepare the data when you receive it in the saga, or use a redux selector in mapStateToProps.
Note 2: If you really need the data in your state, you can use the getDerivedStateFromProps life-cycle method (React 16.3), or update the state in the componentWillReceiveProps if you use an older version.
For this case you can use ComponentWillRecieveProps method like this
componentWillRecieveProps(nextProps) {
// Condition as per ur requirement.
If(this.props.data != nextProps.data) {
this.updateDeviceInfoWithState(nextProps)
}
}
This method will only run whenever ur component props are changed.
I have a component with two functions which should update state object:
class Categories extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
categoryData: [],
objects: [],
object:[],
};
}
componentDidMount() {
this.setState({
data:data.Dluga,
categoryData: data.Dluga.basic,
objects:data,
})
}
changeCategory(event) {
event.preventDefault();
this.setState({
categoryData: this.state.data[(event.currentTarget.textContent).split(' ')[1]],
});
}
changeObject(event) {
event.preventDefault();
const objectOne = Object.assign({}, this.state.objects[event.currentTarget.parentElement.parentElement.children[0].children[0].value]);
this.setState({
objects: this.state.objects,
object:objectOne,
});
};
render() {
return (
<div className='categories'>
<SelectObejct onValueChange={this.changeObject}/>
<ul>
{Object.keys(this.state.data).map((item) => {
return (
<li className='category' key={item}
onClick={this.changeCategory.bind(this)}>
<span className='category-item'> {item}</span>
</li>
)})
}
</ul>
<div>
<CategoryData categoryData={this.state.categoryData}/>
</div>
</div>
);
}
}
When I update state with changeObject I have in state object two properties: objects and object, but initially it was 4 properties... Next when I update state with changeCategory I have initial properties from componentDidMount and updated categoryData but object is empty... I can't update state in one function because it's two onClick elements. What should I do to update state correctly?
The primary thing you're doing incorrectly is updating state based on existing state without using the callback version of setState. State updates can be asynchronous, and can be combined (batched). Any time you're setting state derived from the current state, you must use the callback form. E.g.:
changeCategory(event) {
event.preventDefault();
this.setState(prevState = > {
return {
categoryData: prevState.data[(event.currentTarget.textContent).split(' ')[1]]
};
});
}
Note that we're passing it a function, which will get called later (only a tiny bit later, but later), and will get the then-current state passed to it as a parameter; and we return the new state as a return value.
When I update state with changeObject I have in state object two properties: objects and object, but initially it was 4 properties...
That's absolutely normal. It's common to only specify a subset of your state properties when calling setState. In fact, changeObject should be:
changeObject(event) {
event.preventDefault();
this.setState(prevState => {
const objectOne = Object.assign({}, prevState.objects[event.currentTarget.parentElement.parentElement.children[0].children[0].value]);
return { object: objectOne };
});
}
Note that I didn't specify objects: prevState.objects. There's no reason to if you're not changing it.
Next when I update state with changeCategory I have initial properties from componentDidMount and updated categoryData but object is empty.
object will only be empty (whatever "empty" means) if you set it to that at some point. I suspect resolving the above will resolve this issue, but if not, and if you can't figure it out with further debugging, I suggest posting a new question with an [mcve] demonstrating that problem (you can do a runnable one with Stack Snippets; here's how).