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);
}
Related
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 trying to create a typewriter animation like this in my es6 component (essentially, iteratively renders additional passed elements or letters). However, any time I execute / render this component, all that is rendered is the first element / letter, 'a', of the larger set, 'abc'. The timeout period is working fine, so I think that the for loop is failing. How do I properly run a for loop over a setTimeout function in es6 such that my new elements will render? Thanks.
import React from 'react';
import { CSSTransitionGroup } from 'react-transition-group';
import Radium from 'radium';
export default class Logo extends React.Component {
constructor(props) {
super();
this.state = {
final: ''
}
this.typeWriter = this.typeWriter.bind(this);
}
typeWriter(text, n) {
if (n < (text.length)) {
let k = text.substring(0, n+1);
this.setState({ final: k });
n++;
setTimeout( () => { this.typeWriter(text, n) }, 1000 );
}
}
render() {
this.typeWriter('abc', 0);
return (
<div>
<h1>{this.state.final}</h1>
</div>
);
}
}
module.exports = Radium(Logo);
Since this.typeWriter('abc', 0); is in the render function, whenever the state changes, it runs the typewriter method, which updates the state back to a.
Move the this.typeWriter('abc', 0); to componentDidMount(). It will start the type writer when the component has finished rendering.
class Logo extends React.Component {
constructor(props) {
super();
this.state = {
final: ''
}
this.typeWriter = this.typeWriter.bind(this);
}
typeWriter(text, n) {
if (n < (text.length)) {
let k = text.substring(0, n+1);
this.setState({ final: k });
n++;
setTimeout( () => { this.typeWriter(text, n) }, 1000 );
}
}
componentDidMount() {
this.typeWriter('abc', 0);
}
render() {
return (
<div>
<h1>{this.state.final}</h1>
</div>
);
}
}
ReactDOM.render(
<Logo />,
demo
);
<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>
<div id="demo"></div>
How does one show a counter going from 1 to 2 to 3 to n on the click of a button. I've tried doing a setState in a for loop but thats not worked.
I know react's setState is async, i've even tried to use prevState, but its not worked.
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0
};
this.startCounter = this.startCounter.bind(this);
}
startCounter() {
const self = this;
for (let i = 0; i < 100; i++) {
this.setState(prevState => {
const counter = prevState.counter + 1;
return Object.assign({}, prevState, {counter: counter})
});
}
}
render() {
return (
<div>
Counter Value: {this.state.counter}
<button onClick={this.startCounter}>Start Counter</button>
</div>
)
}
}
export default App;
webpack bin below
https://www.webpackbin.com/bins/-KkU1NJA-ectflyDgf_S
I want to increase the count from 0 to n as a timer of sorts when clicked.
Something like this?
When you run the startCounter() function, you start the interval which increments the counter value by 1, each second. Once it reaches n (5 in this example), it resets.
class App extends React.Component {
constructor() {
super();
this.interval;
this.state = {
counter: 1,
n: 5
};
}
startCounter = () => {
if (this.interval) return; //if the timer is already running, do nothing.
this.interval = setInterval(() => {
let c = (this.state.counter % this.state.n) + 1;
this.setState({
counter: c
});
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval); //remove the interval if the component is unmounted.
}
render() {
return (
<div>
Counter Value: {this.state.counter}
<button onClick={this.startCounter}>Start Counter</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("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>
<div id="app"></div>
for example I have this React component which simply does loading text. I have a problem with refactoring .bind(this) to es6 syntax.
var Loading = React.createClass({
getInitialState:() => {
this.originalText = 'Loading';
return {
text: 'Loading'
}
},
componentDidMount:function() {
let stopper = this.originalText + '...' ;
this.interval = setInterval(function(){
if(this.state.text === stopper) {
this.setState({
text:this.originalText
})
}else {
this.setState({
text: this.state.text + '.'
})
}
}.bind(this), 300)
},
render: function () {
return (
<div style={styles.container}>
<p style={styles.content}>{this.state.text}</p>
</div>
)
}
});
here I want to refactor
}.bind(this), 300)
to ES6 syntax. What would be solution.
You can replace:
this.interval = setInterval(function(){
/* ... */
}.bind(this), 300)
with:
this.interval = setInterval( () => {
/* ... */
}, 300)
That's because arrow functions automatically binds. BTW, I refactored all your component code to ES6:
class Loading extends React.Component {
constructor(props) {
super(props)
this.originalText = 'Loading'
this.state = {
text: 'Loading'
}
}
componentDidMount() {
let stopper = this.originalText + '...' ;
this.interval = setInterval( () => {
if(this.state.text === stopper) {
this.setState({
text:this.originalText
})
} else {
this.setState({
text: this.state.text + '.'
})
}
}, 300)
}
render() {
return (
<div style={styles.container}>
<p style={styles.content}>{this.state.text}</p>
</div>
)
}
}
Fiddle here: https://jsfiddle.net/mrlew/jgtyetwu/
Still }.bind(this), 300). ES6 is backwards compatible.
You could also use an arrow function (as you do to define getInitialState).
function () { ... }.bind(this) is what arrow function is supposed to do.
It is
this.interval = setInterval(() => { ... }, 300)
class Child extends Component {
componentWillAppear(callback) {
console.log('will appear');
callback();
}
componentDidAppear() {
console.log('did appear');
}
componentWillEnter(callback) {
callback();
console.log('will enter');
}
componentDidEnter() {
console.log('did enter');
}
componentWillLeave(callback) {
callback();
console.log('wiil leave');
}
componentDidLeave() {
console.log('did leave');
}
componentWillUnmount() {
console.log('will unmount');
}
render() {
return (
<div key={this.props.children} className="item">{this.props.children}</div>
);
}
}
class Carousel extends Component {
state = {
idx: 0,
items: ['abc', 'def', 'hij']
};
onClick = ()=> {
var idx = this.state.idx + 1;
this.setState({
idx: idx
});
};
getChild(item) {
return <Child>{item}</Child>;
}
render() {
const idx = this.state.idx;
const items = this.state.items;
const item = items[idx];
const child = this.getChild(item);
return (
<div>
<ReactTransitionGroup
component="div"
className="carousel"
transitionName="item"
>
{child}
</ReactTransitionGroup>
<div onClick={this.onClick} className="btn">click me</div>
</div>
);
}
}
At first, i want to make a carousel by ReactCSSTransitionGroup, but there is a simple css transition. If i want to add some events, it could not work.
So, i want to use ReactTransitionGroup. But once i use it, i do not get i want for some hook do not work.
Not all ReactTransitionGroup lifecycle hooks fire! In the above examples, only
'will appear'
'did appear'
logs.
If there is a bug in React?