I have successfully implemented an incrementer app that shows a number with a + and - on each side. When either is clicked, the number goes up or down respectively. But because if statements work differently in jsx I am having trouble setting limits for the number, such as not going below zero.
I have tried putting in if statements but they don't work. How can I make it for instance so if the + or - is clicked once the number reaches a certain min or max value it doesn't change?
class App extends Component {
constructor () {
super()
this.state = {
num: 1
}
this.addNum = this.addNum.bind(this)
this.minusNum = this.minusNum.bind(this)
}
addNum() {
this.setState({
num: this.state.num +1
}, function() {
console.log('number of issues is ' + this.state.num)
})
}
minusNum() {
this.setState({
num: this.state.num - 1
}, function() {
})
}
`
render() {
return (
<div>
<body>
<div className="incrementer">
<div>
<h2 className="minus" onClick={this.minusNum}>-</h2>
</div>
<div>
<h2 className="number">{this.state.num}</h2>
</div>
<div>
<h2 className="plus" onClick={this.addNum}>+</h2>
</div>
</div>
</body>
</div>
);
}
}
export default App;
You should implement your own logic to control edge cases which you don't want to happen.
Here is your code with additional functionality and ES6 arrow functions (without bind).
To change max and min numbers just edit minValue and maxValue
import React from 'react'
class App extends React.Component {
state = {
num: 1
}
minValue = 0
maxValue = 10
addNum = () => {
const { num } = this.state
if (num < this.maxValue) {
this.setState({ num: num + 1 }, this.log)
}
}
minusNum = () => {
const { num } = this.state
if (num > this.minValue) {
this.setState({ num: num - 1 }, this.log)
}
}
log = () => {
console.log('number of issues is ' + this.state.num)
}
render() {
return (
<div className="incrementer">
<div>
<h2 className="minus" onClick={this.minusNum}>
-
</h2>
</div>
<div>
<h2 className="number">{this.state.num}</h2>
</div>
<div>
<h2 className="plus" onClick={this.addNum}>
+
</h2>
</div>
</div>
)
}
}
export default App
Check the bounds of the variable in your addNum and minusNum methods.
There are no “inbuilt” ways to implement state constraints in React.js (in the way one might with e.g. a database column).
Just add a check in your minus function, if the current value in state is zero, do nothing.
this.state = {
num: 1,
minValue: 0,
maxValue: 10
}
minusNum() {
if (this.state.num === this.state.minValue) {
return;
}
this.setState({
num: this.state.num - 1
}, function() {})
}
addNum() {
// assuming you have maxValue in state
if (this.state.num > this.state.maxValue) {
return;
}
this.setState({
num: this.state.num + 1
}, function() {
console.log('number of issues is ' + this.state.num)
})
}
Related
I am tring to make a simple counter and display it to the page.
But it renders unexpected o/p.
The counter counts a value twice in example 1 but works perfect as i want in example 2.
What is the reason for not working in ex.1.
What is the background process for this.
// Example: 1
import React, { Component } from 'react';
class Counter extends Component {
constructor() {
super()
this.state = {
count: 0,
isFirstTime: true
}
}
in() {
console.log('How many time function called?'); // consoled one time
if (this.state.isFirstTime) {
this.setState({
isFirstTime: false
})
setInterval(() => {
this.setState({
count: this.state.count + 1
})
}, 1000)
}
}
render() {
return (
<div>
{this.state.isFirstTime && this.in.apply(this)}
Counter: {this.state.count}
</div>
)
}
}
export default Counter;
// Example: 2
import React, { Component } from 'react';
let isFirstTime = true;
class Counter extends Component {
constructor() {
super()
this.state = {
count: 0
}
}
in() {
console.log('How many time function called?'); // consoled one time
if (isFirstTime) {
isFirstTime = false
setInterval(() => {
this.setState({
count: this.state.count + 1
})
}, 1000)
}
}
render() {
return (
<div>
{isFirstTime && this.in.apply(this)}
Counter: {this.state.count}
</div>
)
}
}
export default Counter;
I am running it on React.StrictMode.
my code:
import React from "react"
export default class MenuItem extends React.Component {
constructor(props) {
super(props)
this.state = { value: this.props.value }
}
updateValue(newValue) {
let propertyName =
this.state.selectedItem.objType === "number" ? "value" : "strValue"
console.log(propertyName)
this.setState((prevState) => {
let objectToUpdate = prevState.currentObjects.find(
)
objectToUpdate[propertyName] = newValue
return { currentObjects: this.state.currentObjects }
})
}
data = [
{ itemId: 1 },
]
addTen() {
this.setState((prevState) => {
let newValue = prevState.value + 1
return {
value: newValue,
}
})
}
subTen() {
this.setState((prevState) => {
let newValue = prevState.value - 1
this.setState({ newValue })
return {
value: newValue,
}
})
}
render() {
let array = this.data.map((itemPrice, itemName) => {
return (<div>
<p>${this.props.itemPrice}</p>
<h2>{this.props.itemName}</h2>
<p>{this.props.itemDescription}</p>
<button onClick={() => this.addTen()}>Add to Cart</button>
<p> Item Count: {this.state.value}</p>
<button onClick={() => this.addTen()}>Add One</button>
<button onClick={() => this.subTen()}>Subtract One</button>
</div>
)
})
return (array)
}
}
The problem is that when I go to use the add or subtract button I get this
picture of item count: NaN
Basically, I am trying to get the button to add or subtract one count when the button is pushed but it is not doing that, instead, it is just showing up as NaN. Also, there is no count of 0 even in the beginning. I have to press the button to make even NaN pop up. I don't know why. any suggestions? I don't know how to fix it.
So a couple of changes needed to happen here. First off in your constructor you never gave value a default value therefore setState returned NaN when trying to add 1 to an undefined value. Constructor for reference:
constructor(props) {
super(props);
this.state = { value: 0 };
}
One other change is to your subTen() method. You called this.setState twice inside of hit here is the updated version:
subTen() {
this.setState((prevState) => {
let newValue = prevState.value - 1;
return {
value: newValue
};
});
}
I am new to React and I have been having an issue with my Fizz-Buzz app. The console output seems to be ok but the display always increments the console number. The outputs are as follows:
Console: 1 /
Display: 2 /
Text: /
Display Text:
Console: 2 /
Display: 3 /
Text: /
Display Text:
Console: 3 /
Display: 4 /
Text: Fizz /
Display Text: Fizz
The code:
class FizzBuzz extends Component {
constructor() {
super()
this.state = {
number: 1,
fizzbuzz: ""
}
this.onClick = this.onClick.bind(this)
this.fizzOrBuzz = this.fizzOrBuzz.bind(this)
}
onClick() {
let tempNo = this.state.number + 1
this.setState({ number: tempNo })
console.log(this.state.number)
this.fizzOrBuzz()
}
fizzOrBuzz() {
if ((this.state.number % 3 === 0) && (this.state.number % 5 === 0)) {
console.log("fizz buzz")
this.setState({ fizzbuzz: "Fizz-Buzz" })
} else if (this.state.number % 3 === 0) {
console.log("fizz")
this.setState({ fizzbuzz: "Fizz" })
} else if (this.state.number % 5 === 0) {
console.log("buzz")
this.setState({ fizzbuzz: "Buzz" })
} else {
this.setState({ fizzbuzz: "" })
}
console.log(this.state.number)
}
render() {
return (
<div className="App">
<p>Number: {this.state.number}</p>
<h2>{this.state.fizzbuzz}</h2>
<button onClick={this.onClick}>Next</button>
</div>
)
}
}
export default FizzBuzz
How can I get the page to render the same number as the console?
As I mention in the comment, this.setState() is asynchronous, and you'd have to call this.fizzOrBuzz() as its callback.
However, as the fizziness or buzziness of a number is always derivable from the number itself, it should not be in the state at all.
function fizzOrBuzz(number) {
if (number % 3 === 0 && number % 5 === 0) {
return "Fizz-Buzz";
} else if (number % 3 === 0) {
return "Fizz";
} else if (number % 5 === 0) {
return "Buzz";
}
return "";
}
class FizzBuzz extends Component {
constructor() {
super();
this.state = {
number: 1,
};
this.onClick = this.onClick.bind(this);
}
onClick() {
this.setState({ number: this.state.number + 1 });
}
render() {
return (
<div className="App">
<p>Number: {this.state.number}</p>
<h2>{fizzOrBuzz(this.state.number)}</h2>
<button onClick={this.onClick}>Next</button>
</div>
);
}
}
Setting state and calling fizzbuzz which uses the state would not work as the state may or may not have been updated. You should put fizzbuzz as the callback to ensure the state is already updated before using it.
https://reactjs.org/docs/react-component.html#setstate
I saw there are already answered questions on how to add spinners during fetch requests.
However what I need is to stop showing the animation when the animation completes. The animation completes after the timeout is reached.
Also I have a best practice question.
It's a good practice to empty the resources on componentWillUnmount and clear there the timeout. In the code below I clear the timeout in a if condition, because it has to stop as the height of the element reaches the right level.
Is that ok as I did it? If now, how should it look like to have the same functionality in the componentWillUnmount lifecycle phase?
Here is the animation Component:
class Thermometer extends Component {
state = {
termFill : 0
};
componentDidMount() {
const interval = setInterval(() => {
this.setState({
termFill: this.state.termFill + 10
});
if (this.state.termFill === 110) {
window.clearInterval(interval);
}
}, 200)
}
render() {
const styles = {
height: `${this.state.termFill}px`
};
if (this.state.termFill < 100) {
return (
<section>
<div id="therm-fill" style={styles} />
[MORE CODE - SHORTENED FOR EASIER READING]
)
}
};
And here is the Component that has to appear after the animation disappears.
The steps are like this:
A user enter and uses this tool
The user clicks "calculate"
The animation appears instead or on top of the tool
When the animation completes, the animation Component disappears and the tool
is once again visible with its updated state (results of the
calculation).
class DiagnoseTool extends Component {
state = {
[OTHER STATES REMOVED TO KEEP THE CODE SHORTER]
wasBtnClicked: false
};
[OTHER RADIO AND CHECKBOX HANDLERS REMOVED TO KEEP THE CODE SHORTER]
onButtonClick = e => {
e.preventDefault();
this.calculate();
this.setState({
wasBtnClicked: true
})
};
addResult = () => {
const resultColor = {
backgroundColor: "orange"
};
let theResult;
if (this..... [CODE REMOVED TO HAVE THE CODE SHORTER]
return theResult;
};
calculate = () => {
let counter = 0;
let radiocounter = 0;
Object.keys(this.state).filter(el => ['cough', 'nodes', 'temperature', 'tonsillarex'].includes(el)).forEach(key => {
// console.log(this.state[key]);
if (this.state[key] === true) {
counter += 1;
}
});
if (this.state.radioAge === "age14") {
radiocounter++
} else if (this.state.radioAge === "age45") {
radiocounter--
}
if (this.state.radioAge !== "") {
this.setState({
isDisabled: false
})
}
this.setState({
points: counter + radiocounter
});
};
render() {
const {cough, nodes, temperature, tonsillarex, radioAge, wasBtnClicked} = this.state;
return (
<Container>
<BackArrow />
[JSX REMOVED TO KEEP THE CODE SHORTER]
<div className="resultbox">
{
(wasBtnClicked) && this.addResult()
}
</div>
</div>
[HERE IS THE BUTTON]
<button
style={{height: "40px", width: "150px", cursor:"pointer"}}
type="submit"
className="calculateBtn"
onClick={this.onButtonClick}
disabled={!radioAge}
>CALCULATE</button>
</Container>
Add a boolean to your state and set it to false, when the user clicks the button set it to true, after doing the calculation set it to false.
calculate = () => {
let counter = 0;
let radiocounter = 0;
this.setState({
isLoading: true // set is loading to true and show the spinner
})
Object.keys(this.state)
.filter(el =>
["cough", "nodes", "temperature", "tonsillarex"].includes(el)
)
.forEach(key => {
// console.log(this.state[key]);
if (this.state[key] === true) {
counter += 1;
}
});
if (this.state.radioAge === "age14") {
radiocounter++;
} else if (this.state.radioAge === "age45") {
radiocounter--;
}
if (this.state.radioAge !== "") {
this.setState({
isDisabled: false
});
}
this.setState({
points: counter + radiocounter,
isLoading: false // set it to false and display the results of the calculation
});
};
Example
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel">
class App extends React.Component {
timer = null;
constructor() {
super();
this.state = {
result: '',
isLoading: false
};
}
showContent = () => { this.setState({ isLoading: false, result: `7 + 5 = ${7 + 5}` })}
calculate = () => {
this.setState({
isLoading: true,
result: ''
});
this.timer = setTimeout(this.showContent, 5000);
}
componentWillUnmount = () => {
clearTimeout(this.timer);
}
render() {
return (
<div>
<p>7 + 5</p>
<p>{this.state.result}</p>
{ this.state.isLoading
? <p>Calculating...</p>
: <button onClick={this.calculate}>Calculate</button>
}
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>
I am building a React app that - among other things - generates a random number when a button is clicked and then filters an array of JSON objects to only the one at the index of that random number (i.e. JSON[random]). Normally the app is supposed to re-render after the array of JSON objects is filtered, but for some reason, on the first time the button is clicked and a random is picked, it requires two clicks to update. From then on it updates as expected, with a new random rendering each time the button is clicked.
I'm not sure if the problem is coming from App.js or somewhere lower down. On the first click, it generates a new random and supposedly saves this to state, but fails to re-render right away. On subsequent clicks, things seem to update based on the previously-generated random, while a new random is put in the queue. I would prefer the this all happens in one go: click, generate random, save to state, update to reflect the new random à la JSON[random].
This might have something to do with the way I have implemented lifecycle methods, as I'm admittedly not sure of all the nuances of each and have just tried to use whichever ones seemed to do what I wanted. If you have any suggestions there, please let me know...
Thanks!
Here are the relevant files:
App.js - where the random is generated and stored when a new click is registered in Header.state.randomClicks
class App extends Component {
constructor(props){
super(props)
this.state = {headerLink: "", searchValue: "", random: 0, randomClicks: 0}
this.generateRandom = this.generateRandom.bind(this);
}
getLinkFromHeader = (link) => {
if (this.state.headerLink !== link) {
this.setState({
headerLink: link,
})
}
}
getSearchValueFromHeader = (string) => {
this.setState({
searchValue: string,
});
}
getRandomMax = (max) => {
this.setState({
randomMax: max,
})
}
getRandomClicks = (value) => {
this.setState({
randomClicks: value,
})
}
generateRandom(number) {
let random = Math.floor(Math.random() * number) + 1;
console.log("generateRandom = ", random)
return random
}
shouldComponentUpdate(nextProps, nextState) {
return this.state.randomClicks !== nextState.randomClicks;
}
componentWillUpdate() {}
componentDidUpdate(prevState) {
let randomClicks = this.state.randomClicks;
console.log("this.state.randomClicks: ", this.state.randomClicks)
// console.log("prevState: ", prevState)
// console.log("prevState.randomClicks = ", prevState.randomClicks)
// ^^ is this a bug ? ^^
let random = this.generateRandom(this.state.randomMax);
if (this.state.random !== random) {
this.setState({random: random})
}
}
render() {
return (
<div className="App background">
<div className="content">
<Header getLinkFromHeader={this.getLinkFromHeader} getSearchValueFromHeader={this.getSearchValueFromHeader} randomClick={this.randomClick} getRandomClicks={this.getRandomClicks}/>
<TilesContainer link={this.state.headerLink} searchValue={this.state.searchValue} getRandomMax={this.getRandomMax} random={this.state.random} randomClicks={this.state.randomClicks}/>
</div>
</div>
);
}
}
export default App
Header.js* - where the randomClick count is incremented each time RandomButton is clicked
class Header extends Component {
constructor(props){
super(props);
this.state = { selectorLink: "", searchValue: "", randomClicks: 0 }
this.randomClick = this.randomClick.bind(this);
}
getLinkFromSelector = (link) => {
this.setState({
selectorLink: link,
})
}
getSearchValue = (string) => {
this.setState({
searchValue: string,
})
}
shouldComponentUpdate(nextProps, nextState) {
console.log("this.state !== nextState: ", this.state !== nextState)
return this.state !== nextState;
}
componentDidUpdate(previousState){
if(this.state.selectorLink !== previousState.selectorLink) {
this.props.getLinkFromHeader(this.state.selectorLink);
}
this.props.getSearchValueFromHeader(this.state.searchValue);
this.props.getRandomClicks(this.state.randomClicks);
console.log("Header Did Update")
}
randomClick(){
this.props.randomClick;
this.setState({
randomClicks: this.state.randomClicks += 1,
});
}
render(){
return(
<div id="header" className="header">
<div className="title-div">
<div className="h1-wrapper title-wrapper">
<h1>Pokédex Viewer App</h1>
</div>
</div>
<PokedexSelector getLinkFromSelector={this.getLinkFromSelector}/>
<SearchBar getSearchValue={this.getSearchValue}/>
<button type="button" id="random-button" onClick={this.randomClick}>Random Pokémon</button>
<button type="button" id="show-all-button" onClick={this.showAllClick}>Show All</button>
</div>
)
}
}
export default Header
TilesContainer.js - where the random number from App is sent and the tiles list is filtered/re-rendered
class TilesContainer extends Component {
constructor(props){
super(props);
this.state = {
pokemon: [],
filteredPokemon: [],
randomMax: 0,
showDetails: false,
};
this.getPokemon = this.getPokemon.bind(this);
this.tiles = this.tiles.bind(this);
this.getPokemon(this.props.link);
}
getPokemon(pokedexLink) {
let link = "";
(pokedexLink === "")
? link = "https://pokeapi.co/api/v2/pokedex/national/"
: link = this.props.link;
fetch(link)
.then(response => response.json())
.then(myJson => {
let list = myJson['pokemon_entries'];
this.setState({
pokemon: list,
randomMax: list.length,
})
this.props.getRandomMax; // send randomMax to App
})
}
filterPokemon(string) {
if (string !== "") {
console.log("string: ", string)
string = string.toString().toLowerCase()
let filteredPokemon = this.state.pokemon.filter(pokemon => {
const name = pokemon.pokemon_species.name;
const nameStr = name.slice(0,string.length);
const number = pokemon.entry_number;
const numberStr = number.toString().slice(0, string.length);
return (this.state.random !== 0) ? number.toString() === string : nameStr === string || numberStr === string;
})
if (this.props.randomClicks !== 0) { // i.e. using a random
this.setState({
filteredPokemon: filteredPokemon,
})
} else {
this.setState({
filteredPokemon: filteredPokemon,
randomMax: filteredPokemon.length,
})
}
} else {
this.setState({
filteredPokemon: [],
randomMax: this.state.pokemon.length,
})
}
}
componentDidUpdate(prevProps, prevState) {
if (this.props.link !== prevProps.link) {
this.getPokemon(this.props.link)
}
if (this.props.searchValue !== prevProps.searchValue) {
this.filterPokemon(this.props.searchValue)
}
if (this.state.randomMax !== prevState.randomMax){
this.props.getRandomMax(this.state.randomMax);
}
if (this.props.random !== prevProps.random) {
console.log("TilesContainer random: ", this.props.random)
this.filterPokemon(this.props.random)
}
}
tiles() {
console.log("tiles() filteredPokemon: ", this.state.filteredPokemon)
console.log("tiles() searchValue: ", this.props.searchValue)
console.log("tiles() random: ", this.props.random)
if (this.state.pokemon.length > 0) {
if (this.state.filteredPokemon.length == 0 && this.props.searchValue === ""){
return (
this.state.pokemon.map(pokemon => (
<Tile key={pokemon.entry_number} number={pokemon.entry_number} name={pokemon.pokemon_species.name} url={pokemon.pokemon_species.url}/>
))
)
} else if (this.state.filteredPokemon.length > 0){
return (
this.state.filteredPokemon.map(pokemon => (
<Tile key={pokemon.entry_number} number={pokemon.entry_number} name={pokemon.pokemon_species.name} url={pokemon.pokemon_species.url}/>
))
)
}
}
}
render(){
return (
<div id="tiles-container"
className="tiles-container">
{this.tiles()}
</div>
)
}
}
export default TilesContainer
You should not use current state in setState and should not modify state directly. And you do no actually call this.props.randomClick and it is undefined. Change
randomClick(){
this.props.randomClick;
this.setState({
randomClicks: this.state.randomClicks += 1,
});
}
to
randomClick(){
if (typeof(this.props.randomClick) === 'function') this.props.randomClick();
this.setState(olState => ({
randomClicks: olState.randomClicks + 1,
}));
}
Also check your shouldComponentUpdate methods. They might be buggy or redundant. Looks like you prevent updating App when state.random changes. So every time you click the button you store the new random value but use the previous one. So for the initial render and for the first click you use random: 0.
And I guess that getRandomClicks should be setRandomClicks.