REACT JS reset integer counter - javascript

I have this function that gets data from a service using a fetch api call and waits for the response using async and await. If the response isn't null, it loads a react component and passes the fetched data to the component, it uses react state to manage data content.
Because of the wait, i had to introduce an integer counter to help me manage the react page. So the integer counter is initialized to 0 and only increments if the response from fetch call isn't null. So i keep showing a progress bar as long as the counter is 0. Once the data state changes, the integer counter is incremented and the page loads the a new react component with the fetched data.
function CheckDeliveries(){
const [deliveries, setDeliveries] = useState(null);
const urlAPpend = "delivery/findByCustomerId/";
let userid = JSON.parse(localStorage.getItem("User"))["userId"];
const httpMethod = 'GET';
let token = localStorage.getItem("Token");
console.error('RELOAD >>>>>>', reload);
if(reload < 1){
makeApiAuthCallsWithVariable(userid,urlAPpend,httpMethod,token).then(
data => {
if (data !== null) {
//console.log("Api response: Data "+JSON.stringify(data));
setDeliveries(data);
reload++
}else{
console.error('Response not ok', data);
}
}
)
}
if(reload >= 1 && deliveries !== null){
reload = 0;
console.error('RELOAD AllDeliveryDiv >>>>>>', reload);
return (<AllDeliveryDiv obj = {deliveries} />);
}else if(reload >= 1 && deliveries === null){
reload = 0;
console.error('RELOAD MakeDeliveryDiv >>>>>>', reload);
return (<MakeDeliveryDiv />);
}else if(reload === 0){
return ( <ProgressBar striped variant="primary" now={value}/>);
}
}
My Question
I have tried using a boolean state instead of integer counter but the page gets into an infinite loop whenever i update the boolean state. Is there a way i can implement this boolean state in without experiencing the infinite loop ?
After i fetch the data, and reset the counter to 0. I log the value before reset and after reset and i get 1 and 0 respectively. But when i call this function again without reloading the entire page, counter value remains 1 even after i had reset it earlier. How do i resolve this?
Any better way to implement this, please share.

It's hard to really tell what you're going for with reload, so that's why I left the whole MakeDeliveryDiv stuff out, but this would load data from your endpoint when the component is first mounted using the useEffect side effect hook:
function CheckDeliveries() {
const [deliveries, setDeliveries] = useState(null);
const [loaded, setLoaded] = useState(false);
React.useEffect(() => {
const userid = JSON.parse(localStorage.getItem("User"))["userId"];
const token = localStorage.getItem("Token");
makeApiAuthCallsWithVariable(
userid,
"delivery/findByCustomerId/",
"GET",
token,
).then((data) => {
setDeliveries(data);
setLoaded(true);
});
}, []);
if (!loaded) {
return <ProgressBar striped variant="primary" now={value} />;
}
if (deliveries === null) {
return <>Data not OK.</>;
}
return <AllDeliveryDiv obj={deliveries} />;
}

Related

Stop setInterval function set inside useEffect

I have this simple useEffect code. When the user logged in to the application every 2 minutes I will dispatch an action which is an API call, and I need to stop this interval once a user is logged out. Still, the current code even runs after the user is logged out, what shall I do to prevent this interval when the user logs out.
I am using the value from the localStorage to determine whether the user is logged in or not.
const intervalId = useRef(null)
useEffect(() => {
const isLoggedIn = localStorage.getItem("isUserLoggedIn") //(true or false)
intervalId.current = setInterval(() => {
dispatch(refreshUserToken());
if(isLoggedIn === false){
clearInterval(intervalId.current)
}
},1000*60*2)
return () => {
clearInterval(intervalId.current)
}
},[])
Is there any way to resolve my issue?
Any help would be much appreciated!!
You should be adding the line where you get that value from localStorage inside the interval, if you want the updated value. Also, localStorage would gives you a string instead of a boolean, either you parse it, or you change your if statement. Try with this:
const intervalId = useRef(null);
useEffect(() => {
intervalId.current = setInterval(() => {
const isLoggedIn = localStorage.getItem("isUserLoggedIn"); //(true or false)
if (isLoggedIn === "false") {
clearInterval(intervalId.current);
return;
}
dispatch(refreshUserToken());
}, 1000 * 60 * 2);
return () => {
clearInterval(intervalId.current);
};
}, []);
You could use an event instead of a setInterval. As an example, change the code where you are setting the localStorage to this:
localStorage.setItem("isUserLoggedIn", true); // or false depending on the context
window.dispatchEvent(new Event("storage")); // you notice that there is a change
You change your useEffect to this:
useEffect(()=>{
const listenStorageChange = () => {
const isLoggedIn = localStorage.getItem("isUserLoggedIn");
console.log(isLoggedIn);
// do your logic here
};
window.addEventListener("storage", listenStorageChange);
return () => window.removeEventListener("storage", listenStorageChange);
},[])
The keys and the values stored with localStorage are always in the UTF-16 string format. As with objects, integer keys and booleans are automatically converted to strings.
So you have to call like this:
if(isLoggedIn === 'false'){
clearInterval(intervalId.current)
}
Check the documentation.

