Why setState() doesn't update render on React? - javascript

I am trying to make a 2D game-board that consists of "Cells" which has a binary state value called isAlive. The user can change this value by clicking on them. This value is indicated to the user using different colors. The Board.js constructor creates every cell with a false value.
The board also has a "Reset" button to reset all Cell's back to false.
The values of the Cells changes correctly when I click on them. When "Reset" button is clicked, I want every Cell's isAlive value to be false. However, when "Reset" button is pressed visually there is no change on the board (The Cell's colors doesn't change). The console.log(this.state.cells); line on handleClick() method on Board.js prints props: Object { isAlive: false } for all Cells. So why the Cell's are not updating visually? What am I doing wrong here?
Board.js
export default class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
cells: []
};
this.rowNum = 10;
this.colNum = 10;
for (let i = 0; i < (this.rowNum * this.colNum); i++) {
this.state.cells.push(<Cell isAlive={false}/>);
}
this.handleClick = this.handleClick.bind(this);
this.getCols = this.getCols.bind(this);
}
getCols() {
let cols = [];
for (let i = this.rowNum; i <= (this.rowNum * this.colNum); i += this.rowNum) {
cols.push(this.state.cells.slice(i - this.rowNum, i).map(function (item, i) {
return <div key={i}>{item}</div>
}));
}
return cols;
}
handleClick() {
const newBoard = this.state.cells;
newBoard.forEach((item, index, arr) => {
arr[index] = <Cell isAlive={false}/>
});
this.setState({
cells: newBoard
}, function () {
console.log(this.state.cells);
}
);
}
render() {
return (<div className="background">
<table className="gameBoard">
<tbody>
<tr>
{this.getCols().map(function (item, index) {
return <td className="col" key={index}>{item}</td>
})}
</tr>
</tbody>
</table>
<button onClick={this.handleClick}> Reset</button>
</div>
);
}
}
The significant parts of the Cell.js:
export default class Cell extends React.Component {
constructor(props) {
super(props);
this.colors = {
dead: '#041b40',
alive: '#b2e8f7',
hover_dead: '#495d76',
hover_alive: '#e6fffd'
};
this.state = {
isAlive: this.props.isAlive,
isMouseDown: false,
isHovering: false
};
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState({
isAlive: !this.state.isAlive,
isMouseDown: this.state.isMouseDown,
isHovering: this.state.isHovering
})
}
...
determineColor() {
if (this.state.isAlive && this.state.isHovering) {
return this.colors.hover_alive;
} else if (!this.state.isAlive && this.state.isHovering) {
return this.colors.hover_dead;
} else if (this.state.isAlive && !this.state.isHovering) {
return this.colors.alive;
}else {
return this.colors.dead;
}
}
render() {
return (
<div>
<button className='square'
onClick={this.handleClick}
type='button'
style={{
backgroundColor: this.determineColor()
}}>
</button>
</div>
)
};
}

