am writing an app in react native and i got a problem with useState and useEffect hooks. I would like to change increment state value by one every 10 seconds.
Here is my code:
const [tripDistance, setTripDistance] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setTripDistance((prevState) => prevState + 1);
console.log(tripDistance);
}, 10000);
return () => {
clearInterval(interval);
};
}, []);
but the output from the console.log is always 0.
What am I doing wrong?
The output is always zero because in your useEffect you are not listening for the changes on tripDistance state. When you call setTripDistance you cannot access the updated value immediately.
You should add another useEffect that listen on tripDistance in order to have the correct console.log.
So you have to do something like:
const [tripDistance, setTripDistance] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setTripDistance((prevState) => prevState + 1);
}, 10000);
return () => {
clearInterval(interval);
};
}, []);
useEffect(() => console.log(tripDistance), [tripDistance]);
try this
const [tripDistance, setTripDistance] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setTripDistance((prevState) => prevState + 1);
}, 10000);
return () => {
clearInterval(interval);
};
}, [tripDistance]);
Related
In the following React Component below, I am trying to add increment count by each second passed so it looks like a stopwatch, but the count is shown as 2, then blinks to 3, and back to 2. Does anyone know how to deal with this bug, and get the count to show up as intended?
import React, { useEffect, useState } from "react";
const IntervalHook = () => {
const [count, setCount] = useState(0);
const tick = () => {
setCount(count + 1);
};
useEffect(() => {
const interval = setInterval(tick, 1000);
return () => {
clearInterval(interval);
};
}, [ count ]);
return <h1> {count} </h1>;
};
export default IntervalHook;
if you want to change some state based on its previous value, use a function:setCount(count => count + 1); and your useEffect becomes independant of [ count ]. Like
useEffect(() => {
const tick = () => {
setCount(count => count + 1);
};
const interval = setInterval(tick, 1000);
return () => {
clearInterval(interval);
};
}, [setCount]);
or you get rid of tick() and write.
useEffect(() => {
const interval = setInterval(setCount, 1000, count => count + 1);
return () => {
clearInterval(interval);
};
}, [setCount]);
But imo it's cleaner to use a reducer:
const [count, increment] = useReducer(count => count + 1, 0);
useEffect(() => {
const interval = setInterval(increment, 1000);
return () => {
clearInterval(interval);
};
}, [increment]);
onStart is button and when i press it , it must run useeffect but it does not run it in first start but run it on reload. state value change on first start on dev tool.
const [started, setStarted] = useState(false);
const onStart = () => {
setStarted(true);
};
useEffect(() => {
if(started){
let timer = setInterval(() => tick(), 1000);
return () => clearInterval(timer);
}
}, []);
You need to add started to the dependency array of the useEffect.
const [started, setStarted] = useState(false);
const onStart = () => {
setStarted(true);
};
useEffect(() => {
if (started) {
let timer = setInterval(() => tick(), 1000);
return () => clearInterval(timer);
}
}, [started]); // Add started here.
const HeroSection = () => {
const [Change,setChange] = useState(false);
useEffect(() => {
setInterval(() => {
setChange(!Change)
}, 5000);
return () => clearInterval();
}, [Change]);
return (
<HeroContainer>
<HeroBg>
<ImageBg src={ Change ? Image1 : Image2 } />
</HeroBg>
</HeroContainer>
)``
}
export default HeroSection
react auto slide images works for the first few times then becomes buggy as in fast paced changes of images regardless of the 5 sec interval
clearInterval(); does nothing. You need to pass the created interval's ID to clearInterval for it to be cleared.
I also don't think the effect hook should have change as a dependency unless you use setTimeout instead.
const [change, setChange] = useState(false);
useEffect(() => {
const timeoutId = setTimeout(() => {
setChange(!change)
}, 5000);
return () => clearTimeout(timeoutId);
}, [change]);
to set a new timeout in the effect callback every time change changes.
Or:
const [change, setChange] = useState(false);
useEffect(() => {
const intervalId = setInterval(() => {
setChange(change => !change)
}, 5000);
return () => clearInterval(intervalId);
}, []);
to set an interval only once, when the component mounts.
I have this code which updates the state count every 1 seconds.
How can I access the value of the state object in setInterval() ?
import React, {useState, useEffect, useCallback} from 'react';
import axios from 'axios';
export default function Timer({objectId}) {
const [object, setObject] = useState({increment: 1});
const [count, setCount] = useState(0);
useEffect(() => {
callAPI(); // update state.object.increment
const timer = setInterval(() => {
setCount(count => count + object.increment); // update state.count with state.object.increment
}, 1000);
return () => clearTimeout(timer); // Help to eliminate the potential of stacking timeouts and causing an error
}, [objectId]); // ensure this calls only once the API
const callAPI = async () => {
return await axios
.get(`/get-object/${objectId}`)
.then(response => {
setObject(response.data);
})
};
return (
<div>{count}</div>
)
}
The only solution I found is this :
// Only this seems to work
const timer = setInterval(() => {
let increment = null;
setObject(object => { increment=object.increment; return object;}); // huge hack to get the value of the 2nd state
setCount(count => count + increment);
}, 1000);
In your interval you have closures on object.increment, you should use useRef instead:
const objectRef = useRef({ increment: 1 });
useEffect(() => {
const callAPI = async () => {
return await axios.get(`/get-object/${objectId}`).then((response) => {
objectRef.current.increment = response.data;
});
};
callAPI();
const timer = setInterval(() => {
setCount((count) => count + objectRef.current);
}, 1000);
return () => {
clearTimeout(timer);
};
}, [objectId]);
This question already has answers here:
State not updating when using React state hook within setInterval
(14 answers)
Closed 4 years ago.
I'm trying to use new React hooks capabilities, but stumbled a bit.
Fiddle
I have a useEffect which calls setInterval, which updates local state. Like this:
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const k = setInterval(() => {
setCounter(counter + 1);
}, 1000);
return () => clearInterval(k);
}, []);
return (
<div>Counter via state: {counter}<br/></div>
);
It doesn't work right, because counter is captured on first call and so counter is stuck at 1 value.
If i use refs, ref is updated, but rerender is not called (will only see 0 value in UI):
const counterRef = React.useRef(0);
React.useEffect(() => {
const k = setInterval(() => {
counterRef.current += 1;
}, 1000);
return () => clearInterval(k);
}, []);
return (
<div>Counter via ref: {counterRef.current}</div>
);
I can make what i want by combining them, but it really doesn't look right:
const [counter, setCounter] = React.useState(0);
const counterRef = React.useRef(0);
React.useEffect(() => {
const k = setInterval(() => {
setCounter(counterRef.current + 1);
counterRef.current += 1;
}, 1000);
return () => clearInterval(k);
}, []);
return (
<div>Counter via both: {counter}</div>
);
Could you please tell who to handle such cases properly with hooks?
useRef is useful only in cases when component update is not desirable. But the problem with accessing current state in asynchronous useEffect can be fixed with same recipe, i.e. using object reference instead of immutable state:
const [state, setState] = React.useState({ counter: 0 });
React.useEffect(() => {
const k = setInterval(() => {
state.counter++;
setCounter(state);
}, 1000);
return () => clearInterval(k);
}, []);
The use of mutable state is discouraged in React community because it has more downsides than immutable state.
As with setState in class components, state updater function can be used to get current state during state update:
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const k = setInterval(() => {
setCounter(conter => counter + 1);
}, 1000);
return () => clearInterval(k);
}, []);
Alternatively, setTimeout can be used to set new interval every second:
React.useEffect(() => {
const k = setTimeout(() => {
setCounter(counter + 1);
}, 1000);
return () => clearInterval(k);
}, [counter]);
I have found myself in this situation a couple times by now, and my prefered solution is to use useReducer instead of useState, like so:
const [counter, dispatch] = React.useReducer((state = 0, action) => {
// better declared outside of the component
if (action.type === 'add') return state + 1
return state
});
React.useEffect(() => {
const k = setInterval(() => {
dispatch({ type: 'add' });
}, 1000);
return () => clearInterval(k);
}, []);
return (
<div>Counter via state: {counter}<br/></div>
);
Although is adds a bit of boilerplate, it really simplifies the "when is my variable taken in account ?".
More here https://reactjs.org/docs/hooks-reference.html#usereducer