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
Related
I was trying to create a delete operation on the array of objects (videoData).videoData is getting mapped in the child component along with the DELETE button. At the click of the DELETE button in the child component (childComp).
I want to set the current id to the "childData" state in the child component but it's not getting updated with the current id.
When I am consoling log the childData, in the child component, it still says null which means it was not updated.Why is it not updating?
My own explanation -
When the delete button is getting clicked, the testFunc() is getting fired in the parent component which is removing the item with that particular id from videoData array, and as a result, the id is not able to pass to the child component due to which child component is getting rendered with the original state (null). I don't know if the explanation is correct or not, can someone help me in clearing this up?
function ParentComp() {
const [videoData, setvideoData] = useState([{ id: 2 }, { id: 3 }]);
function testFunc(id) {
let hasMatch = false
if (!hasMatch) {
let arr = videoData.filter(item => {
return item.id !== id
})
setvideoData(arr)
}
}
return (
<childComp testFunc={testFunc} videoData={videoData}/>
)
}
function childComp({testFunc, videoData}) {
const [childData, setchildData] = useState(null)
function ChildFunc(itemId) {
testFunc(itemId)
setchildData(itemId)
}
console.log(childData) //null (state not getting updated)
return (
<>
{videoData.map((item) => {
return (
<button onClick={() => ChildFunc(item.id)}>Delete</button>
);
})}
</>
)
}
In the parent component, I receive data from the server and then map this data into a jsx format. Inside this mapping I have a child component and try to pass a value from state of parent to child as a property, however when I update state of this value, the render function for child is not executed.
Expected behavior: As a user I see a list of items. If I click on an item it should become as checked.
export class ReactSample extends React.Component {
constructor(props){
super(props);
this.state = {
items: [],
mappedItems: [],
selectedIds: [],
isSelected: false,
clickedTripId: null
};
this.toggleSelection = this.toggleSelection.bind(this);
}
componentWillMount(){
console.log("Component mounting")
}
toggleSelection (id, e) {
if(!_.includes(this.state.selectedIds, id)) {
this.setState((state) => ({selectedIds:
state.selectedIds.concat(id)}));
this.setState(() => ({clickedTripId: id}));
this.mapItems(this.state.items);
}
}
componentDidMount() {
const self = this;
MyService.getItems()
.then(res => {
self.setState(() => ({ items: res.allItems }));
self.setState(() => ({ mappedItems:
this.mapItems(res.allItems) }));
}
)
}
mapItems (items) {
return items.map(trip => {
return (
<li key={trip.id} onClick={(e) => (this.toggleSelection(trip.id,
e))}>
<span>{trip.title}</span>
<Tick ticked={this.state.clickedTripId}/>
<span className="close-item"></span>
</li>
);
});
}
getItems() {
}
render() {
return (
<div>
<a className="title">This is a react component!</a>
<Spinner showSpinner={this.state.items.length <= 0}/>
<div className="items-container">
<ul id="itemsList">
{this.state.mappedItems}
</ul>
</div>
</div>
);
}
}
export class Tick extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log('RENDER');
return (<span className={this.props.ticked ? 'tick display' :
'tick hide' }></span>);
}
}
I see a couple issues.
In toggleSelection you aren't doing anything with the result of mapItems. This kind of bug would be much easier to avoid if you just remove mappedItems from state and instead just call mapItems within your render method.
The other issue is you are passing this.state.clickedTripId as the ticked property. I assume you meant to pass something more like this.state.clickedTripId === trip.id.
As Ryan already said, the problem was that mappedItems where not updated when toggleSelection was clicked. As it is obvious from the code mapItems returns data in jsx format. To update it I had to call this.setState({mappedItems: this.mapItems(this.state.items)}) which means that I call mapItems and then I assign the result to the state. In this case my list will be updated and Tick component will receive this.state.clickedItemId as a tick property. There is one more issue that needs to be done to make this code working:
this mapped list needs to be updated after this.state.clickedItemId is updated. The method setState is asynchronous which means that this.setState({mappedItems: this.mapItems(this.state.items)}) has to be called only after this.state.clickedItemId is updated. To achieve this, the setState method can receive a callback function as a second parameter. The code snippet is the following:
toggleSelection (id, e) {
if(!_.includes(this.state.selectedIds, id)) {
this.setState((state) => ({
clickedItemId: id,
selectedIds: state.selectedIds.concat(id)
}), () => this.setState({mappedItems: this.mapItems(this.state.items)}));
}
}
In this case, at the time the mapItems function is executed all data from the state that is needed here will be already updated:
mapItems (items) {
return items.map(item => {
return (
<li key={item.id} onClick={(e) => (this.toggleSelection(item.id, e))}>
<span>{item.title}</span>
<span>{this.state.clickedItemId}</span>
<Tick ticked={this.state.clickedItemId === item.id}/>
<span className="close-item"></span>
</li>
);
});
}
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.
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