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
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.
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)
})
}
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.
Consider an input of type number, I would like this number input to only allow a user to enter one positive, non-zero, integer (no decimals) number. A simple implementation using min and step looks like this:
class PositiveIntegerInput extends React.Component {
render () {
return <input type='number' min='1' step='1'></input>
}
}
ReactDOM.render(
<PositiveIntegerInput />,
document.getElementById('container')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<p>
Try to input a decimal or negative number or zero:
</p>
<div id="container"></div>
The above code works fine if a user sticks to ONLY clicking the up/down arrows in the number input, but as soon a the user starts using the keyboard they will have no problem entering numbers like -42, 3.14 and 0
Ok, lets try adding some onKeyDown handling to disallow this loophole:
class PositiveIntegerInput extends React.Component {
constructor (props) {
super(props)
this.handleKeypress = this.handleKeypress.bind(this)
}
handleKeypress (e) {
const characterCode = e.key
if (characterCode === 'Backspace') return
const characterNumber = Number(characterCode)
if (characterNumber >= 0 && characterNumber <= 9) {
if (e.currentTarget.value && e.currentTarget.value.length) {
return
} else if (characterNumber === 0) {
e.preventDefault()
}
} else {
e.preventDefault()
}
}
render () {
return (
<input type='number' onKeyDown={this.handleKeypress} min='1' step='1'></input>
)
}
}
ReactDOM.render(
<PositiveIntegerInput />,
document.getElementById('container')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<p>
Try to input a decimal or negative number or zero:
</p>
<div id="container"></div>
Now everything almost appears to work as desired. However if a user highlights all the digits in the text input and then types over this selection with a 0 the input will allow 0 to be entered as a value.
To fix this issue I added an onBlur function that checks if the input value is 0 and if so changes it to a 1:
class PositiveIntegerInput extends React.Component {
constructor (props) {
super(props)
this.handleKeypress = this.handleKeypress.bind(this)
this.handleBlur = this.handleBlur.bind(this)
}
handleBlur (e) {
if (e.currentTarget.value === '0') e.currentTarget.value = '1'
}
handleKeypress (e) {
const characterCode = e.key
if (characterCode === 'Backspace') return
const characterNumber = Number(characterCode)
if (characterNumber >= 0 && characterNumber <= 9) {
if (e.currentTarget.value && e.currentTarget.value.length) {
return
} else if (characterNumber === 0) {
e.preventDefault()
}
} else {
e.preventDefault()
}
}
render () {
return (
<input
type='number'
onKeyDown={this.handleKeypress}
onBlur={this.handleBlur}
min='1'
step='1'
></input>
)
}
}
ReactDOM.render(
<PositiveIntegerInput />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<p>
Try to input a decimal or negative number or zero:
</p>
<div id="container"></div>
Is there a better way to implement a number input with this type of criteria? It seems pretty crazy to write all this overhead for an input to allow only positive, non-zero integers... there must be a better way.
If you did it as a controlled input with the value in component state, you could prevent updating state onChange if it didn't meet your criteria. e.g.
class PositiveInput extends React.Component {
state = {
value: ''
}
onChange = e => {
//replace non-digits with blank
const value = e.target.value.replace(/[^\d]/,'');
if(parseInt(value) !== 0) {
this.setState({ value });
}
}
render() {
return (
<input
type="text"
value={this.state.value}
onChange={this.onChange}
/>
);
}
}
Here's a number spinner implantation in React Bootstrap. It only accepts positive integers and you can set min, max and default values.
class NumberSpinner extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
oldVal: 0,
value: 0,
maxVal: 0,
minVal: 0
};
this.handleIncrease = this.handleIncrease.bind(this);
this.handleDecrease = this.handleDecrease.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleBlur = this.handleBlur.bind(this);
}
componentDidMount() {
this.setState({
value: this.props.value,
minVal: this.props.min,
maxVal: this.props.max
});
}
handleBlur() {
const blurVal = parseInt(this.state.value, 10);
if (isNaN(blurVal) || blurVal > this.state.maxVal || blurVal < this.state.minVal) {
this.setState({
value: this.state.oldVal
});
this.props.changeVal(this.state.oldVal, this.props.field);
}
}
handleChange(e) {
const re = /^[0-9\b]+$/;
if (e.target.value === '' || re.test(e.target.value)) {
const blurVal = parseInt(this.state.value, 10);
if (blurVal <= this.state.maxVal && blurVal >= this.state.minVal) {
this.setState({
value: e.target.value,
oldVal: this.state.value
});
this.props.changeVal(e.target.value, this.props.field);
} else {
this.setState({
value: this.state.oldVal
});
}
}
}
handleIncrease() {
const newVal = parseInt(this.state.value, 10) + 1;
if (newVal <= this.state.maxVal) {
this.setState({
value: newVal,
oldVal: this.state.value
});
this.props.changeVal(newVal, this.props.field);
};
}
handleDecrease() {
const newVal = parseInt(this.state.value, 10) - 1;
if (newVal >= this.state.minVal) {
this.setState({
value: newVal,
oldVal: this.state.value
});
this.props.changeVal(newVal, this.props.field);
};
}
render() {
return ( <
ReactBootstrap.ButtonGroup size = "sm"
aria-label = "number spinner"
className = "number-spinner" >
<
ReactBootstrap.Button variant = "secondary"
onClick = {
this.handleDecrease
} > - < /ReactBootstrap.Button> <
input value = {
this.state.value
}
onChange = {
this.handleChange
}
onBlur = {
this.handleBlur
}
/> <
ReactBootstrap.Button variant = "secondary"
onClick = {
this.handleIncrease
} > + < /ReactBootstrap.Button> < /
ReactBootstrap.ButtonGroup >
);
}
}
class App extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
value1: 1,
value2: 12
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(value, field) {
this.setState({ [field]: value });
}
render() {
return (
<div>
<div>Accept numbers from 1 to 10 only</div>
< NumberSpinner changeVal = {
() => this.handleChange
}
value = {
this.state.value1
}
min = {
1
}
max = {
10
}
field = 'value1'
/ >
<br /><br />
<div>Accept numbers from 10 to 20 only</div>
< NumberSpinner changeVal = {
() => this.handleChange
}
value = {
this.state.value2
}
min = {
10
}
max = {
20
}
field = 'value2'
/ >
<br /><br />
<div>If the number is out of range, the blur event will replace it with the last valid number</div>
</div>);
}
}
ReactDOM.render( < App / > ,
document.getElementById('root')
);
.number-spinner {
margin: 2px;
}
.number-spinner input {
width: 30px;
text-align: center;
}
<script src="https://unpkg.com/react#16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/react-bootstrap#next/dist/react-bootstrap.min.js" crossorigin></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css" crossorigin="anonymous">
<div id="root" />
That's how number input works. To simplify the code you could try to use validity state (if your target browsers support it)
onChange(e) {
if (!e.target.validity.badInput) {
this.setState(Number(e.target.value))
}
}
I had a similar problem when I need to allow only positive number, fount solution on another question on StackOverflow(https://stackoverflow.com/a/34783480/5646315).
Example code that I implemented for react-final-form.
P.S: it is not the most elegant solution.
onKeyDown: (e: React.KeyboardEvent) => {
if (!((e.keyCode > 95 && e.keyCode < 106) || (e.keyCode > 47 && e.keyCode < 58) || e.keyCode === 8)) {
e.preventDefault()
}
},
class BasketItem extends React.Component {
constructor(props) {
super(props);
this.state = {
countBasketItem: props.qnt,
};
}
componentDidMount() {
const $ = window.$;
// using jquery-styler-form(bad practice)
$('input[type="number"]').styler();
// minus 1
$(`#basket_${this.props.id} .jq-number__spin.minus`).click(() => {
if (this.state.countBasketItem > 1) {
this.setState({ countBasketItem: +this.state.countBasketItem - 1 });
this.setCountProduct();
}
});
// plus 1
$(`#basket_${this.props.id} .jq-number__spin.plus`).click(() => {
this.setState({ countBasketItem: +this.state.countBasketItem + 1 });
this.setCountProduct();
});
}
onChangeCount = (e) => {
let countBasketItem = +e.target.value
countBasketItem = (countBasketItem === 0) ? '' : (countBasketItem > 999) ? 999 : countBasketItem;
this.setState({ countBasketItem })
};
onBlurCount() {
// number empty
if (+this.state.countBasketItem == 0 || isNaN(+this.state.countBasketItem)) {
this.setState({ countBasketItem: 1 });
}
this.setCountProduct();
}
setCountProduct = (colrKey = this.props.colr.key, idProduct = this.props.product.id, qnt) => {
qnt = +this.state.countBasketItem || 1; // if don't work setState
this.props.basket.editCountProduct(idProduct, colrKey, qnt); // request on server
};
render() {
return;
<input
type="number"
className="number"
min="1"
value={this.state.countBasketItem}
onChange={this.onChangeCount.bind(this)}
// onFocused
onBlur={this.onBlurCount.bind(this)}
// input only numbers
onKeyPress={(event) => {
if (!/[0-9]/.test(event.key)) {
event.preventDefault();
}
}}
/>;
}
}
This is not a react problem, but a html problem as you can see over here https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number and I have made a stateless example you can see right here
https://codesandbox.io/s/l5k250m87
Sorry I couldn't come up with a more specific title for this question. When I execute the below snippet I get the following warning:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the Typewriter component.
However, if the render() in MyComponent is changed to the following, I get no such warning:
render() {
return (
<div>
<h1>
<Typewriter />
{ this.state.render == 1 && "Render 1" }
{ this.state.render == 2 && "Render 2" }
{ this.state.render == 3 && "Render 3" }
</h1>
</div>
);
}
How do I properly unmount this rendered Typewriter component that itself is performing some mounting and unmounting actions? Thanks!
class Typewriter extends React.Component {
constructor(props) {
super();
this.state = {
finalText: ''
}
this.typeWriter = this.typeWriter.bind(this);
}
typeWriter(text, n) {
if (n < (text.length)) {
if (n + 1 == (text.length)) {
let j = text.substring(0, n+1);
this.setState({ finalText: j });
n++;
}
else {
let k = text.substring(0, n+1) + '|';
this.setState({ finalText: k });
n++;
}
setTimeout( () => { this.typeWriter(text, n) }, 100 );
}
}
componentDidMount() {
this.typeWriter('testing_typewriter', 0);
}
render() {
return (
<div>
{ this.state.finalText }
</div>
);
}
}
class MyComponent extends React.Component {
constructor(props) {
super();
this.state = {
render: 1,
update: false
};
this.interval = null;
}
componentDidMount() {
this.interval = setTimeout( () =>
this.rendering(), 1700
);
}
componentWillUpdate(nextProps, nextState) {
if (this.state.render < 3) {
this.interval = setTimeout( () =>
this.rendering(), 1200
);
}
}
componentWillUnmount() {
clearInterval(this.interval);
this.interval = null;
}
rendering() {
if (this.state.render < 3) {
if (this.interval) {
this.setState({ render: this.state.render + 1 });
}
}
}
render() {
return (
<div>
<h1>
{ this.state.render == 1 && "Render 1" }
{ this.state.render == 2 && <Typewriter /> }
{ this.state.render == 3 && "Render 3" }
</h1>
</div>
);
}
}
ReactDOM.render(<MyComponent />, app);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="app"></div>
I had a similar issue and I solved it by clearing the Timeout/Interval in the componentWillUnmount function
in the typewriter function you need to keep track of this timeout:
setTimeout( () => { this.typeWriter(text, n) }, 100 );
with something like
this._timer = setTimeout( () => { this.typeWriter(text, n) }, 100 );
Then add a lifecycle method:
componentWillUnmount() {
window.clearTimeout(this._timer);
}