Is there any way to change state inside render in React js? - javascript

So I am working on a dice application where I have a class component for setting the number of dice and sides of each dice with up and down buttons. My problem is that each time I press up or down button to set number of sides or number of dice, an array of random numbers gets created and displays on screen. However, I want the value to display only when the roll button is clicked.
So is there a way I can change the state of displayDice to false after I have created the array in the render, so that it only becomes true when I click roll button again

You can move logic to componentDidMount. Render is to just render UI. No business logic. It will handle event and delegate to state.
Move generate random to parent component, pass method rollChange from parents to child.
// Dice component
class SideAndDice extends React.Component {
constructor(props) {
super(props);
this.state = { sides: 6, dice: 1, randoms: this.generateRandom() };
}
increaseDice() {
this.setState({ dice: this.state.dice + 1 });
}
decreaseDice() {
if (this.state.dice > 1) {
this.setState({ dice: this.state.dice - 1 });
}
}
increaseSides() {
this.setState({ sides: this.state.sides + 1 });
}
decreaseSides() {
if (this.state.sides > 2) {
this.setState({ sides: this.state.sides - 1 });
}
}
generateRandom() {
let randoms = [];
for (var i = 0; i < this.state.dice; i++) {
var randomValue = Math.floor(Math.random() * this.state.sides + 1);
randoms.push(randomValue);
}
return randoms;
}
onRollDice() {
this.setState({ randoms: this.generateRandom() });
}
render() {
return (
<div>
<h1>Number of Sides</h1>
<h2>{this.state.sides}</h2>
<button onClick={this.increaseSides.bind(this)}>Up</button>
<button onClick={this.decreaseSides.bind(this)}>Down</button>
<h1>Number of Dice</h1>
<h2>{this.state.dice}</h2>
<button onClick={this.increaseDice.bind(this)}>Up</button>
<button onClick={this.decreaseDice.bind(this)}>Down</button>
<CreateScores
randoms={this.state.randoms}
rollChange={this.rollChange.bind(this)}
/>
</div>
);
}
}
class CreateScores extends React.Component {
render() {
return (
<div>
<button onClick={this.props.onRollDice.bind(this)}>Roll</button>
<br />
<br />
{this.props.randoms.map(random => (
<Dice key={i} diceNumber={randomValue} />
))}
</div>
);
}
}

Related

Creating show and hide sections with buttons in reactjs

