React useState Not Storing New Values After UseEffect Intervals - javascript

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]);

Related

Why react returns same value in every increment?

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]);

Timer/Counter for react component - value remains 0 after increasing it with setInterval()

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);
};
}, []);

Why my React component is still updating state even if I do cleanup in useEffect()?

I've got a little problem there and I dont know why my solution is not working properly.
Here is the code:
const [progress, setProgress] = useState(0);
useEffect(() => {
let isMounted = true;
if(isMounted === true) {
progress < 100 && setTimeout(() => setProgress(progress + 1), 20);
}
return () => isMounted = false;
}, [progress]);
Im doing there setTimeout async operation. Every 20ms i want to set state of progress by 1. Thats all. Also I added isMounted variable which contains state of component.
The problem is that when lets say, I mount this component and I unmount this
immediately after 1s maybe two then i dont get this error.
If I wait longer and unmount the component (before setTimeout has time to change the progress state to 100, which is the limit) then this error appears.
Why this error is appearing in such weird way?
Why does this error even appear when the component has clearly communicated when it is mounted and when not?
You need to either clear the timeout in the cleanup or use your isMounted variable within the timeout itself.
Clearing the timeout:
useEffect(() => {
let timeout;
if (progress < 100) {
timeout = setTimeout(() => {
setProgress(progress + 1)
}, 20);
}
return () => { clearTimeout(timeout) };
}, [progress]);
Using the isMounted variable:
useEffect(() => {
let isMounted = true;
if (progress < 100) {
setTimeout(() => {
if (isMounted) setProgress(progress + 1);
}, 20)
}
return () => { isMounted = false };
}, [progress]);

on toggling, Search Box value is not getting updated [React + Redux]

Currently I am working on a project where i am using react and redux to build it.
Scenario
There is a toggle button which will toggle between Group Queue and My Queue. Both these will hit a api and load the data(list of tickets) .[i.e. 2 separate entities]
Now there is a search box while will give the result based on the value i enter. I have added the 'Delay functionality' i.e. after 2 seconds onChange event will fire and it will dispatch an action to all reducers.
Problem
Let's say, i search for a value(68) under group Queue, it works fine. Now if i toggle to my queue, SearchBox value should be null.but it shows as 68
As i told, both queue are separate bodies, if i toggle, searchBox's value should disappear, but its not.
Reason
i am storing a state of search box value and timer to set the value on typing after 2 seconds and on every character I type, setTimeOut and clearTimeOut will be invoked to accommodate the delay functionality.
Thus i have a state inside my React SearchBox Component and once i type and stay put for 2 seconds, then it will dispatch the action via useEffect/ComponentDidUpdate.
i decided to have a state inside the component as i did not want to fire an action on every character user types. Only the intended search Value Redux should store.
Things i tried
i tried with getDerivedStateFromProps, but as i need to use SetState and its asynchronous, its not reflecting the current value.
i have added 2 functionality (onChange and OnEnter). With OnEnter, i think it will work as i can pass the redux store's value and action to the input's value and OnEnter attribute. [i have not tried]
But i want to have the delay functionality. Can anyone suggest an alternative ?
Codes
1. SearchComponent
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { filterAction } from "../../actions";
import "./search.css";
const WAIT_INTERVAL = 2000;
const ENTER_KEY = 13;
const Search = (props) => {
console.log("search", props.value);
let [value, setValue] = useState(props.value);
let [timer, setTimer] = useState(0);
useEffect(() => {
setTimer(
setTimeout(() => {
if (timer) props.filterAction("SEARCH_FILTER", value);
}, WAIT_INTERVAL)
);
return () => clearTimeout(timer);
}, [value]);
const handleKeyDown = (event) => {
if (event.keyCode === ENTER_KEY) {
clearTimeout(timer);
props.searchAction("SEARCH_FILTER", value);
}
};
// GETDERIVEDSTATEFROMPROPS
// if (props.value !== value) {
// setValue("");
// // console.log("check2", props.value, value);
// }
return (
<input
className="search-box"
type="search"
placeholder="Search any Request Id/ Email Id..."
aria-label="Search"
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={handleKeyDown}
></input>
);
};
const mapStateToProps = (store) => ({
value: store.filterValues.searchFilter,
});
export default connect(mapStateToProps, { filterAction })(Search);
Redux Redcuer (FilterReducer) -- working fine i.e. on toggling, searchValue is getting emptied('').
export const filterReducer = (filterValues = {}, action) => {
switch (action.type) {
case "TOGGLE_QUEUE":
return {
order: [],
tileFilter: action.payload.tileType,
searchFilter: "",
buttonFilter: {},
timelineFilter: "all",
};
case "SEARCH_FILTER":
let arr = [...filterValues.order];
arr.push("SEARCH_FILTER");
let ob = {
...filterValues,
searchFilter: action.payload.value,
order: arr,
};
return ob;
}
return filterValues;
};
Please let me know if you have any solutions.
EDIT 1
i tried with props.value on UseEffect, now the search itself is not working..
useEffect(() => {
// setValue(props.value); // not working
setTimer(
setTimeout(() => {
if (timer) props.filterAction("SEARCH_FILTER", value);
}, WAIT_INTERVAL)
);
setValue(props.value); // not working
return () => clearTimeout(timer);
}, [props.value]);
Edit2
i added a separate useffect as suggested by Sabbit, but if i type 68, the search functionality is getting invoked on 6 and typed value (68) alternatively.
useEffect(() => {
setTimer(
setTimeout(() => {
if (timer && value.length > 0)
props.filterAction("SEARCH_FILTER", value);
}, WAIT_INTERVAL)
);
return () => clearTimeout(timer);
}, [value]);
useEffect(() => {
if (value !== props.value) setValue(props.value);
}, [props.value]);
Did you tried using another useEffect for props.value?
useEffect(() => {
setTimer(
setTimeout(() => {
if (timer && value.length > 0) props.filterAction("SEARCH_FILTER", value);
}, WAIT_INTERVAL)
);
return () => clearTimeout(timer);
}, [value]);
useEffect(() => {
setValue(props.value);
}, [props.value]);
That will update the value in the state each time the props.value is changed. I believe the searchValue from reducer is passed via props to the Search component.
Update: I have updated the condition to execute props.filterAction("SEARCH_FILTER", value) from if(timer) to if(timer && value.length > 0). We need to do that because everytime the props changes we are setting the value in state and we have used an useEffect depending on the change of value in state. That is why it is searching with empty string. We need to make sure that the searching doesn't happen unless we have something in the input field.

React useEffect doesn't block the re-render

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.

Categories