How to use useMemo instead of a local state using javascript and react?

i want to use useMemo instead of a local state using javascript, react and graphql.
what i am trying to do?
I am displaying a progress bar based on data fetched from progress query. the fetched data from progress query is set in a state.
below is the code,
const ProgressModal = (status) => {
const [progress, setProgress] = React.useState<>(undefined); //progress state
//setting
const { data: progressData, stopPolling: stopPolling } =
useCheckProgressQuery({
variables: {id},
pollInterval: 3000,
})
React.useEffect(() => {
if (status === initial) {
setProgress(undefined);
}
if (status===started) {
setProgress(progressData);
}
if (status === finished && completed >= total || status === failed) {
stopPolling();
setProgress(undefined);
}
}, [progress, progressData, setProgress]);
const completed= progress
? progress.Progress.completed : 0;
const total = progress ? progress.Progress.total : 0;
let value = 0;
if (completed > 0 && total > 0) {
value = (completed / total) * 100;
}
return (
<ProgressBar value = {progress} />
);
}
the above code works but how can i use useMemo for above case instead of a local state. could someone help me with this. i am new to using react hooks. thanks.
useMemo and useState with useEffect do different things, so you can not convert useState/useEffect 100 % equally to useMemo.
A more or less equivalent useMemo approach would be this (but it doesn't work, other refactoring would then also be necessary, see below):
const progress = useMemo(() =>{
if( status === initial ){
return undefined;
}
if( status===started ){
return progressData;
}
if( status === finished && completed >= total || status === failed ){
return undefined);
}
return undefined; // <-- you need to define default/fallback
},
[ progressData, status, completed, total ] // <-- some where missing in your example
);
This is not a working solution, more refactoring is required:
Here e.g. stopPolling() is not called, which needs an extra useEffect now.
progress depends on completed and total, and completed / total both depend on progress (circular dependencies)

Tracking changes in state?

The component code has several parameters, each of which has an initial value received from the server. How can I track that one of them (or several at once) has changed its state from the original one in order to suggest that the user save the changes or reset them?
Something similar can be seen in Discord when changing the profile / server.
The solution I found using useEffect () looks redundant, because there may be many times more options.
const [hiddenData, setHiddenData] = useState(server.hidden_data);
const [hiddenProfile, setHiddenProfile] = useState(server.hidden_profile);
const [isChanged, setIsChanged] = useState(false);
useEffect(() => {
if (hiddenData !== server.hidden_data
|| hiddenProfile !== server.hidden_profile) {
setIsChanged(true);
} else {
setIsChanged(false);
}
}, [hiddenData, server.hidden_data, hiddenProfile, server.hidden_profile]);
return (
<>
{isChanged && <div>You have unsaved changes!</div>}
</>
);
Maybe something like that?
const [draftState, setDraftState] = useState(server)
const [state, setState] = useState(server)
// a more complex object with the list of changed props is fine too
const isChanged = lodash.isEqual(state, draftState)
function changeProp (prop, value) {
setState({
...draftState,
[prop]: value
})
}
function saveState () {
setState(draftState)
// Persist state if needed
}

Using axios doesn't rerender my React Native component

