Reputation: 851
componentDidMount(prevProps, prevState, prevContext) {
let [audioNode, songLen] = [this.refs.audio, List.length-1];
audioNode.addEventListener('ended', () => {
this._endedPlay(songLen, () => {
this._currSong(this.state.songIndex);
this._Play(audioNode);
});
});
audioNode.addEventListener('timeupdate', () => {
let [remainTime, remainTimeMin, remainTimeSec, remainTimeInfo] = [];
if(!isNaN(audioNode.duration)) {
remainTime = audioNode.duration - audioNode.currentTime;
remainTimeMin = parseInt(remainTime/60); // 剩余分
remainTimeSec = parseInt(remainTime%60); // 剩余秒
if(remainTimeSec < 10) {
remainTimeSec = '0'+remainTimeSec;
}
remainTimeInfo = remainTimeMin + ':' + remainTimeSec;
this.setState({'time': remainTimeInfo});
}
});
}
componentWillUnmount () {
let audio = this.refs.audio;
audio.removeEventListener('timeupdate');
audio.removeEventListener('ended');
}
Error:
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 undefined component.
I removeEventListener 'ended' in componentWillUnmount
, but it is not working. because I add this.setState({'time': remainTimeInfo});
in componentDidMount
.
Upvotes: 79
Views: 112211
Reputation: 3868
I solved this by assigning a ref to the component and then checking if the ref exists before setting the state:
myMethod(){
if (this.refs.myRef)
this.setState({myVar: true});
}
render() {
return (
<div ref="myRef">
{this.state.myVar}
</div>
);
}
And lately, since I am using mostly functional components, I am using this pattern:
const Component = () => {
const ref = React.useRef(null);
const [count, setCount] = React.useState(0);
const increment = () => {
setTimeout(() => { // usually fetching API data here
if (ref.current !== null) {
setCount((count) => count + 1);
}
}, 100);
};
return (
<button onClick={increment} ref={ref}>
Async Increment {count}
</button>
);
};
Upvotes: 101
Reputation: 2742
componentWillUnmount
setState
,Upvotes: 0
Reputation: 45
I had this problem before, and solved it according to React official page isMounted is an Antipattern.
Set a property isMounted
flag to be true in componentDidMount
, and toggle it false in componentWillUnmount
. When you setState()
in your callbacks, check isMounted
first! It works for me.
state = {
isMounted: false
}
componentDidMount() {
this.setState({isMounted: true})
}
componentWillUnmount(){
this.setState({isMounted: false})
}
callback:
if (this.state.isMounted) {
this.setState({'time': remainTimeInfo});}
Upvotes: 2
Reputation: 998
Having named method in place of the anonymous function in audioNode.addEventListener
's callback should eliminate the subject warning:
componentDidMount(prevProps, prevState, prevContext) {
let [audioNode, songLen] = [this.refs.audio, List.length-1];
audioNode.addEventListener('ended', () => {
this._endedPlay(songLen, () => {
this._currSong(this.state.songIndex);
this._Play(audioNode);
});
});
audioNode.addEventListener('timeupdate', this.callbackMethod );
}
callBackMethod = () => {
let [remainTime, remainTimeMin, remainTimeSec, remainTimeInfo] = [];
if(!isNaN(audioNode.duration)) {
remainTime = audioNode.duration - audioNode.currentTime;
remainTimeMin = parseInt(remainTime/60); // 剩余分
remainTimeSec = parseInt(remainTime%60); // 剩余秒
if(remainTimeSec < 10) {
remainTimeSec = '0'+remainTimeSec;
}
remainTimeInfo = remainTimeMin + ':' + remainTimeSec;
this.setState({'time': remainTimeInfo});
}
}
And yes, named method is needed anyways because removeEventListener
won't work with anonymous callbacks, as mentioned above several times.
Upvotes: 0
Reputation: 2751
I was getting this warning when I wanted to show a popup (bootstrap modal) on success/failure callback of Ajax request. Additionally setState was not working and my popup modal was not being shown.
Below was my situation-
<Component /> (Containing my Ajax function)
<ChildComponent />
<GrandChildComponent /> (Containing my PopupModal, onSuccess callback)
I was calling ajax function of component from grandchild component passing a onSuccess Callback (defined in grandchild component) which was setting state to show popup modal.
I changed it to -
<Component /> (Containing my Ajax function, PopupModal)
<ChildComponent />
<GrandChildComponent />
Instead I called setState (onSuccess Callback) to show popup modal in component (ajax callback) itself and problem solved.
In 2nd case: component was being rendered twice (I had included bundle.js two times in html).
Upvotes: 0
Reputation: 27584
I encountered this issue because I used setState
instead of state
in the constructor.
EXAMPLE
Change the following incorrect code
constructor(props) {
super(props);
this.setState({
key: ''
});
}
to
constructor(props) {
super(props);
this.state = {
key: ''
};
}
Upvotes: 5
Reputation: 4552
I have faced same problem since I have updated the latest react version. Solved like below.
My code was
async componentDidMount() {
const { default: Component } = await importComponent();
Nprogress.done();
this.setState({
component: <Component {...this.props} />
});
}
Changed to
componentWillUnmount() {
this.mounted = false;
}
async componentDidMount() {
this.mounted = true;
const { default: Component } = await importComponent();
if (this.mounted) {
this.setState({
component: <Component {...this.props} />
});
}
}
Upvotes: 3
Reputation: 51
addEventListener and removeEventListener,the Callback must not be Anonymous inner class,and they should have the same params
Upvotes: 1
Reputation: 11
A lot of the answers in this thread get the point of using refs, but I think a complete example would be good. Since you're operating on an actual DOM node by using the event listener and stepping out of the React context, a ref should be considered the standard solution. Here's a complete example:
class someComponent extends Component {
constructor(props) {
super(props)
this.node = null
}
render() {
return (
<div ref={node => { this.node = node }}>Content</div>
)
}
handleEvent(event) {
if (this.node) {
this.setState({...})
}
}
componentDidMount() {
//as soon as render completes, the node will be registered.
const handleEvent = this.handleEvent.bind(this)
this.node.addEventListener('click', handleEvent)
}
componentWillUnmount() {
const handleEvent = this.handleEvent.bind(this)
this.node.removeEventListener('click', handleEvent)
}
}
Upvotes: 1
Reputation: 8879
Edit: isMounted
is deprecated and will probably be removed in later versions of React. See this and this, isMounted is an Antipattern.
As the warning states, you are calling this.setState
on an a component that was mounted but since then has been unmounted.
To make sure your code is safe, you can wrap it in
if (this.isMounted()) {
this.setState({'time': remainTimeInfo});
}
to ensure that the component is still mounted.
Upvotes: 3
Reputation: 86240
removeEventListener
has the same signature as addEventListener
. All of the arguments must be exactly the same for it to remove the listener.
var onEnded = () => {};
audioNode.addEventListener('ended', onEnded, false);
this.cleanup = () => {
audioNode.removeEventListener('ended', onEnded, false);
}
And in componentWillUnmount call this.cleanup()
.
Upvotes: 23