I need to create a functional react component that automatically update the inner data every time there is a particular event.
I written the code below, but every time the callback is called the component is re-rendered and the variable myArray is set to empty array instead to be filled with all content coming in the callback.
What am I doing wrong? What's the approach I should use?
// event emulation
function onEventEmulator(callback, interval = 1000) {
setInterval(() => callback('content'), interval);
}
function PollingComponent(props) {
const [myArray, setMyArray] = useState([]);
useEffect(() => {
onEventEmulator(content => {
setMyArray([...myArray, content]);
});
}, []); // with or without passing the empty array as second param, result does not change.
return <ul>
<li>Callback</li>
<li>{myArray.length}</li>
</ul>;
}
Let's try this:
function onEventEmulator(callback, interval = 1000) {
setInterval(() => callback("content"), interval);
}
And then:
function PollingComponent(props) {
const [myArray, setMyArray] = useState([]);
useEffect(() => {
onEventEmulator(content => {
setMyArray(arr => [...arr, ...content]);
});
}, []);
return (
<ul>
<li>Callback</li>
<li>{myArray.length}</li>
</ul>
);
}
One thing that is missing here is a way to clear setInterval, but I guess that is not your concern here.
Related
Let's took a look a this functional component :
note : this is just an exemple
function Foo() {
const [data, setData] = useState([]);
const GetData = () => {
//setting the data...
setData([1, 2, 3]);
}
const ShowData = (data) => {
if(data)
console.log(data);
}
useEffect( () => {
GetData();
ShowData(data)
},[]);
console.log(data) // Here I get the new data normally;
return (
<>
<h2>Hello world ! </h2>
</>
)}
So my question is how can I get the updated value ( the new value of data ) to use it inside ShowData function ?
The best way is to use another useEffect with data as dependency, so everytime data is updated you will run showData() like the following
useEffect( () => {
GetData();
},[]);
useEffect( () => {
ShowData()
},[data]); // runs when data is updated
this way you don't need to pass data argument to showData fn. You will get the state updated.
On first render use
useEffect(() => {
GetData();
},[]);
Then we also add a useEffect with data in the dependency array - which means it will run the code every time data changes.
useEffect(() => {
ShowData(data)
},[data]);
Since data will be empty on first render you could do something like this in getData to just skip the function if data is not available yet.
const ShowData = (data) => {
if(!data) return; //if there is no data yet, dont read code below
console.log(data);
}
Jsfiddle demo
I'v written the code below but always return 0. Why?
const [inc, setInc] = useState(0);
useEffect(() => {
const ti = setInterval(() => {
setInc(prevState => prevState + 1);
console.log(inc);
}, 3000)
return () => {
clearInterval(ti);
}
}, []);
inc is assigned a value at the top of the component. It doesn't change until the component re-renders.
Your useEffect runs the function you pass to it once (because the dependency list is []). That function closes over the value of inc during the first render.
Every time setInterval triggers the function you pass to it, it sees that closed over value of the original inc.
The updated values are only available in:
The re-rendered component
The callback function you pass to setInc
So log it during the re-render of the component instead of inside the interval. You can use a second effect hook to log it only when it changes.
const [inc, setInc] = useState(0);
useEffect(() => {
const ti = setInterval(() => {
setInc(prevState => prevState + 1);
}, 3000)
return () => {
clearInterval(ti);
}
}, []);
useEffect(() => {
console.log(inc);
}, [inc]);
I want to store "jokes" and console log a new value every time the interval runs (a get call is made) after 5 seconds. However, the value doesn't render anything after each interval. I'm unsure if jokes are being called and captured since it prints out as JOKE: []. My end goal is to create a logic using the "joke" state.
If you wish to test it yourself, https://codesandbox.io/s/asynchronous-test-mp2fq?file=/AutoComplete.js
const [joke, setJoke] = React.useState([]);
React.useEffect(() => {
const interval = setInterval(() => {
axios.get("https://api.chucknorris.io/jokes/random").then((res) => {
setJoke(res.data.value);
console.log("JOKE: ", joke); // <- Doesn't print every time it is called.
});
console.log("Every 5 seconds");
}, 5000);
if (joke.length !== 0) {
clearInterval(interval);
console.log("Returns True");
}
return () => clearInterval(interval);
}, []);
Your setJoke call is actually working. The problem is the console.log being called right after setJoke. As mentioned in the answer of this question, setState is async. You can find this issue explained in the React docs:
Calls to setState are asynchronous - don’t rely on this.state to reflect the new value immediately after calling setState. Pass an updater function instead of an object if you need to compute values based on the current state (see below for details).
You can see that joke variable is changing every 5 seconds by adding it to the JSX:
return (
<>
{JSON.stringify(joke)}
<Autocomplete
sx={{ width: 300 }}
open={open}
onOpen={() => {
setOpen(true);
}}
...
To achieve your goal you have to, for example, split your code into two useEffect calls:
const [joke, setJoke] = React.useState([]);
React.useEffect(() => {
const interval = setInterval(() => {
axios.get("https://api.chucknorris.io/jokes/random").then((res) => {
if (res.data.value.length !== 0) {
setJoke(res.data.value);
clearInterval(interval);
console.log("Returns True");
}
});
console.log("Every 5 seconds");
}, 5000);
return () => clearInterval(interval);
}, []);
React.useEffect(() => {
// here you can write any side effect that depends on joke
}, [joke]);
export default function Timer() {
const [timer, setTimer] = useState(0)
const checkTimer = () => {
console.log(timer);
}
useEffect(() => {
const timer = setInterval(() => {
setTimer(prevCount => prevCount + 1);
}, 1000);
startProgram(); //This starts some other functions
return () => {
checkTimer();
clearInterval(timer);
}
}, [])
}
Above is a simplified version of my code and the main issue - I am trying to increase the timer state by setting an interval in useEffect() (only once). However, in checkTimer() the value is always 0, even though the console statement execute every second. I am new to reactjs and would appreciate some help as this is already taking me too many hours to fix.
checkTimer is showing you the initial value of timer state because of stale closure. That means at the time when useEffect was executed, (i.e. once at component mount due to [] as dependency), it registered a cleanup function which created a closure around checkTimer function (and everything, state or props values, it uses). And when this closure was created the value of timer state was 0. And it will always remain that.
There are few options to fix it.
One quick solution would be to use useRef:
const timer = useRef(0);
const checkTimer = () => {
console.log(timer.current);
};
useEffect(() => {
const id = setInterval(() => {
timer.current++;
}, 1000);
return () => {
checkTimer();
clearInterval(id);
};
}, []);
Check this related post to see more code examples to fix this.
Edit:
And, if you want to show the timer at UI as well, we need to use state as we know that "ref" data won't update at UI. So, the option 2 is to use "updater" form of setTimer to read the latest state data in checkTimer function:
const [timer, setTimer] = useState(0);
const checkTimer = () => {
let prevTimer = 0;
setTimer((prev) => {
prevTimer = prev; // HERE
return prev; // Returning same value won't change state data and won't causes a re-render
});
console.log(prevTimer);
};
useEffect(() => {
const id = setInterval(() => {
setTimer((prev) => prev + 1);
}, 1000);
return () => {
checkTimer();
clearInterval(id);
};
}, []);
I am trying to make a pomodoro app and the count down clock will keep toggle the breaking state when the time is up, the breaking state will indicate whether you're currently working (breaking === false) or you're taking a break (breaking === true).
However, the console shows that the setBreaking are keep looping, resulting in error. I've tried to pass in an anonymous function with prevState => !prevState, error still occur. Any advice?
Here are the excerpt:
function Clock() {
const [minute, setMinute] = useState(parseInt(remainingTime/60));
const [second, setSecond] = useState(padZero(remainingTime%60));
const [breaking, setBreaking] = useState(false);
function padZero(num) {
return num.toString().padStart(2,0);
}
function countDown() {
useInterval(() => {
setRemainingTime(remainingTime-1);
}, 1000);
}
if (remainingTime === 0) {
setBreaking(!breaking) // keep looping
setCountDown(false);
setRemainingTime(breakMinute*60)
}
if (countingDown === true) {
countDown();
} else {
console.log('Timer stopped!');
}
return <h1>{minute}:{second}</h1>
};
You are not supposed to put subscriptions, timers... inside the main body. Instead, you need to put your code and start the countdown into a useEffect(..., []) hook.
By not using hooks, your code will be executed everytime you are trying to render the component, and sometimes it's kind of random...
function Clock() {
const [minute, setMinute] = useState(parseInt(remainingTime/60));
const [second, setSecond] = useState(padZero(remainingTime%60));
const [breaking, setBreaking] = useState(false);
React.useEffect(() => {
// Your code here
}, []);
return <h1>{minute}:{second}</h1>
};
Here you go with a solution using useEffect
function Clock() {
const [minute, setMinute] = useState(parseInt(remainingTime/60));
const [second, setSecond] = useState(padZero(remainingTime%60));
const [breaking, setBreaking] = useState(false);
function padZero(num) {
return num.toString().padStart(2,0);
}
function countDown() {
useInterval(() => {
setRemainingTime(remainingTime-1);
}, 1000);
}
React.useEffect(() => {
if (remainingTime === 0) {
setBreaking(!breaking);
setCountDown(false);
setRemainingTime(breakMinute*60)
}
}, [remainingTime]);
React.useEffect(() => {
if (countingDown) {
countDown();
} else {
console.log('Timer stopped!');
}
}, [countingDown]);
return <h1>{minute}:{second}</h1>
};
Using useEffect you can watch the state variable and take action based on that.