I am using hooks in React Native. This is my code:
useEffect(() => {
if (var1.length > 0 ){
let sym = [];
var1.map((items) => {
let count = 0;
state.map((stateItems) => {
if(items === stateItems.name) {
count = count + 1;
}
})
if (count === 0) {
sym.push(items)
}
});
async function getAllStates (sym) {
let stateValues = [];
await Promise.all(sym.map(obj =>
axios.get(ServerURL + "/note?name=" + obj).then(response => {
stateValues.push(response.data[0]);
})
)).then(() =>{
setNewItem(stateValues);
});
}
getAllStates (sym);
}
}, [var1]);
useEffect(() => {
let stateValues = state;
for( let count = 0 ; count < newItem.length; count++ ){
stateValues.push(newItem[count]);
}
setState(stateValues);
}, [newItem]);
This runs successfully without any errors. However, when the state is displayed as below, I am not seeing the latest value added in the state. It shows all the previous values. When I refresh the whole application, I am able to see my value added.
return (
<View style={styles.container}>
<Text style = {{color:"white"}}>
{
state.map( (item, key) =>{
return(
<Text key = {key} style = {{color:"white"}}> {item.name} </Text>
)
})
}
</Text>
</View>
);
Can someone tell me why this is happening? I want to see the data render immediately after the axios call. I am using React Native.
when i force update using :stackoverflow.com/questions/53215285/... it works fine. However, i am looking for a better fix if anyone can provide?
This should do:
useEffect(() => {
var1.forEach(async (name) => {
if (state.some(item => item.name === name)) return;
const response = await axios.get(ServerURL + "/note?name=" + name);
setState(state => [...state, response.data[0]]);
});
}, [var1]);
I still see two issues in your approach:
this code may start the same ajax request multiple times before the result of the firstz ajax-request is added to state; this also means that the result for the same name may be added multiple times to state.
for each item of var1 times each item of state, this is an O(n*m) problem or in this case basically O(n²) as m is pretty fast catching up to n. You should find a better approach here.
And I'm almost certain that [var1] is wrong here as the dependency for useEffect. But you'd need to show where this value comes from to fix that, too.

ReactJS how to run an API call when a state has a particular value

I'm teaching myself React and one of my exercises is using axios to fetch a list of countries from an API
const fetchCountries = () => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
};
React.useEffect(fetchCountries, []);
Then as a user types into an input the list of countries filters down.
const handleInputChange = event => {
const filter = event.target.value; // current input value
let matchingCountries = query !== ''
? countries.filter(country => country.name.toLowerCase().indexOf(query.toLowerCase()) !== -1)
: countries;
setQuery(filter);
setMatches(matchingCountries)
console.log('matches', matches)
console.log('query', query)
};
My goal is that when a single country is matched, a new API request is triggered (to fetch the weather, but the what isn't my problem, the timing is). When a single country is matched, I will then render some data about the country, then fetch and render the weather details for the single country's capital city.
One of the problems I'm having is that when I set the state, the value always seems to be one step behind. For example, in this Codepen when you enter FRA you should get "France". However, I have to enter "FRAN" to get the match. This doesn't happen when I don't use a state variable for the matches (just let matches). This becomes a problem because I need to run the next API call when the number of matches = 1, but the length of the matches state is always wrong.
So I would like to know 1. how to get the correct state of the matched countries. And 2. when I should run the second API call without getting into an infinite loop.
useEffect solution using separation of concern
1 function should do 1 thing
handleInputChange updates state
useEffect updates state
But they are not coupled.
Later you might have a new function called handleDropdownChange which updates state
It that case you don't need to modify useEffect
At the end of the day, we (developers) don't like to rewrite things
const [countries, setCountries] = React.useState([]);
const [query, setQuery] = React.useState("");
const [matches, setMatches] = React.useState([]);
React.useEffect(() => {
let matchingCountries = query !== ''
? countries.filter(country => country.name.toLowerCase().indexOf(query.toLowerCase()) !== -1)
: countries;
setMatches(matchingCountries)
}, [query]); // called whenever state.query updated
const handleInputChange = event => {
setQuery(event.target.value); // update state
};
const fetchCountries = () => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
};
React.useEffect(fetchCountries, []);
And there is also solution (not recommended) by directly using event.target.value provided by #Joseph D.
The only problem is you are using an old query value in handleInputChange().
Remember setting the state is asynchronous (i.e. doesn't take effect immediately)
Here's an updated version:
const handleInputChange = event => {
const filter = event.target.value; // current input value
let matchingCountries = filter ? <code here>
// ...
setQuery(filter);
};
UPDATE:
To call the weather api if there's a single country match is to have matches as dependency in useEffect().
useEffect(
() => {
async function queryWeatherApi() {
// const data = await fetch(...)
// setData(data)
}
if (matches.length === 1) {
queryWeatherApi();
}
},
[matches]
)
1) The reason for your problem is in this line:
let matchingCountries = filter !== ''
? countries.filter(country => country.name.toLowerCase().indexOf(query.toLowerCase()) !== -1)
: countries;
you use query instead of filter variable, your handler function should look like this:
const handleInputChange = event => {
const filter = event.target.value; // current input value
let matchingCountries = filter !== ''
? countries.filter(country => country.name.toLowerCase().indexOf(filter.toLowerCase()) !== -1)
: countries;
setQuery(filter);
setMatches(matchingCountries)
};
2) Where to run your next API call:
For studying purpose I do not want to recommend you using some application state management lib like redux.Just calling it right after setFilter and setQuery. It will run as expected. Because calling an API is asynchronous too so it will be executed after setQuery and setFilter what does not happen with console.log, a synchronous function.

Categories