Why is shouldComponentUpdate not firing despite my props changing? - reactjs

I'm trying to figure out why the shouldComponentUpdate method doesn't fire.
Here are what my props looks like:
Object
children: [Object, Object, Object, Object, Object] (5)
data: Array (2)
0 {id: 1, activated: true}
1 {id: 2, activated: true}
key: undefined
rowRender: function()
I have the following snippet.
export function withState(WrappedGrid) {
return class StatefulGrid extends React.Component {
constructor(props) {
super(props);
setInterval(() => console.log(this.props), 5000)
}
shouldComponentUpdate(){
console.log("props updated")
return true
}
componentDidUpdate(prevProps, prevState) {
console.log("update")
}
render() { ... }
}
}
Creating StatefulGrid component :
const StatefulGrid = withState(Grid);
class NFTTable extends React.Component {
constructor({rules}){
super()
this.rules = rules
this.renderers = new Renderers()
this.contextMenu = false
}
render(){
return([
<StatefulGrid
key={"table"}
data={this.rules}
rowRender={this.renderers.rowRender}
>
// GridColumn ...
</StatefulGrid>,
<ContextMenu
key={"context_menu"}
caller={this.renderers.caller}
/>
])
}
}
Updating the data :
export default class ContextMenu extends React.Component{
constructor({caller}){
super()
this.state = {
show: false
}
caller(this)
}
handleContextMenu = (e, dataItem) => {
e.preventDefault()
this.dataItem = dataItem
this.offSet = { left: e.clientX, top: e.clientY }
this.activated = dataItem.dataItem.activated
this.setState({ show: true })
}
componentDidMount() {
document.addEventListener('click', () => {
if(this.state.show)
this.setState({ show: false })
})
}
handleSelect = (e) => {
switch(e.item.data){
case "activation":
this.toggleActivation()
break
default:
console.log("Error, non registered event " + e.data)
}
}
toggleActivation(){
this.dataItem.dataItem.activated = !this.dataItem.dataItem.activated;
}
render() {
return (
<Popup show={this.state.show} offset={this.offSet}>
<Menu vertical={true} style={{ display: 'inline-block' }} onSelect={this.handleSelect}>
<MenuItem data="delete" text="Delete rule"/>
<MenuItem data="activation" text={this.activated ? "Deactivate Rule" : "Activate rule"}/>
</Menu>
</Popup>
);
}
}
Calling handleContextMenu :
export default class Renderers {
rowRender = (trElement, dataItem) => {
let greyed = { backgroundColor: "rgb(235,235,235)" }
let white = { backgroundColor: "rgb(255,255,255)" }
const trProps = {
...trElement.props,
style: dataItem.dataItem.activated ? white : greyed,
onContextMenu : (e) => {
this.contextMenu.handleContextMenu(e, dataItem)
}
};
return React.cloneElement(trElement, { ...trProps }, trElement.props.children);
}
caller = (contextMenu) => {
this.contextMenu = contextMenu
}
}
For debugging purpose, I've added a setInterval method. Through another snippet in my code, I do change props.data[0].activated to false and the changes do reflect in the console log. So why is shouldComponentUpdate not triggered and how to get it to trigger ?

Related

React this.setState function gives error when try to set new state value

