This is part of an electron app, this action is called on pressing refresh button on UI. I want to make it autorefresh. How do I do it?
Components/counter.js:
export default class Counter extends Component<Props> {
props: Props;
render() {
const {
refresh,
counter
} = this.props;
return (
<button onClick={() => refresh()}>
Refresh
</button>
);
}
}
actions/counter.js:
export function refresh() {
// Do some local CRUD here.
return {
type: NO-OP
};
}
You could create a function that continously calls setTimeout, and stops when the component is unmounted:
Example
class Counter extends Component {
runRefresh = () => {
this.timer = setTimeout(() => {
this.props.refresh();
this.runRefresh();
}, 1000);
};
componentDidMount() {
this.runRefresh();
}
componentWillUnmount() {
clearTimeout(this.timer);
}
render() {
const { refresh, counter } = this.props;
return <button onClick={() => refresh()}>Refresh</button>;
}
}
I am assuming that you need to refresh on regular interval in your application.
So in redux action creators you can write:
Here refreshInterval is defined in action creator.
startRefresh(){
refreshIntervalId = window.setInterval(() => {
refresh();
}, 3000);
}
OR if you are just returning action object from refresh function then you should use redux-thunk
startRefresh => dispatch => (){
refreshIntervalId = window.setInterval(() => {
dispatch(refresh());
}, 3000);
}
You can call this startRefresh function in the componentDidMount lifecycle method of your main app component OR from the component you wish.
componentDidMount(){
this.props.startRefresh()
}
Also you should store the id of this interval and clear the interval on componentWillUnmount lifecycle method.
componentWillUnmount(){
this.props.clearRefreshInterval()
}
clearRefreshInterval would simply be like:
clearRefreshInterval(){
window.clearInterval(refreshIntervalId);
}
Related
Trying to make a button event onMouseDown, a function should run at the end of the set amount of time. The function runs onMouseDown and clears the interval onMouseUp, but the interval still runs after releasing the button.
This is the code currently. I have the interval global and set it in the planting function. It should unset in the notPlanting function, but it does not.
import React from "react";
function PlantDefuser() {
var interval
function planting() {
interval = setInterval(() => {
console.log("Defuser Planted")
}, 1000)
}
function notPlanting() {
console.log(interval)
clearInterval(interval)
}
return (
<button onMouseDown={planting} onMouseUp={notPlanting}>Press and Hold</button>
)
}
export default PlantDefuser
This could help you:
useRef allows us to store and update data in the component without triggering a re-render. Now the only re-render happens when the props are updated.
We can store interval in a ref like so
import { useRef } from "react";
const PlantDefuser = () => {
const interval = useRef();
function planting() {
interval.current = setInterval(() => {
console.log("Defuser Planted");
}, 1000);
}
function notPlanting() {
clearInterval(interval.current);
}
return (
<button onMouseDown={planting} onMouseUp={notPlanting}>
Press and Hold
</button>
);
}
export default PlantDefuser
When you declare variables like so in the function component, it is being created on each render. You should be saving the interval id in a state like so:
import React, { useState } from "react";
const PlantDefuser = () => {
const [plantingInterval, setPlantingInterval] = useState(null);
const planting = () => {
const plantingIntervalId = setInterval(() => {
console.log("Defuser Planted");
}, 1000);
setPlantingInterval(plantingIntervalId);
};
const notPlanting = () => {
clearInterval(plantingInterval);
setPlantingInterval(null);
};
return (
<button onMouseDown={planting} onMouseUp={notPlanting}>
Press and Hold
</button>
);
};
export default PlantDefuser;
You might also want to make sure the interval is being cleared when the component unmounts.
You can use useEffect hook with cleanup function to manage the clearInterval method .
like this :
function PlantDefuser() {
const [run, setRun] = useState(false);
useEffect(() => {
if (run) {
const countTimer = setInterval(() => {
console.log("Defuser Planted");
}, 1000);
return () => {
console.log(countTimer);
clearInterval(countTimer);
};
}
}, [run]);
return (
<button onMouseDown={() => setRun(!run)} onMouseUp={() => setRun(!run)}>
Press and Hold
</button>
);
}
export default PlantDefuser;
I have two functions in React Native Component, in that one should refresh every 10s and another one should refresh every 1s. I have implemented setInterval() function for refreshing on componentDidMount() and clearInterval() on componentWillUnmount(), but am facing trouble it takes only one function which has the lowest duration. But am achieving result if set duration of both function same duration.
Here is the example
...
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
btLevel: 0,
btState: null,
};
}
componentDidMount() {
this.getBtLevels();
this.getBtState();
this.batLS2();
this.batLS10();
}
componentWillUnmount() {
clearInterval(() => { this.batLSS(); this.batLS10(); });
}
getBtLevels = () => {
fetch(apiUrl).then((res) =>
this.setState({btLevel: res.level}),
);
};
getBtLevelArcs = () => {
fetch(apiUrl).then((res) =>
this.setState({btLevelArc: res.level}),
);
};
getBtState = () => {
fetch(apiUrl).then((res) =>
this.setState({BtState: res.state}),
);
};
batLS10 = () => {
setInterval(() => {
this.getBtLevelArcs();
}, 10000);
};
batLS2 = () => {
setInterval(() => {
this.getBtLevels();
this.getBtState();
}, 1000);
};
...
On the above Code this.getBtLevels(); this.getBtState(); fetch value every 1 seconds and this.getBtLevelArcs(); fetch value every 10 secounds. In this this.getBtLevels(); this.getBtLevelArcs(); functions get same value. But one should refresh every 1 second and another one every 10 seconds. Here am getting is 1s setInterval function this.batLS2() is refresh whole component.
How can I achieve this one should refresh value 1s and another 10s.
here is the Original Version code. Expo
Issue
clearInterval works by being passed the reference returned from setInterval, i.e. this.timerId = setInterval(... and clearInterval(this.timerId).
What I suspect is occurring is you edited you code and ran, which set an interval callback (but didn't clear it), and then edited and re-ran your code, which sets another interval callback (again, not cleared), etc... You basically aren't cleaning up the interval callbacks when the component unmounts (like a page refresh).
Solution
Add a timer variable for each interval timer
constructor(props) {
super(props);
...
this.timer1 = null;
this.timer2 = null;
}
Clear each interval on dismount
componentWillUnmount() {
clearInterval(this.timer1)
clearInterval(this.timer2)
}
Save the timer ref
batLS10 = () => {
this.timer2 = setInterval(() => {
this.getBtLevelArcs();
}, 10000);
};
batLS2 = () => {
this.timer1 = setInterval(() => {
this.getBtLevels();
this.getBtState();
}, 1000);
};
What I understood by the example and statement is you want getBtLevels and getBtState to be called after every 1 sec and getBtLevelArcs after every 10 seconds.
But what happens is when getBtState and getBtLevels invoke, their setState updates the whole component, which is not acceptable in your case.
Ideally this should not be a problem, because all the three functions have different states. btLevel, btLevelArc and btState. Updating one state should not impact the other one. But that totally depends upon your UI logic.
If that is still a problem: what you can do. You can split your component into two components. First one will hold the UI related to getBtLevels and getBtState and second component will contain UI related to getBtLevelArcs. This is required because setState will re-render the whole component.
Code will be something like this:
class App extends React.Component {
...
//some common handlers for SubApp1 and SubApp2
...
render() {
return (
<React.Fragment>
<SubApp1 />
<SubApp2 />
</React.Fragment>
)
}
class SubApp1 extends React.Component {
constructor(props) {
super(props);
this.state = {
btLevel: 0,
btState: null,
};
}
componentDidMount() {
this.getBtLevels();
this.getBtState();
this.batLS2();
}
componentWillUnmount() {
clearInterval(() => { this.batLSS(); });
}
getBtLevels = () => {
fetch(apiUrl).then((res) =>
this.setState({ btLevel: res.level }),
);
};
getBtState = () => {
fetch(apiUrl).then((res) =>
this.setState({ BtState: res.state }),
);
};
batLS2 = () => {
setInterval(() => {
this.getBtLevels();
this.getBtState();
}, 1000);
}
...
...
class SubApp2 extends React.Component {
constructor(props) {
super(props);
this.state = {
btLevelArc: 'some default value'
};
}
componentDidMount() {
this.batLS10();
}
componentWillUnmount() {
clearInterval(() => { this.batLS10(); });
}
getBtLevels = () => {
fetch(apiUrl).then((res) =>
this.setState({ btLevel: res.level }),
);
};
getBtState = () => {
fetch(apiUrl).then((res) =>
this.setState({ BtState: res.state }),
);
};
getBtLevelArcs = () => {
fetch(apiUrl).then((res) =>
this.setState({ btLevelArc: res.level }),
);
};
...
...
Let me take you to my problem. I am making a timer functional component, I am passing startValue to component and then that component will start timer(decreasing one in second) using startValue passed through props.
const FunctionalComponent = (props: any) => {
const [timerValue, setTimerValue] = useState(props.initialValue)
console.log('Set State')
useEffect(() => {
console.log('UseEffects called')
setInterval(() => {
setTimerValue(timerValue - 1)
}, 1000)
}, [])
return <View><Text style={styles.textStyle}>{timerValue}</Text></View>
}
My render function in Parent.
render() {
return <View style={styles.mainView}>
<FunctionalComponent initialValue={30} />
</View>
}
Now, Every time react re-render parent component, FunctionalComponent gets called and resets timerValue value. I solved this problem using class component constructor, but I wonder is there any solution to do same in functional components.
class OTPTimer extends Component {
constructor(props: any) {
super(props)
this.state = {
timeLeft: props.fromStart
}
if (props.startTimer) {
this.startTimer()
}
}
componentDidUpdate(prevProps: any) {
if (!prevProps.startTimer && this.props.startTimer) {
this.startTimer()
this.setState({
timeLeft: this.props.fromStart
})
}
}
startTimer = () => {
var interval = setInterval(() => {
this.setState({
timeLeft: this.state.timeLeft - 1
})
if (this.state.timeLeft === 0) {
clearInterval(interval)
}
}, 1000)
}
render() {
return <Text style={globalStyles.testStyleThree}>{`00:${this.state.timeLeft > 9 ? this.state.timeLeft : `0${this.state.timeLeft}`}`}</Text>
}
}
checkout React.memo, witch will prevent child component to re-render if it's props has not changed
const FunctionalComponent = React.memo((props: any) => { .... } )
This is where it makes sense to use React.memo in order to prevent re-rendering child components when their props don't change.
React.memo is a higher order component. It’s similar to
React.PureComponent but for function components instead of classes.
If your function component renders the same result given the same
props, you can wrap it in a call to React.memo for a performance boost
in some cases by memoizing the result. This means that React will skip
rendering the component, and reuse the last rendered result.
const FunctionalComponent = React.memo<{initialValue: number}>({initialValue}) => {
const [timerValue, setTimerValue] = useState(initialValue)
console.log('Set State')
useEffect(() => {
console.log('UseEffects called')
setInterval(() => {
setTimerValue(timerValue - 1)
}, 1000)
}, [])
return <View><Text style={styles.textStyle}>{timerValue}
</Text></View>
};
People suggested useEffect but it will be invoked after render.
Use useMemo instead:
useMemo(() => {
console.log('This is useMemo')
}, []);
I'm using a React Context to store data and to provide functionality to modify these data.
Now, I'm trying to convert a Class Component into a Functional Component using React Hooks.
While everything is working as expected in the Class, I don't get it to work in the Functional Component.
Since my applications code is a bit more complex, I've created this small example (JSFiddle link), which allows to reproduce the problem:
First the Context, which is the same for both, the Class and the Functional Component:
const MyContext = React.createContext();
class MyContextProvider extends React.Component {
constructor (props) {
super(props);
this.increase = this.increase.bind(this);
this.reset = this.reset.bind(this);
this.state = {
current: 0,
increase: this.increase,
reset: this.reset
}
}
render () {
return (
<MyContext.Provider value={this.state}>
{this.props.children}
</MyContext.Provider>
);
}
increase (step) {
this.setState((prevState) => ({
current: prevState.current + step
}));
}
reset () {
this.setState({
current: 0
});
}
}
Now, here is the Class component, which works just fine:
class MyComponent extends React.Component {
constructor (props) {
super(props);
this.increaseByOne = this.increaseByOne.bind(this);
}
componentDidMount () {
setInterval(this.increaseByOne, 1000);
}
render () {
const count = this.context;
return (
<div>{count.current}</div>
);
}
increaseByOne () {
const count = this.context;
if (count.current === 5) {
count.reset();
}
else {
count.increase(1);
}
}
}
MyComponent.contextType = MyContext;
The expected result is, that it counts to 5, in an interval of one second - and then starts again from 0.
And here is the converted Functional Component:
const MyComponent = (props) => {
const count = React.useContext(MyContext);
const increaseByOne = React.useCallback(() => {
console.log(count.current);
if (count.current === 5) {
count.reset();
}
else {
count.increase(1);
}
}, []);
React.useEffect(() => {
setInterval(increaseByOne, 1000);
}, [increaseByOne]);
return (
<div>{count.current}</div>
);
}
Instead of resetting the counter at 5, it resumes counting.
The problem is, that count.current in line if (count.current === 5) { is always 0, since it does not use the latest value.
The only way I get this to work, is to adjust the code on the following way:
const MyComponent = (props) => {
const count = React.useContext(MyContext);
const increaseByOne = React.useCallback(() => {
console.log(count.current);
if (count.current === 5) {
count.reset();
}
else {
count.increase(1);
}
}, [count]);
React.useEffect(() => {
console.log('useEffect');
const interval = setInterval(increaseByOne, 1000);
return () => {
clearInterval(interval);
};
}, [increaseByOne]);
return (
<div>{count.current}</div>
);
}
Now, the increaseByOne callback is recreated on every change of the context, which also means that the effect is called every second.
The result is, that it clears the interval and sets a new one, on every change to the context (You can see that in the browser console).
This may work in this small example, but it changed the original logic, and has a lot more overhead.
My application does not rely on an interval, but it's listening for an event. Removing the event listener and adding it again later, would mean, that I may loose some events, if they are fired between the remove and the binding of the listener, which is done asynchronously by React.
Has someone an idea, how it is expected to React, to solve this problem without to change the general logic?
I've created a fiddle here, to play around with the code above:
https://jsfiddle.net/Jens_Duttke/78y15o9p/
First solution is to put data is changing through time into useRef so it would be accessible by reference not by closure(as well as you access actual this.state in class-based version)
const MyComponent = (props) => {
const countByRef = React.useRef(0);
countByRef.current = React.useContext(MyContext);
React.useEffect(() => {
setInterval(() => {
const count = countByRef.current;
console.log(count.current);
if (count.current === 5) {
count.reset();
} else {
count.increase(1);
}
}, 1000);
}, []);
return (
<div>{countByRef.current.current}</div>
);
}
Another solution is to modify reset and increase to allow functional argument as well as it's possible with setState and useState's updater.
Then it would be
useEffect(() => {
setInterval(() => {
count.increase(current => current === 5? 0: current + 1);
}, 1000);
}, [])
PS also hope you have not missed clean up function in your real code:
useEffect(() => {
const timerId = setInterval(..., 1000);
return () => {clearInterval(timerId);};
}, [])
otherwise you will have memory leakage
If the increaseByOne function doesn't need to know the actual count.current, you can avoid recreating it. In the context create a new function called is that checks if the current is equal a value:
is = n => this.state.current === n;
And use this function in the increaseByOne function:
if (count.is(5)) {
count.reset();
}
Example:
const MyContext = React.createContext();
class MyContextProvider extends React.Component {
render() {
return (
<MyContext.Provider value={this.state}>
{this.props.children}
</MyContext.Provider>
);
}
increase = (step) => {
this.setState((prevState) => ({
current: prevState.current + step
}));
}
reset = () => {
this.setState({
current: 0
});
}
is = n => this.state.current === n;
state = {
current: 0,
increase: this.increase,
reset: this.reset,
is: this.is
};
}
const MyComponent = (props) => {
const { increase, reset, is, current } = React.useContext(MyContext);
const increaseByOne = React.useCallback(() => {
if (is(5)) {
reset();
} else {
increase(1);
}
}, [increase, reset, is]);
React.useEffect(() => {
setInterval(increaseByOne, 1000);
}, [increaseByOne]);
return (
<div>{current}</div>
);
}
const App = () => (
<MyContextProvider>
<MyComponent />
</MyContextProvider>
);
ReactDOM.render( <
App / > ,
document.querySelector("#app")
);
body {
background: #fff;
padding: 20px;
font-family: Helvetica;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="app"></div>
I am writing a website using react . In one component I have a setInterval() function which gets executed, it updates them DOM. Now, when I change onto another site with my router (react-router-dom) the setInterval() function crashed, because it cannot find the DOM elements to update. How do I go on about this? I though I use componentWillUnmount() but the same error occurs.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
stop: false,
}
}
componentWillUnmount() {
if(!this.state.stop) {
this.setState({
stop: true,
})
}
}
_stop = (counter) => {
clearInterval(counter);
}
_someFunc = () => {
...
}
render() {
...
const update = setInterval(function () {
document.getElementById('some-id').innerText = this._someFunc();
}, 1000);
if(this.state.stop) {
this._stop(update)
}
return (
<p id='some-id'></p>
)
}
}
export default Counter;
TypeError: document.getElementById(...) is null.
How do I stop the interval?
Changes:
1- Put the timer outside of the render method, better to use componentDidMount lifecycle method. By this way timer will be registered only once and after each 1ms it will execute the callback method.
2- Store the timer id in a variable (in instance) to stop it before leaving the page.
Like this:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
stop: false,
}
}
componentDidMount() {
this.timerId = setInterval(function () {
document.getElementById('some-id').innerText = this._someFunc();
}, 1000);
}
componentWillUnmount() {
this._stop();
}
_stop = () => {
clearInterval(this.timerId);
}
_someFunc = () => {
...
}
render() {
return (
<p id='some-id'></p>
)
}
}
export default Counter;
If it is not a performance optimization then do it react way
https://stackoverflow.com/a/39426527/6124657