I have three buttons that when clicking show and individual div but this is done in reactjs
import React, { Component } from 'react';
export class ModeExtended extends Component {
constructor() {
super();
this.busButton = this.busButton.bind(this);
this.trainButton = this.trainButton.bind(this);
this.tramButton = this.tramButton.bind(this);
this.state = {
isHidden: false,
}
}
busButton(){
console.log('Bus Button Was Pressed');
this.setState((prevState) => {
return{
isHidden: !prevState.isHidden
};
});
}
trainButton(){
console.log('Train Button Was Pressed');
this.setState((prevState) => {
return{
isHidden: !prevState.isHidden
};
});
}
tramButton(){
console.log('Tram Button Was Pressed');
this.setState((prevState) => {
return{
isHidden: !prevState.isHidden
};
});
}
render() {
return (
<div>
<h5>Mode Extended</h5>
<button onClick={this.busButton}>Bus</button>
<button onClick={this.trainButton}>Train</button>
<button onClick={this.tramButton}>Tram</button>
{this.state.isHidden && (
<div>
<h6>You can show Bus Data Now....</h6>
</div>
)}
{this.state.isHidden && (
<div>
<h6>You can show Train Data Now....</h6>
</div>
)}
{this.state.isHidden && (
<div>
<h6>You can show Tram Data Now....</h6>
</div>
)}
</div>
)
}
}
export default ModeExtended
When I click any of the buttons it shows all bus, tram and train data - how do I get them to just show one thing at a time and making sure that the other states are closed. I am really missing something here and need a pointer or two or three…
How can I add an ID to make each button open separate from each other and when one is clicked how can I close the rest of the divs - or open state, I am so lost here. Please help me out.
Cheers as always!
Here is a REPL of my code:
You need to have 3 different isHidden properties to control your divs. You can do it like this:
this.state = {
isHiddenBus: false,
isHiddenTrain: false,
isHiddenTram: false,
}
and then in your render like this:
{this.state.isHiddenBus && (
<div>
<h6>You can show Bus Data Now....</h6>
</div>
)}
{this.state.isHiddenTrain && (
<div>
<h6>You can show Train Data Now....</h6>
</div>
)}
{this.state.isHiddenTram && (
<div>
<h6>You can show Tram Data Now....</h6>
</div>
)}
also your buttons have to change to state accordingly to this.
busButton(){
console.log('Bus Button Was Pressed');
this.setState((prevState) => {
return{
isHiddenBus: !prevState.isHiddenBus
isHiddenTram: false
isHiddenTrain: false
};
});
}
trainButton(){
console.log('Train Button Was Pressed');
this.setState((prevState) => {
return{
isHiddenTrain: !prevState.isHiddenTrain
isHiddenBus: false
isHiddenTram: false
};
});
}
tramButton(){
console.log('Tram Button Was Pressed');
this.setState((prevState) => {
return{
isHiddenTram: !prevState.isHiddenTram
isHiddenTrain: false
isHiddenBus: false
};
});
}
you can do somthing like this:
import React, { Component } from 'react';
export class ModeExtended extends Component {
constructor() {
super();
this.state = {
curDivIndex:0,//currently visible div index
// isHidden: false,
}
}
renderDiv=()=>{
switch(this.state.curDivIndex){
case 1:return <div> <h6>You can show Bus Data Now....</h6> </div>
case 2:return <div> <h6>You can show Train Data Now....</h6> </div>
case 3:return <div> <h6>You can show Tram Data Now....</h6> </div>
}
return null
}
setVisibleDiv=(index)=>{
this.setState({curDivIndex:index})
}
render() {
return (
<div>
<h5>Mode Extended</h5>
<button onClick={()=>{this.setVisibleDiv(1)} }>Bus</button>
<button onClick={()=>{this.setVisibleDiv(2)}}>Train</button>
<button onClick={()=>{this.setVisibleDiv(3)}}>Tram</button>
{this.renderDiv()}
</div>
)
}
}
export default ModeExtended
EDIT
you want to have three different buttons, on click of each certain div
needs to be visible.
you can achieve this by maintaining the index of currently visible div.
when user clicks any button you have to set the index of div to be visible
which in the above code is achieved by using setVisibleDiv(index) call.
and you can at rendering time use curDivIndex to decide visible div.
Or you can achieve this by declaring state properties for all case:
this.state = {
hiddenBus: false,
hiddenTrain: false,
hiddenTram: false,
}
providing a name attribute to your buttons like so:
<button name="hiddenBus" onClick={toggleDisplay}>Bus</button>
<button name="hiddenTrain" onClick={toggleDisplay}>Train</button>
<button name="hiddenBus" onClick={toggleDisplay}>Tram</button>
then by defining the toggleDisplay function to toggle their display:
toggleDisplay = (event) => {
event.preventDefault(); // default behavior of a clicked button is to send a form so let's prevent this
const { name } = event.target; // find the clicked button name value
this.setState((prevState => ({
[name]: !prevState[name],
}));
}
Setting[name] enables us to target the state prop via the nameattribute value and update it based on the previous state.
Try this
import React, { Component } from "react";
export default class Create extends Component {
constructor(props) {
super(props);
this.state = {
currentBtn: null
};
}
clickedButton = e => {
this.setState({ currentBtn: e.target.id });
};
showDivElem = () => {
const { currentBtn } = this.state;
switch (currentBtn) {
case "A":
return <div>A</div>;
break;
case "B":
return <div>B</div>;
break;
case "C":
return <div>C</div>;
break;
default:
return <div>ABC</div>;
break;
}
};
render() {
console.log(this.state.currentBtn);
return (
<div>
<button id="A" onClick={e => this.clickedButton(e)}>
A
</button>
<button id="B" onClick={e => this.clickedButton(e)}>
B
</button>
<button id="C" onClick={e => this.clickedButton(e)}>
C
</button>
{this.showDivElem()}
</div>
);
}
}

Jest: Unit test toHaveBeenCalled() not working

I have a small component in React which generates two random numbers on render, then asks the user to submit the sum of these numbers and if they are correct, increment their score.
The following code handles this game and works as intended in the browser:
import React, { Component } from 'react';
export const randomNumber = () => {
var maxNumber = 10;
var randomNumber = Math.floor((Math.random() * maxNumber) + 1);
return randomNumber;
}
class Arithmetic extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
numbers: {
x: randomNumber(),
y: randomNumber()
},
score: ''
}
this.updateVals = this.updateVals.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
updateVals() {
this.setState({
numbers: {
x: randomNumber(),
y: randomNumber()
},
score: this.state.score + 1
});
}
componentDidMount() {
this.setState({
score: 0
});
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
var isCorrect = this.state.numbers.x + this.state.numbers.y == this.state.value ? this.updateVals() : alert("Try again");
event.preventDefault();
}
render() {
return (
<section className="arithmetic">
<div className="arithmetic__game">
<div className="row arithmetic__row--details">
<div className="arithmetic__score">
Score: {this.state.score}
</div>
<div className="arithmetic__timer">
</div>
</div>
<div className="row arithmetic__row--main">
<div className="arithmetic__examples">
1 + 1 = 2<br/>
2 + 1 = 3<br />
</div>
<div className="arithmetic__game-container">
What is {this.state.numbers.x} + {this.state.numbers.y}?
<div className="arithmetic__form-container">
<form onSubmit={this.handleSubmit}>
<label>
Answer:
<input className="input-field" type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<button className="btn-submit" type="submit" onClick={(e) => (this.handleSubmit) ? this.handleSubmit(e) : null}>Submit</button>
</form>
</div>
</div>
</div>
</div>
</section>
);
}
};
However, when trying to check whether or not updateVals is called when the sum of both numbers is entered correctly, this fails. I have checked to see if handleSubmit is called on simulation of the "Submit" button being clicked, and it is called. I have also checked the values of the value and numbers props to see if the states have been correctly updated, which they have.
However, when updateVals is called, the score is incremented (and again, this is shown in the browser). But when I try to simulate this in Jest, the score remains at 0 as it is when it is initialised.
My test is as follows:
it("passes with correct input", () => {
const updateVals = jest.fn();
const handleSubmit = jest.fn();
Arithmetic.prototype.updateVals = updateVals;
Arithmetic.prototype.handleSubmit = handleSubmit;
let wrapper = mount(<Arithmetic />);
wrapper.find('.input-field').instance().value = wrapper.update().state().numbers.x + wrapper.update().state().numbers.y;
expect(wrapper.find('.input-field').instance().value).toEqual((wrapper.update().state().numbers.x + wrapper.update().state().numbers.y).toString());
wrapper.find('.input-field').simulate('change');
wrapper.find('.btn-submit').simulate('click');
console.log(wrapper.update().state().numbers.x, wrapper.update().state().numbers.y, wrapper.update().state().value);
expect(updateVals).toHaveBeenCalled();
});
Running tests in the terminal shows that, for instance, if the numbers.x is 1 and numbers.y is 9 then the value key in state is '10'. I'm not sure why when I test handleSubmit, it gets called and the test passes but updateVals does not.
I managed to get this working, I removed any events from the submit button and tested the form itself which already calls onSubmit and tested that the score itself updated in the state.
it("passes with correct input", () => {
const wrapper = shallow(<Arithmetic />);
const preventDefault = jest.fn();
const currentScore = wrapper.update().state().score;
wrapper.find('.input-field').simulate('change', {target: {value: wrapper.update().state().numbers.x + wrapper.update().state().numbers.y}});
wrapper.find('.main-form').simulate('submit', { preventDefault });
expect(wrapper.update().state().score).toEqual(currentScore+1);
});