When I try to update state of component with this.setState function (with or without callback) it gives following warning followed by an error as shown below.
Warning: An update (setState, replaceState, or forceUpdate) was scheduled from inside an update function. Update functions should be pure, with zero side-effects. Consider using componentDidUpdate or a callback.
Uncaught TypeError: itemValue.toLowerCase is not a function
import React from 'react';
import ReactAutocomplete from 'react-autocomplete';
export default class AddMedicine extends React.Component {
constructor (props) {
super(props)
this.state = {
autocompleteData: props.list,
bid: '',
formId: '',
formsOfBrand: []
};
this.renderBrandItem = this.renderBrandItem.bind(this);
this.onChangeBrand = this.onChangeBrand.bind(this);
this.onSelectBrand = this.onSelectBrand.bind(this);
this._getFormsByBID = this._getFormsByBID.bind(this);
this.renderFormItem = this.renderFormItem.bind(this);
this.onChangeForm = this.onChangeForm.bind(this);
this.onSelectForm = this.onSelectForm.bind(this);
}
renderBrandItem(item, highlighted) {
return <div
key={item.bid}
style={{ backgroundColor: highlighted ? '#eee' : 'transparent'}}
>
{item.brand_name}
</div>
}
onChangeBrand(e) { this.setState({
bid: e.target.value,
formsOfBrand : ['dd', 'dfsd']
})
}
onSelectBrand(bid) {
this.setState( (prevState) => {
let newState = {...prevState};
newState.bid = bid;
newState.formsOfBrand = this._getFormsByBID(bid);
console.log(newState);
return newState;
});
}
componentDidUpdate() {}
_getFormsByBID(id){
let brand = this.state.autocompleteData.filter(brand => {
console.log(brand); console.log(id);
return (brand.bid == id)? true : false; }
);
brand = brand[0];
console.log(brand);
return brand.forms;
}
renderFormItem(item, highlighted) {
return <div
key={Object.keys(item)[0]}
style={{ backgroundColor: highlighted ? '#eee' : 'transparent'}}
>
{item[Object.keys(item)[0]]}
</div>
}
onChangeForm(e) { this.setState({ formId: e.target.value }) }
onSelectForm(formId) { this.setState({ formId: formId }) }
render() {
return (
<React.Fragment>
<ReactAutocomplete
items={this.state.autocompleteData}
getItemValue={item => item.bid}
renderItem={this.renderBrandItem}
value={this.state.bid}
onSelect={this.onSelectBrand}
/>
<select>
{this.state.formsOfBrand.forEach((form) =>
<option value={Object.keys(form)[0]}>{form[Object.keys(form)[0]]}</option>
)}
</select>
</React.Fragment>
)
}
}

Change parent state on a child envent

I want to put all my 'isSelected' to false expect the clicked one.
Code :
class Parent extends Component {
state= {
menu:[
{isSelected:true},
{isSelected:false},
{isSelected:false}
]
}
render() {
let menus=this.state.menu.map((menu,i)=>(
<MenuElem isSelected={menu.isSelected} />
))
return
{menus}
);
}
}
class MenuElem extends Component {
state = {
isSelected: this.props.isSelected
}
render() {
const {isSelected} = this.state;
let clickHandler = ()=>{
this.setState({ isSelected: true })
//I want to put all the MenuElem to false : except the clicked one
// let parent = this._reactInternalInstance._currentElement._owner._instance; ??
// then foreach MenuElem in my parent I change the isSelected ?
}
return (
<li onClick={clickHandler} className={isSelected ? "is-active" : ""}></li>
);
}
}
I'am not sure my logic is the good one.
The this._reactInternalInstance._currentElement._owner._instance looks like Im in the wrong way.
There are all sorts of cleaning that could be done to this code but below I rewrote what you had and allowed the child to update the parent's state. I trust you have a valid reason to do this and not manage the state of the li at the li level. Hope this helps.
class Parent extends Component {
state = {
menu:[
{ isSelected: true },
{ isSelected: false },
{ isSelected: false }
]
}
render() {
const { menu } = this.state;
return (
{ menu.map((menu,i) => {
return <MenuElem
isSelected={menu.isSelected}
onClick={() => {
const newMenu = [...menu];
newMenu[i] = !menu[i]
this.setState({ menu: newMenu }) }
}
/>
})};
);
}
}
class MenuElem extends Component {
render() {
const { onClick, isSelected } = this.props;
return (
<li onClick={onClick} className={isSelected ? "is-active" : ""}></li>
);
}
}
Edit: In the case where you don't really need the state stored in the parent you could just as easily do:
class Parent extends Component {
render() {
return (
{ menu.map((menu,i) => <MenuElem key={i}/> }
);
}
}
class MenuElem extends Component {
state = { isSelected: false }
render() {
const { isSelected } = this.state
return (
<li onClick={() => {this.setState({ isSelected: !isSelected })}} className={isSelected ? "is-active" : ""}></li>
);
}
}

Child component state not being update

This is my first week into React. I'm finding the implementation of trickle down state to be elusive.
What I would like to happen: When the parent container receives a message from the socket I want the state change of the parent to trickle down to each of the child elements.
What is currently happening: With the code below all the components render to the screen, the message is received, but the child elements do not register any type of update.
**Update: Everything works fine if I don't loop the ProgressBlocks into an array and then place that array in the render tree with {this.state.blocks}. This is unfortunate since it's not dynamic but at least there's progress.
Parent Container
class InstanceContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
id: ids.pop(),
callInProgress: false,
callsCompleted: 0,
eventProgress: 0,
blocks: []
}
}
componentWillMount() {
const lightColors = ['white', 'white', 'cyan', 'green', 'green', 'yellow', 'orange', 'magenta', 'red']
for (let i = 0; i < 10; i++) {
this.state.blocks.push(<ProgressBlock bgColor={lightColors[i]} eventPlace={i} eventProgress={this.state.eventProgress} />)
}
}
render() {
socket.on(this.state.id, (msg) => {
console.log(msg)
console.log('RECEIVED MESSAGE')
console.log(this.state.id)
if (msg) {
this.setState((prevState) => ({
eventProgress: 0,
callsCompleted: prevState.callsCompleted + 1
}))
} else {
this.setState((prevState) => ({
eventProgress: prevState.eventProgress + 1
}))
}
console.log(this.state.eventProgress)
})
return (
<div className="instance-container" id={this.state.id}>
{this.state.blocks}
<CompletionCounter callsCompleted={this.state.callsCompleted} />
<DisplayLog />
<VenueName />
</div>
)
}
}
Child Element
class ProgressBlock extends React.Component {
constructor(props) {
super(props)
this.state = {
light: false,
eventProgress: this.props.eventProgress,
bgColor: this.props.bgColor
}
}
componentWillUpdate() {
if (this.state.eventProgress >= this.props.eventPlace) {
this.setState({
light: true
})
}
}
render() {
console.log(this.state.eventProgress) // Does not log when parent changed
console.log(this.props.eventPlace) // Does not log when parent changed
const styleObj = {
backgroundColor: '#232323'
}
if (this.light) {
const styleObj = {
backgroundColor: this.props.bgColor
}
}
return <div className="progress-block" style={styleObj} />
}
}
The constructor in the child element is called once and hence your state is not gettinng updated in the child element. Also its an antipattern to set the state at the constructor when it depends on props. You should do it in the componentWillReceiveProps lifecycle function which is called when the props are updated. See the code below
class ProgressBlock extends React.Component {
constructor(props) {
super(props)
this.state = {
light: false,
eventProgress: '',
bgColor: ''
}
}
componentWillMount() {
this.setState({eventProgress: this.props.eventProgress, bgColor: this.props.bgColor});
}
componentWillReceiveProps(nextProps) {
this.setState({eventProgress: nextProps.eventProgress, bgColor: nextProps.bgColor});
}
componentWillUpdate() {
if (this.state.eventProgress >= this.props.eventPlace) {
this.setState({
light: true
})
}
}
render() {
console.log(this.state.eventProgress)
console.log(this.props.eventPlace)
const styleObj = {
backgroundColor: '#232323'
}
if (this.light) {
const styleObj = {
backgroundColor: this.props.bgColor
}
}
return <div className="progress-block" style={styleObj} />
}
}