You need to update the inner state on props change:
export default class Cell extends React.Component {
constructor(props) {
super(props);
this.colors = {
dead: '#041b40',
alive: '#b2e8f7',
hover_dead: '#495d76',
hover_alive: '#e6fffd'
};
this.state = {
isAlive: this.props.isAlive,
isMouseDown: false,
isHovering: false
};
this.handleClick = this.handleClick.bind(this)
}
componentDidUpdate(prevProps, prevState) { //<--HERE
this.setState({isAlive: this.props.isAlive})
}
handleClick() {
this.setState({
isAlive: !this.state.isAlive,
isMouseDown: this.state.isMouseDown,
isHovering: this.state.isHovering
})
}
...
determineColor() {
if (this.state.isAlive && this.state.isHovering) {
return this.colors.hover_alive;
} else if (!this.state.isAlive && this.state.isHovering) {
return this.colors.hover_dead;
} else if (this.state.isAlive && !this.state.isHovering) {
return this.colors.alive;
}else {
return this.colors.dead;
}
}
render() {
return (
<div>
<button className='square'
onClick={this.handleClick}
type='button'
style={{
backgroundColor: this.determineColor()
}}>
</button>
</div>
)
};

You must use the componentDidUpdate
componentDidUpdate(prevProps) {
if (this.props.isAlive!== prevProps.isAlive) {
this.setState({isAlive: this.props.isAlive})
}
}

Related

HandleClick not changing the state

I have the below where it should display images of beers retrieved from an API. Each image has a handleClick event which will direct them to a details page about this beer. My code below doesn't render the beers at all and goes straight to the details page of a random beer. Can anyone help me figure out why?
Thanks
export default class GetBeers extends React.Component {
constructor() {
super();
this.state = {
beers: [],
showMethod: false,
beerDetails: []
};
this.getBeerInfo = this.getBeerInfo.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleClick(details) {
this.setState({
showMethod: !this.state.showMethod,
beerDetails: details
});
}
render() {
if(this.state.showMethod) {
return (
<Beer details = {this.state.beerDetails}/>
);
}
else {
return (
<div>{this.state.beers.map(each=> {
return <img className = "img-beer" onClick = {this.handleClick(each)} src={each.image_url}/>
})}</div>
);
}
}
componentDidMount() {
this.getBeerInfo()
}
getBeerInfo() {
...gets info
}
}
When you use onClick like that you run the function at the render.
So you have to use arrow function:
Not Working:
<img className = "img-beer" onClick = {this.handleClick(each)} src={each.image_url}/>
Working:
<img className = "img-beer" onClick = {() => this.handleClick(each)} src={each.image_url}/>
The main issue is not calling the handle properly.
Also, I noticed that you are binding the functions in the constructor. It might be simpler to use ES6 function creation, so the scope of the class is bound to your handle method.
export default class GetBeers extends React.Component {
constructor() {
super();
this.state = {
beers: [],
showMethod: false,
beerDetails: []
};
}
handleClick = (details) => {
this.setState({
showMethod: !this.state.showMethod,
beerDetails: details
});
}
render() {
if(this.state.showMethod) {
return (
<Beer details = {this.state.beerDetails}/>
);
}
else {
return (
<div>{this.state.beers.map(each=> {
return <img className = "img-beer" onClick = {() => this.handleClick(each)} src={each.image_url}/>
})}</div>
);
}
}
componentDidMount() {
this.getBeerInfo()
}
getBeerInfo = () => {
...gets info
}
}

How to render multiple component inside map function in REACT?

I am using switch case statement in which i am using two map function. one map function is used for word and other is used for punctuation mark. basically i want to split a word with punctuation mark.
here i face this problem in which everything is going fine but my component in not render. in if else
what should i do call this component whose name is A and B.
</div>
<script type="text/babel">
class A extends React.Component {
constructor(props){
super(props);
this.state={
}
}
render(){
return (
<div>
{this.props.item}hello
</div>
)
}
}
class B extends React.Component {
constructor(props){
super(props);
}
render(){
return(
<div>
{this.props.items}
</div>
)
}
}
class C extends React.Component {
constructor(props){
super(props);
self =this;
this.state = {
cstate:true,
renderdComp:''
}
this.switch =this.switch.bind(this)
this.callComp =this.callComp.bind(this)
}
callComp(item,punc,k){
console.log("item =>",item,"punc =>",punc,"k =>",k);
if(punc == '') {
this.setState({
renderdComp : "A"
})
return <A item={item}/>
} else {
this.setState({
renderdComp : "B"
})
return <B items={item}/>
}}
switch(stateValue) {
if(stateValue) {
let freshItem;
let puncItem;
['a;','as','df','fg'].map( function(item, i) {
[';',':','<'].map( function (punc, j) {
const isFoundPunc = item.indexOf(punc)
if(isFoundPunc > -1) {
console.log(isFoundPunc)
freshItem = item.substr(0,isFoundPunc);
console.log(freshItem)
puncItem = item.substr(isFoundPunc,item.length);
console.log(puncItem)
} else {
freshItem = item
console.log(freshItem+"sec")
puncItem = ''
console.log(puncItem+"sec")
}
})
self.callComp(freshItem, puncItem, i)
})
}
}
render(){
return(
{this.switch(this.state.cstate)}
)
}
}
ReactDOM.render(,document.getElementById("container"));
You can setState here in this function
constructor() {
this.callComp = this.callComp.bind(this)
}
callComp(item,punc,k){
console.log("item =>",item,"punc =>",punc,"k =>",k);
if(punc == '') {
this.setState({
renderdComp : "A"
})
return <A item={item}/>
} else {
this.setState({
renderdComp : "B"
})
return <B items={item}/>
}
This should rerender your component C

React calling methods in different conditions

I'm beginner on react and i've written the code below:
class Note extends React.Component {
constructor(props) {
super(props);
this.state = {editing: false};
this.edit = this.edit.bind(this);
this.save = this.save.bind(this);
}
edit() {
// alert('edit');
this.setState({editing: !this.state.editing});
}
save() {
this.props.onChange(this.refs.newVal.value, this.props.id);
this.setState({editing: !this.state.editing});
// console.log('save is over');
}
renderForm() {
return (
<div className="note">
<textarea ref="newVal"></textarea>
<button onClick={this.save}>SAVE</button>
</div>
);
}
renderDisplay() {
return (
<div className="note">
<p>{this.props.children}</p>
<span>
<button onClick={this.edit}>EDIT</button>
<button onClick={this.remove}>X</button>
</span>
</div>
);
}
render() {
console.log(this.state.editing);
return (this.state.editing) ? this.renderForm()
: this.renderDisplay()
}
}
class Board extends React.Component {
constructor(props){
super(props);
this.state = {
notes: []
};
this.update = this.update.bind(this);
this.eachNote = this.eachNote.bind(this);
this.add = this.add.bind(this);
}
nextId() {
this.uniqeId = this.uniqeId || 0;
return this.uniqeId++;
}
add(text) {
let notes = [
...this.state.notes,
{
id: this.nextId(),
note: text
}
];
this.setState({notes});
}
update(newText, id) {
let notes = this.state.notes.map(
note => (note.id !== id) ?
note :
{
id: id,
note: newText
}
);
this.setState({notes})
}
eachNote(note) {
return (<Note key={note.id}
id={note.id}
onChange={this.update}>
{note.note}
</Note>)
}
render() {
return (<div className='board'>
{this.state.notes.map(this.eachNote)}
<button onClick={() => this.add()}>+</button>
</div>)
}
}
ReactDOM.render(<Board />,
document.getElementById('root'));
In render(), onClick event has a function, that is, if used in this way: {this.add} the following error is created:
Uncaught Error: Objects are not valid as a React child (found: object with keys {dispatchConfig, _targetInst, nativeEvent, type, target, currentTarget, eventPhase, bubbles, cancelable, timeStamp, defaultPrevented, isTrusted, view, detail, ...})
Why? while in the eachNote() method this command is used:
onChange={this.update}
And there was no error.
Someone can tell me the reason? thanks.
The problem is that in the add function you are taking an argument text and setting it in the state so when you call onClick={() => this.add()}, you are not passing any argument to add function and hence in its definition text is undefned and hence state note is set as undefined.
However if you directly call it like onClick={this.add} , the add function receives the event object as a parameter and hence it sets state note to be an event object which you are using to render
onClick={this.add} will pass the click event to this.add.
So what you need to do is either:
onClick={e => this.add('some text')} or similar.
If you want to onClick={this.add} you have to ensure that your add method is: add(event) { ... } instead.
The <Note /> component does not contain a render() method to return anything. Add a render() method and return something.
class Note extends React.Component {
constructor(props) {
super(props);
this.state = {editing: false};
this.edit = this.edit.bind(this);
}
edit() {
// alert('edit');
this.setState({editing: !this.state.editing});
}
render() {
return (
<div>Render something</div>
)
}
}
class Board extends React.Component {
constructor(props){
super(props);
this.state = {
notes: []
};
this.update = this.update.bind(this);
this.eachNote = this.eachNote.bind(this);
this.add = this.add.bind(this);
}
nextId() {
this.uniqeId = this.uniqeId || 0;
return this.uniqeId++;
}
add(text) {
let notes = [
...this.state.notes,
{
id: this.nextId(),
note: text
}
];
this.setState({notes});
}
update(newText, id) {
let notes = this.state.notes.map(
note => (note.id !== id) ?
note :
{
id: id,
note: newText
}
);
this.setState({notes})
}
eachNote(note) {
return (<Note key={note.id}
id={note.id}
onChange={this.update}>
{note.note}
</Note>)
}
render() {
return (<div className='board'>
{this.state.notes.map(this.eachNote)}
<button onClick={() => this.add()}>+</button>
</div>)
}
}
ReactDOM.render(<Board />,
document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="root"></div>

flux: view doesn't refresh after store update

I study FLUX and try to write simple example on it. It is about a menu where I can select every single item (it is marked by (*)). So, here is main excerpts of the code:
AppContainer.js:
class AppContainer extends React.Component
{
static getStores() {
return [MenuStore];
}
static calculateState(prevState) {
return {
menu: MenuStore.getState(),
onclick: Actions.onclick
};
}
render() {
return <MenuView onclick={this.state.onclick} menu={this.state.menu.get('list')} cur={this.state.menu.get('cur')} />;
}
}
MenuStore.js
class MenuStore extends ReduceStore{
constructor() {
super(MenuDispatcher);
}
getInitialState() {
// return {list: ["About", "Contacts", "Price", "Home"], cur: 0};
return Immutable.Map({list: ["About", "Contacts", "Price", "Home"], cur: 2});
}
reduce(state, action) {
switch (action.type) {
case ActionTypes.ON_CLICK:
console.log('MenuStone ON_CLICK');
state.cur = action.index;
return state;
default:
return state;
}
}
}
MenuView.js:
class MenuView extends React.Component{
constructor(props){
super(props);
}
render(){
return <ui>{this.props.menu.map((menu, index) => {
var active = (this.props.cur === index) ? true : false;
return <MenuItem onclick={this.props.onclick} key={index} index={index} active={active} label={menu} />;
})}</ui>;
}
}
class MenuItem extends React.Component {
constructor(props) {
super(props);
this.state = {label: props.label};
this.click = this.click.bind(this);
}
click(index) {
console.log('MenuItem click');
this.props.onclick(this.props.index);
}
render() {
var mark = '';
if (this.props.active)
mark = '(*) ';
return <li onClick={this.click}>{mark}{this.state.label}</li>;
}
}
Actions.js:
const Actions = {
onclick(index) {
console.log('Actions.onclick');
MenuDispatcher.dispatch({
type: ActionTypes.ON_CLICK,
index,
});
}
};
When I run the code, it is work fine until I click a menu item. The code
state.cur = action.index;
executes, but nothing changes on the page.
What should I do to update the views?

Disable react-router Link in react

I'm new to React and got a issue to disable a link covert by a button element.
I tried the following:
class ShoppingCartLink extends React.Component {
constructor(){
super();
ShoppingCartStore.register(this.refresh.bind(this));
this.state = {count:0};
this.item = "items";
this.linkDisabled = new Boolean(true);
}
refresh(){
if (ShoppingCartStore.items.length === 0 || ShoppingCartStore.items.length > 1 )
{
this.item = "items";
}
else
{
this.item = "item";
}
if (ShoppingCartStore.items.length !== 0)
{
this.linkDisabled = false;
}
this.setState({count: ShoppingCartStore.items.length});
}
render() {
return (
<div>
<button type="button" disabled = {this.linkDisabled}>
<Link to="shoppingCart">Shopping Cart: {this.state.count} {this.item}</Link>
</button>
</div>
)
}
}
By default the Link should be disbaled as long no item is added to the cart.
I debugged through it and when the constructor is called "linkDisabled" is set to true as well in render(). The problem is that the link is still enabled.
Thanks for your help!
There is no "disabled" attribute for anchor tags, and Links are just router aware anchor tags. You can use one of the two things
1. Diasble pointer events with css
REACT Component
constructor(){
super();
ShoppingCartStore.register(this.refresh.bind(this));
this.state = {
count:0,
linkDisabled: 'disable-link';
};
}
refresh(){
const length = ShoppingCartStore.items.length;
const classValue = (length === 0)? 'disable-link': '';
this.setState({
count: length,
linkDisabled: classValue
});
}
render() {
const length = ShoppingCartStore.items.length;
return (<div>
<button type="button" disabled={this.state.linkDisabled}>
<Link to="shoppingCart" className={this.state.linkDisabled}>Shopping Cart: {this.state.count} {((length > 0)?"items":"item")}</Link>
</button>
</div>);
}
CSS
. disable-link {
pointer-events: none;
}
2. Make use of event.preventDefault() on click of link if there are no items in the cart
constructor(){
super();
ShoppingCartStore.register(this.refresh.bind(this));
this.state = {
count:0,
linkDisabled: true
};
}
refresh(){
const length = ShoppingCartStore.items.length;
this.setState({
count: length,
linkDisabled: (length !== 0)
});
}
handleClick = (e) => {
if(this.state.linkDisabled == true) {
e.preventDefault();
}
}
render() {
const length = ShoppingCartStore.items.length;
return (<div>
<button type="button" disabled={this.state.linkDisabled}>
<Link to="shoppingCart" onClick={this.handleClick}>Shopping Cart: {this.state.count} {((length > 0)?"items":"item")}</Link>
</button>
</div>);
}
However in both these cases you can access the route with commandline as only pointer events are disabled.
linkDisabled does not need to be in state. This opens up possibilities of changing state and forgetting to update linkDisabled. It is better to compute it in render.
class ShoppingCartLink extends React.Component {
constructor(){
super();
ShoppingCartStore.register(this.refresh.bind(this));
this.state = {count: 0};
}
refresh() {
this.setState({count: ShoppingCartStore.items.length});
}
render() {
const linkDisabled = this.state.count === 0;
const item = this.state.count === 1 ? "item" : "items";
return (
<div>
<button type="button" disabled={linkDisabled}>
<Link to="shoppingCart">Shopping Cart: {this.state.count} {item}</Link>
</button>
</div>
)
}
}
This way, if you ever add more to the component and have to setState in a different place, you will not need to worry about duplicating the logic for linkDisabled and item.
constructor(){
super();
ShoppingCartStore.register(this.refresh.bind(this));
this.state = {
count:0,
linkDisabled: true
};
}
handleClick(event) {
if(this.state.linkDisabled) event.preventDefault();
}
refresh(){
const length = ShoppingCartStore.items.length;
this.setState({
count: length,
linkDisabled: (length === 0)
});
}
render() {
const length = ShoppingCartStore.items.length;
return (<div>
<Link to="shoppingCart"
onClick={this.handleClick.bind(this)}>Shopping Cart: {this.state.count} {((length > 0)?"items":"item")}</Link>
</div>);
}
After some refactoring ... Try this ?

Categories

Resources