Some one Please Help me with this error
Cannot update during an existing state transition
When I am rendering this I'm getting error like below
Cannot update during an existing state transition (such as within
render or another component's constructor). Render methods should be
a pure function of props and state; constructor side-effects are an
anti-pattern, but can be moved to componentWillMount.
I have tried putting this.props.ifListChanged(this); this code inside the componentWillUpdate and componentDidUpadate but it is taking too much time but without errors(almost 2 mins).
import React from 'react';
import ListItemComponent from './ListItem.jsx';
import DropDownButtonComponent from './DropDownButton.jsx';
import DropDownStyle from '../../../../css/sass/drop-down.scss';
module.exports = React.createClass({
handleClick: function () {
this.setState({open: !this.state.open});
},
getInitialState: function () {
return {
open: false,
//listItems: this.props.listItems,
selectedItems:[],
title: this.props.dropdownTitle
}
},
handleItemClick: function (item) {
var selectedItems = [];
if(this.props.multiple == true){
selectedItems = this.state.selectedItems;
if(selectedItems.indexOf(item)==-1){
selectedItems.push(item);
}else{
selectedItems.splice(selectedItems.indexOf(item),1)
}
this.setState({
title: this.state.selectedItems.length+" selected",
selectedItems: selectedItems
});
} else{
selectedItems = [];
selectedItems.push(item);
this.setState({
title: item,
selectedItems: selectedItems,
open: false
});
}
//this.sendStateToParent();
},
sendStateToParent: function(){
this.props.ifListChanged(this);
},
handleTextChange: function (event) {
var filteredItems = [];
this.props.listItems.map(function(item){
if(item.toLowerCase().search(event.target.value.toLowerCase()) != -1){
filteredItems.push(item);
}
},this);
this.setState({
listItems: filteredItems
});
},
clearSelected: function(){
this.setState({
title: this.props.dropdownTitle,
selectedItems: [],
});
},
render: function () {
this.props.ifListChanged(this);
var index = 0;
var list=[];
if (this.state.listItems != undefined) {
list = this.state.listItems.map(function (item) {
return (
<ListItemComponent
key={index++}
item={item}
whenItemClicked={this.handleItemClick}
className={this.state.selectedItems.indexOf(item) != -1 ? "active" : ""}
/>);
}.bind(this));
} else {
list = this.props.listItems.map(function (item) {
return (
<ListItemComponent
key={index++}
item={item}
whenItemClicked={this.handleItemClick}
className={this.state.selectedItems.indexOf(item) != -1 ? "active" : ""}
/>);
}.bind(this));
}
return <div className="btn-group bootstrap-select form-control">
<DropDownButtonComponent
whenClicked={this.handleClick}
title={this.state.title}
/>
<ul className={"dropdown-menu inner dropdown-menu " + (this.state.open ? "show" : "") }>
{this.props.search? <li><input type="text" style={{margin:"auto", maxWidth:"96%"}} onChange={this.handleTextChange} placeholder="Search"/></li> :""}
<li className="disabled"><a>Select from below list {this.props.multiple ? <i title="clear all" style={{fontSize:"15px"}} onClick={this.clearSelected} className="text-danger fa fa-ban pull-right"></i>: ""}</a></li>
{list}
</ul>
</div>
}
});
ListItem.jsx
import React from 'react';
module.exports = React.createClass({
handleClick: function() {
this.props.whenItemClicked(this.props.item);
},
render: function() {
return <li onClick={this.handleClick} className={this.props.className}>
<a>{this.props.item}</a>
</li>
}
});
DropDownButton.jsx
import React from 'react';
module.exports = React.createClass({
render: function() {
return <button onClick={this.props.whenClicked} className="btn dropdown-toggle btn-default" type="button">
<span className="filter-option pull-left">{this.props.title}</span>
<span className="bs-caret"><i className="fa fa-chevron-down"></i></span>
</button>
}
});
Advance thanks to the one who helps me. Thank you.
I think you have a prop/state design issue. Your this.props.ifListChanged(this) inside your render function is very suspicious. Your render function should NOT need to signal anything to parent. Parent already knows all the props it sent down, and if parent needs to know about the state, then it most likely should not be state in the first place.
From what I can gather from your code,
your List component receives an unfiltered list of items as props
and it has a state that keeps track of filtereditems and of selecteditems.
This is a nice setup if the result of both need to be sent somewhere with another action inside the component itself (e.g. a process-selection button or something).
Then (and only then) would you send the state to parent or to somewhere else.
If the parent needs to know about both all the time (for instance when the process-button or process-action is somewhere else), then it is better to:
define some handleFilterUpdate and 'handleSelectionUpdate` methods inside the parent and pass these as props to the child.
also pass the filtered list and selection from the parent to the child.
call the this.props.handleFilterUpdate and 'this.props.handleSelectionUpdate` from the child whenever something happens with selection or filters.
Related
I have a modal displaying data that I'm receiving in props. When I open the modal, I should see select data displayed from my props. However, the modal is empty the first time I open it, and populates the second time.
If I go on to change the data in props, the modal stays the same on the first new click, and refreshes on the second new click.
I've tried forcing it with setTimeout, messing with combos of componentDidMount, componentDidUpdate, and other lifecycle methods, but nothing seems to work. I'm sure it has something to do with my using the prevData param in componentDidMount. But even thought react devtools shows this.state.pricesData updates, when I try rendering from state I get blanks every time. When I invoke a console log as a callback of setState, I get an empty array bracket in the console log (which I can expand to show all the correct array data, but I guess that's populated async after the log).
Here's the code:
import React, { Component } from "react";
import "../../App.css";
let explanations = [];
export default class ExplanationModal extends Component {
constructor(props) {
super(props);
this.state = {
pricesData: [],
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.pricesData !== prevState.pricesData) {
return { pricesData: nextProps.pricesData };
} else {
return null;
}
}
// to allow for async rendering
getSnapshotBeforeUpdate(prevProps) {
if (prevProps.pricesData !== this.state.pricesData) {
return this.state.pricesData;
}
}
componentDidMount = () => {
this.setState({ pricesData: this.props.pricesData }, () =>
console.log(this.state.pricesData)
);
};
componentDidUpdate = (prevData) => {
this.renderExp(prevData.pricesData);
};
renderExp = (data) => {
explanations = [];
data.forEach((set) =>
explanations.push({ title: set.titel, explanation: set.explenation })
);
};
onClose = () => {
this.props.hideModal();
};
render() {
return (
<div className="modal">
<div>
{explanations.map((item) => (
<span>
<h4>{item.title}</h4>
<p>{item.explanation}</p>
</span>
))}
</div>
<button onClick={this.onClose} className="update">
Close
</button>
</div>
);
}
}
you have to keep your explanation array in your state. then update the state when new data arrives. because react doesn't trigger a re render if you don't update the state .
your constructor should be
super(props);
this.state = {
pricesData: [],
explanations : []
};
}
and your renderExp function should be
renderExp = (data) => {
explanations = [];
data.forEach((set) =>
explanations.push({ title: set.titel, explanation: set.explenation })
);
this.setState({ explanations })
};
inside your render function
render() {
return (
<div className="modal">
<div>
{this.state.explanations.map((item) => (
<span>
<h4>{item.title}</h4>
<p>{item.explanation}</p>
</span>
))}
</div>
<button onClick={this.onClose} className="update">
Close
</button>
</div>
);
}
}
This way you will get the updated data when it arrives.
When I'm sending state of the child component to its parent component, React is sending old state to the parent component.
I want to send the updated state on every click on ListItem which is properly working and calling the function handleItemClick.
But when I'm calling sendStateToParent. It is passing old state of it. Suppose I have clicked on ITEM1, it is sending empty array[]. Next I have clicked on ITEM2, it is sending array [ITEM1].
Here I'm actually creating a multiselect dropdown. which also can act as single select based on props it is getting.
import React from 'react';
import ListItemComponent from './ListItem.jsx';
import DropDownButtonComponent from './DropDownButton.jsx';
import DropDownStyle from '../../../../css/sass/drop-down.scss';
module.exports = React.createClass({
handleClick: function () {
this.setState({open: !this.state.open});
},
getInitialState: function () {
return {
open: false,
//listItems: this.props.listItems,
selectedItems:[],
title: this.props.dropdownTitle
}
},
handleItemClick: function (item) {
var selectedItems = [];
if(this.props.multiple == true){
selectedItems = this.state.selectedItems;
if(selectedItems.indexOf(item)==-1){
selectedItems.push(item);
}else{
selectedItems.splice(selectedItems.indexOf(item),1)
}
this.setState({
title: this.state.selectedItems.length+" selected",
selectedItems: selectedItems
});
} else{
selectedItems = [];
selectedItems.push(item);
this.setState({
title: item,
selectedItems: selectedItems,
open: false
});
}
this.sendStateToParent();
},
sendStateToParent: function(){
this.props.ifListChanged(this);
},
handleTextChange: function (event) {
var filteredItems = [];
this.props.listItems.map(function(item){
if(item.toLowerCase().search(event.target.value.toLowerCase()) != -1){
filteredItems.push(item);
}
},this);
this.setState({
listItems: filteredItems
});
},
clearSelected: function(){
this.setState({
title: this.props.dropdownTitle,
selectedItems: [],
});
},
render: function () {
var index = 0;
var list=[];
if (this.state.listItems != undefined) {
list = this.state.listItems.map(function (item) {
return (
<ListItemComponent
key={index++}
item={item}
whenItemClicked={this.handleItemClick}
className={this.state.selectedItems.indexOf(item) != -1 ? "active" : ""}
/>);
}.bind(this));
} else {
list = this.props.listItems.map(function (item) {
return (
<ListItemComponent
key={index++}
item={item}
whenItemClicked={this.handleItemClick}
className={this.state.selectedItems.indexOf(item) != -1 ? "active" : ""}
/>);
}.bind(this));
}
return <div className="btn-group bootstrap-select form-control">
<DropDownButtonComponent
whenClicked={this.handleClick}
title={this.state.title}
/>
<ul className={"dropdown-menu inner dropdown-menu " + (this.state.open ? "show" : "") }>
{this.props.search? <li><input type="text" style={{margin:"auto", maxWidth:"96%"}} onChange={this.handleTextChange} placeholder="Search"/></li> :""}
<li className="disabled"><a>Select from below list {this.props.multiple ? <i title="clear all" style={{fontSize:"15px"}} onClick={this.clearSelected} className="text-danger fa fa-ban pull-right"></i>: ""}</a></li>
{list}
</ul>
</div>
}
});
Thanks in advance
The reason parent gets the old value of selecteditems, is because setState() is an asynchronous operation. See explanation here:
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value.
So in your code, you send a request to update state, and before the new state is processed, you call the method in the parent, to inform parent about state, which still has the old items.
Fix 1: send current state to parent.
To get the item to send the current state, you could use the callback that setState() provides. Explained also in react pages:
The second (optional) parameter is a callback function that will be
executed once setState is completed and the component is re-rendered.
This ensures that the call to parent is made only after setState() is finished. Something like this:
this.setState(
{
title: item,
selectedItems: selectedItems,
open: false
},
this.sendStateToParent
);
Fix 2(optional but recommended): move selecteditems state to parent.
If your parent needs to know about selecteditems, I would advise not to put these in state of the list. The only thing your component does with the selecteditems is to send them to the parent, every time an item is clicked.
Instead, it is better to:
put selectedItems in state of the parent
from the parent, pass selectedItems as props to the component
move the handleItemClick logic to the parent
inside the parent, you update the list of selectedItems, and set state (of the parent)
triggering a re-render of the list, with the new selectedItems as props
I am trying to make a local variable for each component instance, something like a little cache because it is storing information that toggles something and does not need to be on the state/store. So I have attempted it like so:
Setting a default prop to keep my info in :
getDefaultProps: function getDefaultProps() {
return {
showPreviewModal: { value: false,
writable: true
}
};
},
Setting a function to toggle this prop :
togglePreviewModal: function togglePreviewModal() {
this.props.showPreviewModal = !this.props.showPreviewModal;
},
And having that function fired by a click function
<button className="btn btn-default btn-blue previewAsset" onClick={() => this.togglePreviewModal() }>
I thought this would work in theory, but the onclick is firing immedietly on render. I googled this issue and it seems like the best result is to change the click function to :
{() => { this.props.togglePreviewModal() }}
However this does not work either, the click function is still firing immediately.
You can't directly mutate props like that - since props are explicitly passed down from a parent component, you'd need to change the prop at the source. Presumably, it originates from the state of a component somewhere up the hierarchy.
To do that, you'd pass down a function along with the prop that changes it at the source (using setState()). Here's an example:
var ParentComponent = React.createClass({
togglePreviewModal: function() {
this.setState({
showPreviewModal: !this.state.showPreviewModal
};
},
getInitialState: function() {
return {
// Unnecessary but providing for clarity
showPreviewModal: false
};
},
render: function() {
// This is for whatever values you were mapping over
var childComponents = ...map(function() {
return <ChildComponent togglePreviewModal={this.togglePreviewModal} />;
});
if (this.state.showPreviewModal) {
return (<div>
<Modal />
{childComponents}
</div>);
} else {
return (<div>
{childComponents}
</div>);
}
}
});
var ChildComponent = React.createClass({
render: function() {
return <button
className="btn btn-default btn-blue previewAsset"
onClick={this.props.togglePreviewModal} />;
}
});
Note that I'm not invoking the function in the onClick of the <button>, just passing in the prop (which is a function).
The Parent (MyList in my example) component renders an array thru a Child (MyComponent) component. Parent decides to change properties in the array, what is React way of triggering child re-rendering?
All I came up with is this.setState({}); in Parent after tweaking the data. Is this a hack or a React way of triggering an update?
JS Fiddle:
https://jsfiddle.net/69z2wepo/7601/
var items = [
{id: 1, highlighted: false, text: "item1"},
{id: 2, highlighted: true, text: "item2"},
{id: 3, highlighted: false, text: "item3"},
];
var MyComponent = React.createClass({
render: function() {
return <div className={this.props.highlighted ? 'light-it-up' : ''}>{this.props.text}</div>;
}
});
var MyList = React.createClass({
toggleHighlight: function() {
this.props.items.forEach(function(v){
v.highlighted = !v.highlighted;
});
// Children must re-render
// IS THIS CORRECT?
this.setState({});
},
render: function() {
return <div>
<button onClick={this.toggleHighlight}>Toggle highlight</button>
{this.props.items.map(function(item) {
return <MyComponent key={item.id} text={item.text} highlighted={item.highlighted}/>;
})}
</div>;
}
});
React.render(<MyList items={items}/>, document.getElementById('container'));
The problem here is that you're storing state in this.props instead of this.state. Since this component is mutating items, items is state and should be stored in this.state. (Here's a good article on props vs. state.) This solves your rendering problem, because when you update items you'll call setState, which will automatically trigger a re-render.
Here's what your component would look like using state instead of props:
var MyList = React.createClass({
getInitialState: function() {
return { items: this.props.initialItems };
},
toggleHighlight: function() {
var newItems = this.state.items.map(function (item) {
item.highlighted = !item.highlighted;
return item;
});
this.setState({ items: newItems });
},
render: function() {
return (
<div>
<button onClick={this.toggleHighlight}>Toggle highlight</button>
{ this.state.items.map(function(item) {
return <MyComponent key={item.id} text={item.text}
highlighted={item.highlighted}/>;
}) }
</div>
);
}
});
React.render( <MyList initialItems={initialItems}/>,
document.getElementById('container') );
Note that I renamed the items prop to initialItems, because it makes it clear that MyList will mutate it. This is recommended by the documentation.
You can see the updated fiddle here: https://jsfiddle.net/kxrf5329/
I have found a nice solution using key attribute for re-render with React Hook. If we changed key property of a child component or some portion of React Component, it will re-render entirely. It will use when you need to re-render some portion of React Component of re-render a child component. Here is a example. I will re-render the full component.
import React, { useState, useEffect } from "react";
import { PrEditInput } from "./shared";
const BucketInput = ({ bucketPrice = [], handleBucketsUpdate, mood }) => {
const data = Array.isArray(bucketPrice) ? bucketPrice : [];
const [state, setState] = useState(Date.now());
useEffect(() => {
setState(Date.now());
}, [mood, bucketPrice]);
return (
<span key={state}>
{data.map((item) => (
<PrEditInput
key={item.id}
label={item?.bucket?.name}
name={item.bucketId}
defaultValue={item.price}
onChange={handleBucketsUpdate}
mood={mood}
/>
))}
</span>
);
};
export default BucketInput;
An easy option to re-render a child is to update a unique key attribute every time you need a re-render.
<ChildComponent key={this.state.updatedKey}/>
You should trigger a re-rendering by calling setState() and giving the new props you want to propagate down.
If you really want to force an update you can also call forceUpdate().
If you look at the examples on this page, you can see that setState is the method used to update and trigger a re-rendering. The documentation is also stating (ahaha!) that clearly.
In your case I would call forceUpdate.
EDIT: As Jordan mentioned in the comment, it would be better to store items as part of your state. That way you wouldn't have to call forceUpdate but you would really update the state of your component, thus a regular setState with the updated values would work better.
You can set a numeric key on the child component and trigger a key change once an action is performed. e.g
state = {
childKey: 7,
};
<ChildComponent key={this.state.childKey}/>
actionToTriggerReload = () => {
const newKey = this.state.childKey * 89; // this will make sure the key are never the same
this.setState({childKey: newKey})
}
This will surely re-render the ChildComponent
Set a numeric default 'key' in the child component and to re-render just change key value.
this.state = {
updatedKey: 1,
};
triggerReload = () => {
let newKey = Math.floor(Math.random() * 100); // make sure the key are never the same
this.setState({updatedKey: newKey})
}
<childComponent key={this.state.updatedKey} handlerProp = {this.onClickItemEvent} />
This worked for me to re-render the ChildComponent in reactjs class base
The Parent (MyList in my example) component renders an array thru a Child (MyComponent) component. Parent decides to change properties in the array, what is React way of triggering child re-rendering?
All I came up with is this.setState({}); in Parent after tweaking the data. Is this a hack or a React way of triggering an update?
JS Fiddle:
https://jsfiddle.net/69z2wepo/7601/
var items = [
{id: 1, highlighted: false, text: "item1"},
{id: 2, highlighted: true, text: "item2"},
{id: 3, highlighted: false, text: "item3"},
];
var MyComponent = React.createClass({
render: function() {
return <div className={this.props.highlighted ? 'light-it-up' : ''}>{this.props.text}</div>;
}
});
var MyList = React.createClass({
toggleHighlight: function() {
this.props.items.forEach(function(v){
v.highlighted = !v.highlighted;
});
// Children must re-render
// IS THIS CORRECT?
this.setState({});
},
render: function() {
return <div>
<button onClick={this.toggleHighlight}>Toggle highlight</button>
{this.props.items.map(function(item) {
return <MyComponent key={item.id} text={item.text} highlighted={item.highlighted}/>;
})}
</div>;
}
});
React.render(<MyList items={items}/>, document.getElementById('container'));
The problem here is that you're storing state in this.props instead of this.state. Since this component is mutating items, items is state and should be stored in this.state. (Here's a good article on props vs. state.) This solves your rendering problem, because when you update items you'll call setState, which will automatically trigger a re-render.
Here's what your component would look like using state instead of props:
var MyList = React.createClass({
getInitialState: function() {
return { items: this.props.initialItems };
},
toggleHighlight: function() {
var newItems = this.state.items.map(function (item) {
item.highlighted = !item.highlighted;
return item;
});
this.setState({ items: newItems });
},
render: function() {
return (
<div>
<button onClick={this.toggleHighlight}>Toggle highlight</button>
{ this.state.items.map(function(item) {
return <MyComponent key={item.id} text={item.text}
highlighted={item.highlighted}/>;
}) }
</div>
);
}
});
React.render( <MyList initialItems={initialItems}/>,
document.getElementById('container') );
Note that I renamed the items prop to initialItems, because it makes it clear that MyList will mutate it. This is recommended by the documentation.
You can see the updated fiddle here: https://jsfiddle.net/kxrf5329/
I have found a nice solution using key attribute for re-render with React Hook. If we changed key property of a child component or some portion of React Component, it will re-render entirely. It will use when you need to re-render some portion of React Component of re-render a child component. Here is a example. I will re-render the full component.
import React, { useState, useEffect } from "react";
import { PrEditInput } from "./shared";
const BucketInput = ({ bucketPrice = [], handleBucketsUpdate, mood }) => {
const data = Array.isArray(bucketPrice) ? bucketPrice : [];
const [state, setState] = useState(Date.now());
useEffect(() => {
setState(Date.now());
}, [mood, bucketPrice]);
return (
<span key={state}>
{data.map((item) => (
<PrEditInput
key={item.id}
label={item?.bucket?.name}
name={item.bucketId}
defaultValue={item.price}
onChange={handleBucketsUpdate}
mood={mood}
/>
))}
</span>
);
};
export default BucketInput;
An easy option to re-render a child is to update a unique key attribute every time you need a re-render.
<ChildComponent key={this.state.updatedKey}/>
You should trigger a re-rendering by calling setState() and giving the new props you want to propagate down.
If you really want to force an update you can also call forceUpdate().
If you look at the examples on this page, you can see that setState is the method used to update and trigger a re-rendering. The documentation is also stating (ahaha!) that clearly.
In your case I would call forceUpdate.
EDIT: As Jordan mentioned in the comment, it would be better to store items as part of your state. That way you wouldn't have to call forceUpdate but you would really update the state of your component, thus a regular setState with the updated values would work better.
You can set a numeric key on the child component and trigger a key change once an action is performed. e.g
state = {
childKey: 7,
};
<ChildComponent key={this.state.childKey}/>
actionToTriggerReload = () => {
const newKey = this.state.childKey * 89; // this will make sure the key are never the same
this.setState({childKey: newKey})
}
This will surely re-render the ChildComponent
Set a numeric default 'key' in the child component and to re-render just change key value.
this.state = {
updatedKey: 1,
};
triggerReload = () => {
let newKey = Math.floor(Math.random() * 100); // make sure the key are never the same
this.setState({updatedKey: newKey})
}
<childComponent key={this.state.updatedKey} handlerProp = {this.onClickItemEvent} />
This worked for me to re-render the ChildComponent in reactjs class base