React setState in onClick method not updating the DOM

I'm developing a simple game fight animation using react. To start the fight, I've a button with onClick event: onClick={this.fight.bind(this)}. Now I want to update some state variable in the anytime something changes like this:
import React, { Component } from 'react';
import { ProgressBar, Row, Col } from 'react-bootstrap';
const playerA = {
_id: 1,
name: "playerA name",
life: 100,
speed: 50,
}
const playerB = {
_id: 1,
name: "playerB name",
life: 100,
speed: 40,
}
export default class App extends Compornent {
constructor() {
super();
this.state={
playerA : playerA ,
playerB : playerB ,
aLife: 100,
bLife: 100,
};
this.fight = this.fight.bind(this);
};
fight(a,b){
lifeA=this.state.playerA.live;
lifeB=this.state.playerB.live;
speedA=this.state.playerA.speed;
speedB=this.state.playerB.speed;
dmg = 10
while (lifeA>0 && lifeB>0) {
if (speedA > speedb) {
lifeA = lifeA - dmg;
setTimeout(() => {
this.setState({ aLife: lifeA });
}, 1000);
speedB = speedB + 10;
} else {
lifeB = lifeB - dmg;
setTimeout(() => {
this.setState({ bLife: lifeA });
}, 1000);
speedA = speedA + 10;
}
}
render() {
return (
<Row>
<ProgressBar bsStyle="success" now={this.state.aLife} srOnly/>
<ProgressBar bsStyle="success" now={this.state.bLife} srOnly/>
</Row>
<Row>
<Button bsStyle="danger" bsSize="large" onClick={this.fight.bind(this)}>Start Fight</Button>
</Row>
);
}
}
My expectation is to see the progress bars beeing update every 1 second. But it only updates once. When the fight funtion has finisched beein executed.
refer to the ReactJs official documents, you don't have to bind this again in the render function, since you have already done the binding in Constructor Function
<Button bsStyle="danger" bsSize="large" onClick={this.fight.bind(this)}>Start Fight</Button>
should be
<Button bsStyle="danger" bsSize="large" onClick={this.fight}>Start Fight</Button>
I finally get this done by completely rewriting the while loop using setInterval whith a bool condition: ((npcPlayerLife > 0) && (advPlayerLife > 0)) and then stop when the condition in no more meet.
I have this help someone.

ReactJs how to show list with load more option

I am tring to show todo list with load more option. I am appling limit.Limit is apply to list.But when i add loadmore()function. then i get error this.state.limit is null Wher i am wrong.Any one can suggest me.
here is my code
todoList.jsx
var TodoList=React.createClass({
render:function(){
var {todos}=this.props;
var limit = 5;
function onLoadMore() {
this.setState({
limit: this.state.limit + 5
});
}
var renderTodos=()=>{
return todos.slice(0,this.state.limit).map((todo)=>{
return(
<Todo key={todo.todo_id}{...todo} onToggle={this.props.onToggle}/>
);
});
};
return(
<div>
{renderTodos()}
<a href="#" onClick={this.onLoadMore}>Load</a>
</div>
)
}
});
module.exports=TodoList;
Changes:
1. First define the limit in state variable by using getInitialState method, you didn't define the limit, that's why this.state.limit is null.
2. Define all the functions outside of the render method.
3. Arrow function with renderTodos is not required.
4. Use this keyword to call the renderTodos method like this:
{this.renderTodos()}
Write it like this:
var TodoList=React.createClass({
getInitialState: function(){
return {
limit: 5
}
},
onLoadMore() {
this.setState({
limit: this.state.limit + 5
});
},
renderTodos: function(){
return todos.slice(0,this.state.limit).map((todo)=>{
return(
<Todo key={todo.todo_id}{...todo} onToggle={this.props.onToggle}/>
);
});
};
render:function(){
var {todos} = this.props;
return(
<div>
{this.renderTodos()}
<a href="#" onClick={this.onLoadMore}>Load</a>
</div>
)
}
});
This is witout button click.
As you all know react components has a function componentDidMount() which gets called automatically when the template of that component is rendered into the DOM. And I have used the same function to add the event listener for scroll into our div iScroll.
The scrollTop property of the element will find the scroll position and add it with the clientHeight property.
Next, the if condition will check the addition of these two properties is greater or equal to the scroll-bar height or not. If the condition is true the loadMoreItems function will run.
class Layout extends React.Component {
constructor(props) {
super(props);
this.state = {
items: 10,
loadingState: false
};
}
componentDidMount() {
this.refs.iScroll.addEventListener("scroll", () => {
if (this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >=this.refs.iScroll.scrollHeight){
this.loadMoreItems();
}
});
}
displayItems() {
var items = [];
for (var i = 0; i < this.state.items; i++) {
items.push(<li key={i}>Item {i}</li>);
}
return items;
}
loadMoreItems() {
this.setState({ loadingState: true });
setTimeout(() => {
this.setState({ items: this.state.items + 10, loadingState: false });
}, 3000);
}
render() {
return (
<div ref="iScroll" style={{ height: "200px", overflow: "auto" }}>
<ul>
{this.displayItems()}
</ul>
{this.state.loadingState ? <p className="loading"> loading More Items..</p> : ""}
</div>
);
}
}
This is example

TypeError: Cannot read property 'edit' of undefined

I keep getting the following 2 errors for my buttons:
TypeError: Cannot read property 'edit' of undefined
TypeError: Cannot read property 'remove' of undefined
I am building a todo list, each note has 2 buttons 'add' and 'Remove'.
I managed to get the note buttons working when I call DisplayNote once.
Whenever I try to make multiple notes with JS map the buttons stop working and I can't figure out why its not working now. Code is attached.
todo list image
import React from 'react';
class DisplayNote extends React.Component {
handleEdit(e) {
console.log('sdfsdfdfs');
this.props.edit(e)
}
handleRemove(e) {
console.log('sdfsdfdfs');
this.props.remove(e)
}
render(){
return(
<div className="note">
<p>{this.props.note}</p>
<span>
<button onClick={this.handleEdit.bind(this)}>Edit</button>
</span>
<span>
<button onClick={this.handleRemove.bind(this)}>Remove</button>
</span>
</div>
);
}
}
class EditNote extends React.Component {
handleSave(e) {
var val = this.refs.newText.value;
this.props.saveNote(val)
}
render(){
return (
<div className="note">
<textarea ref="newText" defaultValue="test">
</textarea>
<button onClick={this.handleSave.bind(this)}>Save</button>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.edit = this.edit.bind(this);
this.saveNote = this.saveNote.bind(this);
this.remove = this.remove.bind(this);
this.state = {
editing: false,
notes: ['Call Tim','sdsdsd', 'dentist', 'Email Julie']
}
}
AppObject = {
count: 1,
price: 15.00,
amount: '12'
}
AppArray = ['tim','ali', 'jim', 'tom']
edit(e) {
this.setState({editing: true});
console.log('AppObject', this.AppObject);
}
saveNote(val) {
this.setState({editing: false});
console.log('Save note value ' + val)
}
remove() {
alert('remove');
console.log('AppArray', this.AppArray);
}
eachNote(note, i) {
return(
<DisplayNote key={i}
note={note}
edit={(e) => this.edit(e)}
remove={(e) => this.remove(e)}>
{note}
</DisplayNote>
);
}
render() {
if(this.state.editing) {
return (
<div>
<EditNote saveNote={(e) => this.saveNote(e)} />
<div>{this.props.count}</div>
</div>
);
}else{
return (
<div>
/* Calling it once*/<DisplayNote edit={(e) => this.edit(e)} remove={(e) => this.remove(e)} />
<div>{this.props.count}</div>
<div>
/* Using map to create multiple notes */{this.state.notes.map(this.eachNote)}
</div>
</div>
);
}
}
}
App.propTypes = {
count: function(props, propName){
if(typeof props[propName] !== 'number'){
return new Error('Count prop must be a number');
}
if(props[propName] > 100){
return new Error('Creating ' + props[propName] + ' notes is too much!');
}
}
}
export default App;
I think you are loosing the context inside map function, you need to define the binding for that also.
Use this line in the constructor, it will bind that function:
this.eachNote = this.eachNote.bind(this);
Or use that function like this:
{this.state.notes.map((note, i) => this.eachNote(note,i)}
Or
{this.state.notes.map(this.eachNote)}
eachNote = (note, i) => { //use arrow function here
}

Categories

Resources