Use React hook to implement a self-increment counter [duplicate]
This question already has an answer here:
State not updating when using React state hook within setInterval
1 answer
The code is here: https://codesandbox.io/s/nw4jym4n0
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
});
return <h1>{counter}</h1>;
};
The problem is each setCounter
trigger re-rendering so the interval got reset and re-created. This might looks fine since the state(counter) keeps incrementing, however it could freeze when combining with other hooks.
What's the correct way to do this? In class component it's simple with a instance variable holding the interval.
javascript reactjs react-hooks
marked as duplicate by Yangshun Tay, Community♦ Nov 21 at 3:45
This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.
add a comment |
This question already has an answer here:
State not updating when using React state hook within setInterval
1 answer
The code is here: https://codesandbox.io/s/nw4jym4n0
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
});
return <h1>{counter}</h1>;
};
The problem is each setCounter
trigger re-rendering so the interval got reset and re-created. This might looks fine since the state(counter) keeps incrementing, however it could freeze when combining with other hooks.
What's the correct way to do this? In class component it's simple with a instance variable holding the interval.
javascript reactjs react-hooks
marked as duplicate by Yangshun Tay, Community♦ Nov 21 at 3:45
This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.
add a comment |
This question already has an answer here:
State not updating when using React state hook within setInterval
1 answer
The code is here: https://codesandbox.io/s/nw4jym4n0
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
});
return <h1>{counter}</h1>;
};
The problem is each setCounter
trigger re-rendering so the interval got reset and re-created. This might looks fine since the state(counter) keeps incrementing, however it could freeze when combining with other hooks.
What's the correct way to do this? In class component it's simple with a instance variable holding the interval.
javascript reactjs react-hooks
This question already has an answer here:
State not updating when using React state hook within setInterval
1 answer
The code is here: https://codesandbox.io/s/nw4jym4n0
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
});
return <h1>{counter}</h1>;
};
The problem is each setCounter
trigger re-rendering so the interval got reset and re-created. This might looks fine since the state(counter) keeps incrementing, however it could freeze when combining with other hooks.
What's the correct way to do this? In class component it's simple with a instance variable holding the interval.
This question already has an answer here:
State not updating when using React state hook within setInterval
1 answer
javascript reactjs react-hooks
javascript reactjs react-hooks
edited Nov 20 at 16:35
Yangshun Tay
8,82153667
8,82153667
asked Nov 20 at 14:25
PeiSong
524
524
marked as duplicate by Yangshun Tay, Community♦ Nov 21 at 3:45
This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.
marked as duplicate by Yangshun Tay, Community♦ Nov 21 at 3:45
This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.
add a comment |
add a comment |
3 Answers
3
active
oldest
votes
You want to give an empty array as second argument to useEffect
so that the function is only run once after the initial render.
Because of how closures work, this will make the counter
variable always reference the initial value. You can use the function version of setCounter
instead to always get the correct value.
Example
const { useState, useEffect } = React;
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter => counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, );
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="root"></div>
add a comment |
As another answer already shows, it's possible to make it useEffect
callback run only once and work similarly to componentDidMount
. In this case it's necessary to use state updater function due to the limitations imposed by function scopes, otherwise updated counter
won't be available inside setInterval
callback.
The alternative is to make useEffect
callback run on each counter update. In this case setInterval
should be replaced with setTimeout
, and updates should be limited to counter
updates:
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearTimeout(timeout);
};
}, [counter]);
return <h1>{counter}</h1>;
};
This is not ideal as you will keep runsetTimeout
on every render and seems kind of unnecessary.
– Yangshun Tay
Nov 20 at 16:33
I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.
– estus
Nov 20 at 16:43
add a comment |
The correct way to do this would be to run the effect only once. Since you only need to run the effect once because during mounting, you can pass in an empty array as a second argument to achieve.
However, you will need to change setCounter
to use the previous value of counter
. The reason is because the callback passed into setInterval
's closure only accesses the counter
variable in the first render, it doesn't have access to the new counter
value in the subsequent render because the useEffect()
is not invoked the second time; counter
always has the value of 0 within the setInterval
callback.
Like the setState
you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. You should use the second form and read the latest state value within the setState
callback to ensure that you have the latest state value before incrementing it.
function Counter() {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const timer = setInterval(() => {
setCounter(prevCount => prevCount + 1); // <-- Change this line!
}, 1000);
return () => {
clearInterval(timer);
};
}, ); // Pass in empty array to run effect only once!
return (
<div>Count: {counter}</div>
);
}
ReactDOM.render(<Counter />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
add a comment |
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
You want to give an empty array as second argument to useEffect
so that the function is only run once after the initial render.
Because of how closures work, this will make the counter
variable always reference the initial value. You can use the function version of setCounter
instead to always get the correct value.
Example
const { useState, useEffect } = React;
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter => counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, );
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="root"></div>
add a comment |
You want to give an empty array as second argument to useEffect
so that the function is only run once after the initial render.
Because of how closures work, this will make the counter
variable always reference the initial value. You can use the function version of setCounter
instead to always get the correct value.
Example
const { useState, useEffect } = React;
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter => counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, );
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="root"></div>
add a comment |
You want to give an empty array as second argument to useEffect
so that the function is only run once after the initial render.
Because of how closures work, this will make the counter
variable always reference the initial value. You can use the function version of setCounter
instead to always get the correct value.
Example
const { useState, useEffect } = React;
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter => counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, );
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="root"></div>
You want to give an empty array as second argument to useEffect
so that the function is only run once after the initial render.
Because of how closures work, this will make the counter
variable always reference the initial value. You can use the function version of setCounter
instead to always get the correct value.
Example
const { useState, useEffect } = React;
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter => counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, );
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="root"></div>
const { useState, useEffect } = React;
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter => counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, );
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="root"></div>
const { useState, useEffect } = React;
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter => counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, );
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="root"></div>
answered Nov 20 at 14:35
Tholle
33.7k53760
33.7k53760
add a comment |
add a comment |
As another answer already shows, it's possible to make it useEffect
callback run only once and work similarly to componentDidMount
. In this case it's necessary to use state updater function due to the limitations imposed by function scopes, otherwise updated counter
won't be available inside setInterval
callback.
The alternative is to make useEffect
callback run on each counter update. In this case setInterval
should be replaced with setTimeout
, and updates should be limited to counter
updates:
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearTimeout(timeout);
};
}, [counter]);
return <h1>{counter}</h1>;
};
This is not ideal as you will keep runsetTimeout
on every render and seems kind of unnecessary.
– Yangshun Tay
Nov 20 at 16:33
I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.
– estus
Nov 20 at 16:43
add a comment |
As another answer already shows, it's possible to make it useEffect
callback run only once and work similarly to componentDidMount
. In this case it's necessary to use state updater function due to the limitations imposed by function scopes, otherwise updated counter
won't be available inside setInterval
callback.
The alternative is to make useEffect
callback run on each counter update. In this case setInterval
should be replaced with setTimeout
, and updates should be limited to counter
updates:
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearTimeout(timeout);
};
}, [counter]);
return <h1>{counter}</h1>;
};
This is not ideal as you will keep runsetTimeout
on every render and seems kind of unnecessary.
– Yangshun Tay
Nov 20 at 16:33
I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.
– estus
Nov 20 at 16:43
add a comment |
As another answer already shows, it's possible to make it useEffect
callback run only once and work similarly to componentDidMount
. In this case it's necessary to use state updater function due to the limitations imposed by function scopes, otherwise updated counter
won't be available inside setInterval
callback.
The alternative is to make useEffect
callback run on each counter update. In this case setInterval
should be replaced with setTimeout
, and updates should be limited to counter
updates:
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearTimeout(timeout);
};
}, [counter]);
return <h1>{counter}</h1>;
};
As another answer already shows, it's possible to make it useEffect
callback run only once and work similarly to componentDidMount
. In this case it's necessary to use state updater function due to the limitations imposed by function scopes, otherwise updated counter
won't be available inside setInterval
callback.
The alternative is to make useEffect
callback run on each counter update. In this case setInterval
should be replaced with setTimeout
, and updates should be limited to counter
updates:
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearTimeout(timeout);
};
}, [counter]);
return <h1>{counter}</h1>;
};
answered Nov 20 at 16:08
estus
66.8k2198210
66.8k2198210
This is not ideal as you will keep runsetTimeout
on every render and seems kind of unnecessary.
– Yangshun Tay
Nov 20 at 16:33
I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.
– estus
Nov 20 at 16:43
add a comment |
This is not ideal as you will keep runsetTimeout
on every render and seems kind of unnecessary.
– Yangshun Tay
Nov 20 at 16:33
I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.
– estus
Nov 20 at 16:43
This is not ideal as you will keep run
setTimeout
on every render and seems kind of unnecessary.– Yangshun Tay
Nov 20 at 16:33
This is not ideal as you will keep run
setTimeout
on every render and seems kind of unnecessary.– Yangshun Tay
Nov 20 at 16:33
I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.
– estus
Nov 20 at 16:43
I don't see anything wrong with this. Performance concerns are nonexistent. Recursive setTimeout is a common replacement for setInterval in JS when it's justified. State updater function looks fine in setInterval in this particular case, i.e. for simple increment. This would be very different if several states were involved.
– estus
Nov 20 at 16:43
add a comment |
The correct way to do this would be to run the effect only once. Since you only need to run the effect once because during mounting, you can pass in an empty array as a second argument to achieve.
However, you will need to change setCounter
to use the previous value of counter
. The reason is because the callback passed into setInterval
's closure only accesses the counter
variable in the first render, it doesn't have access to the new counter
value in the subsequent render because the useEffect()
is not invoked the second time; counter
always has the value of 0 within the setInterval
callback.
Like the setState
you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. You should use the second form and read the latest state value within the setState
callback to ensure that you have the latest state value before incrementing it.
function Counter() {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const timer = setInterval(() => {
setCounter(prevCount => prevCount + 1); // <-- Change this line!
}, 1000);
return () => {
clearInterval(timer);
};
}, ); // Pass in empty array to run effect only once!
return (
<div>Count: {counter}</div>
);
}
ReactDOM.render(<Counter />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
add a comment |
The correct way to do this would be to run the effect only once. Since you only need to run the effect once because during mounting, you can pass in an empty array as a second argument to achieve.
However, you will need to change setCounter
to use the previous value of counter
. The reason is because the callback passed into setInterval
's closure only accesses the counter
variable in the first render, it doesn't have access to the new counter
value in the subsequent render because the useEffect()
is not invoked the second time; counter
always has the value of 0 within the setInterval
callback.
Like the setState
you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. You should use the second form and read the latest state value within the setState
callback to ensure that you have the latest state value before incrementing it.
function Counter() {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const timer = setInterval(() => {
setCounter(prevCount => prevCount + 1); // <-- Change this line!
}, 1000);
return () => {
clearInterval(timer);
};
}, ); // Pass in empty array to run effect only once!
return (
<div>Count: {counter}</div>
);
}
ReactDOM.render(<Counter />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
add a comment |
The correct way to do this would be to run the effect only once. Since you only need to run the effect once because during mounting, you can pass in an empty array as a second argument to achieve.
However, you will need to change setCounter
to use the previous value of counter
. The reason is because the callback passed into setInterval
's closure only accesses the counter
variable in the first render, it doesn't have access to the new counter
value in the subsequent render because the useEffect()
is not invoked the second time; counter
always has the value of 0 within the setInterval
callback.
Like the setState
you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. You should use the second form and read the latest state value within the setState
callback to ensure that you have the latest state value before incrementing it.
function Counter() {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const timer = setInterval(() => {
setCounter(prevCount => prevCount + 1); // <-- Change this line!
}, 1000);
return () => {
clearInterval(timer);
};
}, ); // Pass in empty array to run effect only once!
return (
<div>Count: {counter}</div>
);
}
ReactDOM.render(<Counter />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
The correct way to do this would be to run the effect only once. Since you only need to run the effect once because during mounting, you can pass in an empty array as a second argument to achieve.
However, you will need to change setCounter
to use the previous value of counter
. The reason is because the callback passed into setInterval
's closure only accesses the counter
variable in the first render, it doesn't have access to the new counter
value in the subsequent render because the useEffect()
is not invoked the second time; counter
always has the value of 0 within the setInterval
callback.
Like the setState
you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. You should use the second form and read the latest state value within the setState
callback to ensure that you have the latest state value before incrementing it.
function Counter() {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const timer = setInterval(() => {
setCounter(prevCount => prevCount + 1); // <-- Change this line!
}, 1000);
return () => {
clearInterval(timer);
};
}, ); // Pass in empty array to run effect only once!
return (
<div>Count: {counter}</div>
);
}
ReactDOM.render(<Counter />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
function Counter() {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const timer = setInterval(() => {
setCounter(prevCount => prevCount + 1); // <-- Change this line!
}, 1000);
return () => {
clearInterval(timer);
};
}, ); // Pass in empty array to run effect only once!
return (
<div>Count: {counter}</div>
);
}
ReactDOM.render(<Counter />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
function Counter() {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const timer = setInterval(() => {
setCounter(prevCount => prevCount + 1); // <-- Change this line!
}, 1000);
return () => {
clearInterval(timer);
};
}, ); // Pass in empty array to run effect only once!
return (
<div>Count: {counter}</div>
);
}
ReactDOM.render(<Counter />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
edited Nov 20 at 16:42
answered Nov 20 at 16:31
Yangshun Tay
8,82153667
8,82153667
add a comment |
add a comment |