React setState called multiple times on the same state object

I have the following:
import React from 'react';
import ReactDOM from 'react-dom'
import {render} from 'react-dom';
import Forms from './forms/forms.jsx';
class Option1 extends React.Component {
render () {
return (
<p>Icon 1</p>
)
}
}
class TShirt extends React.Component {
render () {
console.log(this.props.currentState);
return <div className="thsirt">
<h1>{this.props.name}</h1>
<p>{this.props.iconID}</p>
{this.props.optionA ? <Option1 /> : ''}
</div>;
}
}
class Link extends React.Component {
render () {
return (
<li
data-id={this.props.el}
onClick={this.props.onClick}
className={this.props.activeClass}>{this.props.el}
</li>
);
}
}
class Nav extends React.Component {
getComponentID (id) {
switch(id) {
case 'name':
return 1;
break;
case 'color':
return 2;
break;
case 'design':
return 3;
break;
case 'share':
return 4;
break;
}
}
handleClick (event) {
// setting active class
var id = event.target.getAttribute("data-id");
this.props.action(id);
// switching coomponent based on active class
var component = this.getComponentID(id);
this.props.switchComponent(component);
}
render () {
var links = ['name', 'color', 'design', 'share'],
newLinks = [],
that = this;
links.forEach(function(el){
newLinks.push(<Link
onClick={that.handleClick.bind(that)}
activeClass={that.props.active == el ? 'active': ''}
key={el}
el={el}
/>
);
});
return (
<ol>
{newLinks}
</ol>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
color: '',
active: '',
formId: 1,
optionA: {
on: false,
icon_id: '',
option_id: '',
name: ''
}
};
this.setName = this.setName.bind(this);
this.setColor = this.setColor.bind(this);
this.setAtciveNavEl = this.setAtciveNavEl.bind(this);
this.setFormId = this.setFormId.bind(this);
this.setOptionA = this.setOptionA.bind(this);
this.setOptionAVisibility = this.setOptionAVisibility.bind(this);
}
setName (tshirt) {
this.setState({ name:tshirt })
}
setColor (color) {
this.setState({ color:color })
}
setAtciveNavEl (el) {
this.setState({ active:el })
}
setFormId (id) {
this.setState({ formId:id })
}
setOptionA (iconID, iconName) {
this.setState({
optionA:
{
icon_id: iconID,
name: iconName
}
})
}
setOptionAVisibility (onOff, optionID) {
this.setState({
optionA:
{
option_id: optionID,
on: onOff
}
})
}
render () {
return (
<section className={this.state.color}>
<Nav
active={this.state.active}
action={this.setAtciveNavEl}
switchComponent={this.setFormId}
/>
<TShirt
name={this.state.name}
icons={this.state.options}
optionA={this.state.optionA.on}
currentState={this.state}
/>
<Forms
name={this.state.name}
action={this.setName}
colorVal={this.setColor}
activeNav={this.setAtciveNavEl}
switchComponent={this.setFormId}
formID={this.state.formId}
setOptionA={this.setOptionA}
setOptionAVisibility={this.setOptionAVisibility}
/>
</section>
);
}
}
render(<App/>, document.getElementById('app'));
I need to populate this object at different times like this:
setOptionA (iconID, iconName) {
this.setState({
optionA:
{
icon_id: iconID,
name: iconName
}
})
}
setOptionAVisibility (onOff, optionID) {
this.setState({
optionA:
{
option_id: optionID,
on: onOff
}
})
}
The problem I have is taht when I console.log my state at:
class TShirt extends React.Component {
render () {
console.log(this.props.currentState);
return <div className="thsirt">
<h1>{this.props.name}</h1>
<p>{this.props.iconID}</p>
{this.props.optionA ? <Option1 /> : ''}
</div>;
}
}
after all my click events it seems like I loose the "on" and "option_id" from the optionA object.
Does calling setState on the same object override the previous setState?
If you are writing ES2015, you can use the spread operator to copy the whole object and just modify one of it's properties:
setOptionAVisibility (onOff, optionID) {
this.setState({
optionA:
{
...this.state.optionA,
option_id: optionID,
on: onOff
}
})
}
Can be very useful when modifying single properties of complex objects on the state tree.

React force componentDidMount

I have the following:
import React from 'react';
import axios from 'axios';
class FirstName extends React.Component {
constructor(props) {
super(props);
this.state = {
submitted: false
};
}
getName () {
var name = this.refs.firstName.value;
this.setState(function() {
this.props.action(name);
});
}
handleSubmit (e) {
e.preventDefault();
this.setState({ submitted: true }, function() {
this.props.actionID(2);
this.props.activeNav('color');
});
}
render () {
return (
<div>
<h2>tell us your first name</h2>
<form>
<input
type="text"
ref="firstName"
onChange={this.getName.bind(this)}
/>
<div className="buttons-wrapper">
<button href="#">back</button>
<button onClick={this.handleSubmit.bind(this)}>continue</button>
</div>
</form>
</div>
);
}
};
class PickColor extends React.Component {
backToPrevious (e) {
e.preventDefault();
this.props.actionID(1);
this.props.activeNav('name');
}
goToNext (e) {
e.preventDefault();
this.props.actionID(3);
this.props.activeNav('design');
this.props.displayIconsHolder(true);
}
getColorValue(event) {
this.props.color(event.target.getAttribute("data-color"));
}
render () {
var colors = ['red', 'purple', 'yellow', 'green', 'blue'],
colorsLink = [];
colors.forEach(el => {
colorsLink.push(<li
data-color={el}
key={el}
onClick={this.getColorValue.bind(this)}
ref={el}>
{el}
</li>
);
});
return (
<section>
<ul>
{colorsLink}
</ul>
<button onClick={this.backToPrevious.bind(this)}>back</button>
<button onClick={this.goToNext.bind(this)}>continue</button>
</section>
);
}
}
class ConfirmSingleIcon extends React.Component {
goBack () {
this.props.goBack();
}
confirmCaptionandIcon (event) {
var optionID = event.target.getAttribute("data-option-id"),
name = event.target.getAttribute("data-option-name");
this.props.setOptionID(optionID);
this.props.setIcon(1, name, optionID, false);
}
goNext () {
this.props.goNext();
}
render () {
console.log(this.props.currentState);
var options = [],
that = this;
this.props.iconOptionsList.forEach(function(el){
options.push(<li onClick={that.confirmCaptionandIcon.bind(that)} key={el.option} data-option-name={el.option} data-option-id={el.id}>{el.option}</li>);
});
return (
<div>
<h2>Choose your caption</h2>
<h3>
{this.props.selectedIcon}
</h3>
<ul>
{options}
</ul>
<button onClick={this.goBack.bind(this)} >back</button>
<button onClick={this.goNext.bind(this)} >confirm</button>
</div>
);
}
}
class ConfirmCaption extends React.Component {
handleClick () {
var currentState = this.props.currentState;
this.props.setIcon(currentState.icon_ID, currentState.selectedIcon, currentState.option_ID, true);
this.props.setIconVisiblity(true);
this.props.setIconListVisiblity(false);
}
render () {
console.log(this.props.currentState);
return (
<div>
<p onClick={this.handleClick.bind(this)}>confirm icon and caption</p>
</div>
);
}
}
class ChooseIcon extends React.Component {
constructor(props) {
super(props);
this.state = {
icons: [],
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
setOptionID (id) {
this.setState({ option_ID: id })
}
setIconVisiblity (onOff) {
this.setState({ confirmIcon: onOff })
}
setIconListVisiblity (onOff) {
this.setState({ iconList: onOff })
}
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
handleClick (event) {
var iconId = event.target.getAttribute("data-icon-id"),
that = this;
this.state.icons.forEach(function(el){
if(el.id == iconId){
that.setState(
{
confirmIcon: true,
iconList: false,
selectedIcon: el.name,
icon_ID: iconId,
selectedIconOptions: el.option
}
);
}
});
}
goBack () {
this.setState(
{
confirmIcon: false,
iconList: true
}
);
}
goNext () {
this.setState(
{
confirmIcon: false,
iconList: false,
confirmCaption: true
}
);
}
render () {
var icons = [];
this.state.icons.forEach(el => {
icons.push(<li data-icon-id={el.id} onClick={this.handleClick.bind(this)} key={el.name}>{el.name}</li>);
});
return (
<div>
{this.state.iconList ? <IconList icons={icons} /> : ''}
{this.state.confirmIcon ? <ConfirmSingleIcon goBack={this.goBack.bind(this)}
goNext={this.goNext.bind(this)}
setIcon={this.props.setIcon}
selectedIcon={this.state.selectedIcon}
iconOptionsList ={this.state.selectedIconOptions}
setOptionID={this.setOptionID}
currentState={this.state} /> : ''}
{this.state.confirmCaption ? <ConfirmCaption currentState={this.state}
setIcon={this.props.setIcon}
setIconVisiblity={this.setIconVisiblity}
setIconListVisiblity={this.setIconListVisiblity} /> : ''}
</div>
);
}
}
class IconList extends React.Component {
render () {
return (
<div>
<h2>Pick your icon</h2>
<ul>
{this.props.icons}
</ul>
</div>
);
}
}
class Forms extends React.Component {
render () {
var form;
switch(this.props.formID) {
case 1:
form = <FirstName action={this.props.action} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 2:
form = <PickColor displayIconsHolder={this.props.seticonsHolder} color={this.props.colorVal} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 3:
form = <ChooseIcon setIcon={this.props.setOptionA} />
break;
}
return (
<section>
{form}
</section>
);
}
}
export default Forms;
"ChooseIcon" is a component that will get used 3 times therefore everytime I get to it I need to bring its state back as if it was the first time.
Ideally I would need to make this ajax call everytime:
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
is there a way to manually call componentDidMount perhaps from a parent component?
React handles component lifecycle through key attribute. For example:
<ChooseIcon key={this.props.formID} setIcon={this.props.setOptionA} />
So every time your key (it can be anything you like, but unique) is changed component will unmount and mount again, with this you can easily control componentDidMount callback.
If you are using the ChooseIcon component 3 times inside the same parent component, I would suggest you to do the ajax in componentDidMount of the parent component like this (exaclty how you have in your example, in terms of code)
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
and then pass this data down to the ChooseIcon component
render() {
return (
//do your stuff
<ChooseIcon icons={this.state.icons}/>
)
}
after this you will only need to set the received props in your ChooseIconcomponent, for that you only need to change one line in it's constructor:
constructor(props) {
super(props);
this.state = {
icons: props.icons, // Changed here!
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
The parent component can use a ref to call the function directly.
However, trying to force this function feels like a smell. Perhaps lifting the state higher up the component tree would solve this problem. This way, the parent component will tell ChooseIcon what to show, and there will not be a need to call componentDidMount again. Also, I assume the Ajax call can also occur once.

Resources