React issue with lodash and setInterval - javascript

I have an issue with using Lodash + setInterval.
What I want to do:
Retrieve randomly one element of my object every 3 seconds
this is my object:
const [table, setTable]= useState ([]);
so I start with that:
const result = _.sample(table);
console.log(result);
console give => Object { label: "Figue", labelEn: "fig" }
But if a add :
const result = _.sample(table);
console.log(result.label);
console give => TypeError: result is undefined
Beside that I tried to add setInterval and also try with useEffect but or code crash or console give me two numbers every 3 second => 2,6,2,6 ......

Ciao, supposing that table object is correclty filled, you could use lodash and setInterval to get random object of table using useEffect and useRef hooks like:
const interval = useRef(null);
useEffect(() => {
if (!interval.current) {
interval.current = setInterval(() => {
const result = _.sample(table);
console.log(result.label);
}, 3000);
}
}, [table]);
Then to clean setInterval when component will be unmount you could use another useEffect in this way:
useEffect(() => {
return () => {
clearInterval(interval.current);
interval.current = null;
};
}, []);
EDIT
After your explanation, I found the cause of your problem. Before start setInterval you have to wait that table was filled with values (otherwise you get an error).
To solve that it's just necessary to put another condition on first useEffect (table.length > 0), and load data on second useEffect.
So the first useEffect becomes:
const interval = useRef(null);
useEffect(() => {
if (!interval.current && table.length > 0) {
interval.current = setInterval(() => {
const result = _.sample(table);
console.log(result.label);
}, 3000);
}
}, [table]);
And the second one:
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
return () => {
clearInterval(interval.current);
interval.current = null;
};
}, []);
Here the codesandbox updated.

The problem is that you access table before it is loaded.
You should either provide an initial value to that allows a successful do all your operations:
// use an array that has at least one element and a label as initial table value
const [table, setTable] = useState([{label: "default label"}]);
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
});
const result = _.sample(table);
console.log(result.label);
// ...
Or use an if or something alike to handle multiple scenarios:
// use an empty array as initial table value
const [table, setTable] = useState([]);
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
});
const result = _.sample(table);
// account for the initial empty table value by checking the result
// of _.sample which is `undefined` for empty arrays
if (result) {
console.log(result.label);
} else {
console.log("do something else");
}
// ...
If you fetch your data asynchronously you must think about what you want to use while the data is being fetched. The minimal thing to do is tell React to not render the component while the data is missing (being fetch).
const [table, setTable] = useState([]);
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
});
const result = _.sample(table);
if (!result) return null; // <- don't render if there is no result
console.log(result.label);
// ...

Related

State values passed in as arguments in useEffect not being read in reactjs

I am trying to build a cryptocurrency application. But I am having trouble getting populated state values that are passed into the useEffect as parameters to a debouncing/polling function.
The issue is that the debouncing works well, as in it detects the value and calls the api after the 500ms that I specified in debounce. However, the polling portion seems to not have the state values of of transactionType, fromCurrencyAmount, and pair. It seems like after I debounce the input, after 6 seconds the polling will do its thing but the values passed in the params are undefined. Is there anyway I can solve this?
Here is the method that serves two purposes. It has an api to be polled from every 6 seconds, as well as getting debounced input if the user enters an amount inside the input.
function handleInitPoll(baseAndQuote, side, value) {
getSwapPrice(baseAndQuote, side, value || 0)
.then((res) => {
if (!res.price) {
setIsLoading(true);
} else if (res.error) {
setErrorMessage(res.error);
} else if (res.price) {
setIsLoading(false);
setSwapPriceInfo(res);
}
});
}
And here is the useEffect:
useEffect(() => {
handleInitPoll(pair, transactionType, fromCurrencyAmount);
const timer = setInterval(handleInitPoll, 6000, pair, transactionType, fromCurrencyAmount);
return () => {
clearInterval(timer);
};
}
setSelectedCurrencyState({ ...selectedCurrencyState, selectedFromCurrency: 'USDT', selectedToCurrency: 'XLM' });
}, [pair, transactionType, fromCurrencyAmount]);
And here is the debounce declaration:
const debounceOnChange = useCallback(debounce(handleInitPoll, 500, pair, transactionType, fromCurrencyAmount), []);
And here is where the debouncing is being done, which is inside an onChange handler:
const handleAssetAmount = (e) => {
const { value } = e.target;
const formattedAmount = handleAssetAmountFormat(value);
setFromCurrencyAmount(formattedAmount);
validateInputAmount(formattedAmount);
debounceOnChange(pair, transactionType, formattedAmount);
};
Issue
The issue here is that you've closed over stale values in the interval callback.
Solution
One solution is to cache these state values in a React ref such that the current value can be accessed in the polling function.
Example:
const pairRef = React.useRef(pair);
const transactionTypeRef = React.useRef(transactionType);
const fromCurrencyAmountRef = React.useRef(fromCurrencyAmount);
useEffect(() => {
pairRef.current = pair;
}, [pair]);
useEffect(() => {
transactionTypeRef.current = transactionTypeRef;
}, [transactionType]);
useEffect(() => {
fromCurrencyAmountRef.current = fromCurrencyAmount;
}, [fromCurrencyAmount]);
useEffect(() => {
handleInitPoll(pair, transactionType, fromCurrencyAmount);
const timer = setInterval(() => {
handleInitPoll(
pairRef.current,
transactionTypeRef.current,
fromCurrencyAmountRef.current
);
}, 6000);
return () => {
clearInterval(timer);
};
}, [pair, transactionType, fromCurrencyAmount]);
Fundamentally, your code seems to be correct with a few issues:
There is a race condition.
If getSwapPrice is running and the component is updated, it can still
affect the state when setSwapPriceInfo or setLoading are called when
the promise is resolved.
This is particularly bad, because network requests can "overtake" each
other. Thus it can happen that the return value of getSwapPrice
updates the component with the result of an old network request.
This is discussed in this article.
There is this odd call to setSelectedCurrencyState in the useEffect
block. It's not clear what this is supposed to do, but it clearly doesn't
belong there.
However, the underlying application should work fine, I reproduced it with a simpler application here:
import { useEffect, useState } from "react";
function fetchExchangeRateAsync(multiplier) {
return new Promise(resolve => {
setTimeout(() => {
resolve(Math.random() * multiplier);
}, 100);
});
}
function CurrencyExchangeRate() {
const [exchangeRate, setExchangeRate] = useState(null);
const [inputValueString, setInputValueString] = useState("");
const inputValue = Number(inputValueString);
const [multiplier, setMultiplier] = useState(1.0);
let outputValue = null;
if (!isNaN(inputValue) && exchangeRate !== null) {
outputValue = inputValue * exchangeRate;
}
useEffect(() => {
// To avoid race conditions, we must not update the state from an asynchronous operation if
// the component was re-rendered since then.
//
// https://overreacted.io/a-complete-guide-to-useeffect/
let didCancel = false;
// Do not delay the first request.
fetchExchangeRateAsync(multiplier)
.then(newExchangeRate => {
if (!didCancel) {
setExchangeRate(newExchangeRate);
}
});
// Poll exchange rate.
let intervalHandle = setInterval(() => {
fetchExchangeRateAsync(multiplier)
.then(newExchangeRate => {
if (!didCancel) {
setExchangeRate(newExchangeRate);
}
});
}, 500);
return () => {
didCancel = true;
clearInterval(intervalHandle);
};
}, [multiplier]);
return (
<div>
<input value={inputValueString} onChange={event => setInputValueString(event.target.value)} /><br />
<p>With current exchange rate: {outputValue !== null ? outputValue : "(loading)"}</p>
<button onClick={() => setMultiplier(100.0)}>Set Multiplier</button>
</div>
);
}
function App() {
return (
<CurrencyExchangeRate />
);
}
export default App;
This is quite a bit different from what you are doing but it does demonstrate that your code should generally work:
It is possible to trigger the fetch logic by changing the input field (here without debouncing) or in a given interval.
The fetch logic runs immediately when the component is rendered for the first time.
The "Set Multiplier" button can affect the value of multiplier and this information arrives in the setInterval call correctly.
This works because [multiplier] dictates that the effect should be re-run if that variable changes. When this happens, the old interval is first cleared with clearInterval and then re-started with setInterval.
In your case that would be pair, transactionType and fromCurrencyAmount instead of multiplier.
In other words, your issue seems to be outside the code that you provided in the question.

React State not updating immediatly

I am trying to retrieve data from api and show it in my array but when i setting it up in my useState, it is not updating else it is updating after the 1st render means on the 2nd render and on the first render it is returning the previous state.
Here is my code:
const getMembers = async (id) => {
const groupMembers = await getGroupMembers(id);
setLoading(true);
setMembersData(groupMembers);
if(membersData){
const result = usersList.filter(e => !membersData.some(a => a.userId === e.id));
setRemainingUsers(result);
console.log(result)
} else {
setUsersList(usersList)
}
setLoading(false);
};
let options = remainingUsers.map(function(user){
return { value: user.id, label: user.username}
})
What i want is to get remaining users value on the first time function is called and it is updating after the first render. I tried using useEffect but it didn't help. I also search on the google regarding this but almost all solutions are to use useEffect. Here is how i use the useEffect hook.
useEffect(() => {console.log(groupId, group, remainingUsers)}, [ groupId, group, remainingUsers ]);
If(memebersData) {}
if(groupmembers){
const result = usersList.filter(e => !groupmembers.some(a => a.userId === e.id));
}```

Automatically change which element of array to display in React

Using React.
If I had an array like:
[ data:
title: foo1
body: bar1
title: foo2
body: bar2
title: foo3
body: bar3
]
And I wanted to display a single body and title at one time on my page and loop through these elements (have a paragraph that changes its contents routinely).
So far I specify which data I want using this:
let specificBody = this.state.data[x].body; //loop through different elements of array on timer
let specificTitle = this.state.data[x].title;
let specificPosted_By = this.state.data[x].posted_by;
Is there any way to increment the value of x so that I can display a different body and title? Or is there another way of achieving this aim?
All help greatly appreciated.
Some pseudo code, untested:
const [index, setIndex] = useState(0)
useEffect(() => {
const interval = setInterval(() => { /*todo reset at max value*/ setIndex(index+1) }, 10000)
return () => clearInterval(interval)
},[])
and then use index here:
this.state.data[index].body;
This is an example of how you can use setInterval to update the active index periodically.
Your render will then display the text of the active object in the array.
const data = [{ title, body },{ title, body }, { title, body }]
const Component = () => {
const [active, setActive] = useState(0);
useEffect(() => {
//This is how you use setInterval to change the active index every 5 seconds
setInterval(() => {
setActive( previous => {
if (previous === data.length) {
return 0;
} else {
return previous+1;
}
})
}, 5000) //Change every 10 seconds
},[])
return <div>{data[0].title}</div>
}
It looks like you're trying to accomplish two things:
Increment the specific array position
Do this automatically using a timer.
The snippet of code doesn't show what loop logic you're using, but assuming you're going to implement a setInterval inside a useEffect hook, you want to create an independent state variable for 'x' (which marks the array index on your data object).
You can then set a conditional within the useEffect hook, like this:
useEffect(() => {
const intervalId = setInterval(() => {
if(x <= data.length-1) {
setData(data[x])
}
else {
setState({
x = 0;
})
};
x++;
}, 5000);
return () => clearInterval(intervalId);
}, [data, x])
probably a better approach is to divide it into two components, one that hold the data and has the rotation logic and another one that is just presentation
something like this
const Article = (title, body) => {
return <div>...</div>
}
const ArticleCarousel = (data, time) => {
const [index, setIndex] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setIndex(i => (i + 1) % data.lenght)
}, time)
return () => clearInterval(interval)
})
return <Article {...data[index]} />
}

Firebase undefined is not an object when opening an array

So this is really strange
this is my code when it works
useEffect(() => {
// Alleen preview
async function getTracksData() {
let TrackData = await firebase
.firestore()
.collection('TrackScreen')
.doc('LeerjarenData')
.get();
if (!TrackData.exists) {
console.log('Geen data')
} else {
let TrackDatav2 = TrackData.data();
setTrackScreenData(TrackDatav2)
}
} getTracksData()
// SNAPSHOT USER DATA
db.collection("users").doc(currentUserUID)
.onSnapshot((doc) => {
setUserData(doc.data());
});
}, [])
console.log(trackscreenData.data)
this works perfectly but when i change my console to
console.log(trackscreenData.data[0]
it gives me this error
TypeError: undefined is not an object (evaluating 'trackscreenData.data[0]')
then when i change my console again to
console.log(trackscreenData.data)
it works and when i change i back to
console.log(trackscreenData.data[0])
and save the changes it gives me the data i want
what am i doing wrong?
In short, you are trying to use the data from your asynchronous call before its ready.
To handle the case where your data hasn't finished loading, you should use:
console.log(trackscreenData && trackscreenData.data)
Based on the patterns in your code, you have these lines at the top of your component.
const { currentUserID } = useAuth(); // or similar
const [userData, setUserData] = useState();
const [trackscreenData, setTrackScreenData] = useState();
// NOTE: check for typos: "trackscreenData" !== "trackScreenData"
On the first render of your code, trackscreenData will be undefined because you haven't passed an initial state into the useState() method.
const [trackscreenData, setTrackScreenData] = useState(/* initial state */);
// `trackscreenData` initially set to `null`
const [trackscreenData, getTracksData] = useState(null);
// `trackscreenData` initially set to `[]`
const [trackscreenData, getTracksData] = useState([]);
// `trackscreenData` initially set to the result of the function,
// an array containing the elements 0 through 99. The function is
// only executed once to set the first value.
// trackscreenData = [0, 1, 2, 3, ... , 97, 98, 99];
const [trackscreenData, getTracksData] = useState(() => {
Array.from({length: 100})
.map((_, i) => i);
}
When React executes your code, any calls to setters returned from useState calls (e.g. setTrackScreenData) are queued. Only once your code has finished executing, are they evaluated and any new renders triggered.
const [count, setCount] = useState(0); // `count` is initially set to `0`
useEffect(() => {
if (count < 10) {
setCount(count + 1);
}
})
console.log(count);
console.log("rendered");
// Console logs:
// > "0"
// > "rendered"
// > "1"
// > "rendered"
// > ...
// > "9"
// > "rendered"
// > "10"
// > "rendered"
Your fetch of user data should be in its own useEffect call and should return its unsubscribe function:
useEffect(() => {
if (!currentUserUID) {
setUserData(null); // user not signed in
return;
}
// SNAPSHOT USER DATA
return db.collection("users") // <- note the return here
.doc(currentUserUID)
.onSnapshot((doc) => {
setUserData(doc.data());
});
}, [currentUserUID]);
useEffect(() => {
let disposed = false;
// Alleen preview
async function getTracksData() {
let TrackData = await firebase
.firestore()
.collection('TrackScreen')
.doc('LeerjarenData')
.get();
if (disposed) return; // component was removed, do nothing
if (!TrackData.exists) {
console.log('Geen data')
} else {
let TrackDatav2 = TrackData.data();
setTrackScreenData(TrackDatav2)
}
}
getTracksData()
.catch((err) => console.error(err)); // don't forget to handle errors!
return () => disposed = true; // ignore result if component disposed
}, []);
You are specifying the document ID by .doc(...) and hence you are getting a DataSnapshot as the response which is not an array.
If you were using queries like .where(...) then it would have returned a QuerySnapshot.
A QuerySnapshot contains zero or more DocumentSnapshot objects
representing the results of a query. The documents can be accessed as
an array via the docs property or enumerated using the forEach method.
The number of documents can be determined via the empty and size
properties.
So if you are fetching only a single document that way, you don't need to access the doc by [0] as it is not an array. You can simply access the data by dataSnapshot.data()
Although I can't see where is trackscreenData defined?
useEffect(() => {
// Alleen preview
async function getTracksData() {
let TrackData = await firebase
.firestore()
.collection('TrackScreen')
.doc('LeerjarenData')
.get();
if (!TrackData.exists) {
console.log('Geen data')
} else {
let TrackDatav2 = TrackData.data();
setTrackScreenData(TrackDatav2)
console.log(trackScreenData)
}
}
}, [])

How can I refer to the value set by useState immediately afterwards?

Continuous React.useState() setter does not work.
const [balance, setBalance] = React.useState(0);
const [campaigns, setCampaigns] = React.useState([]);
React.useEffect(() => {
console.log('first use Effect');
getRequest(`/api/v1/tablet/campaigns/) // getRequest return Promise Obj
.then(result => {
console.log(result); // [{...},{...},・・・,{...}]
setCampaigns(result);
console.log(campaigns); // [] this is problem part
});
}, []);
How can I refer to the value set by useState immediately afterwards?
You have to use the value you set it with until the next refresh of the component, as state only updates on rerender
You'd need to track it in a separate useEffect, where you receive the updated value:
useEffect(() => {
console.log(campaigns);
}, [campaigns])
Another option is to use the value that was set on the state instead of the actual state value:
React.useEffect(() => {
console.log('first use Effect');
getRequest(`/api/v1/tablet/campaigns/) // getRequest return Promise Obj
.then(result => {
console.log(result); // [{...},{...},・・・,{...}]
setCampaigns(result);
console.log(result); // Access the result here, which is the same as campaigns
});
}, []);
Actually it works you cant see campaigns in console because setCampaigns works async.
const [balance, setBalance] = React.useState(0);
const [campaigns, setCampaigns] = React.useState([]);
React.useEffect(() => {
console.log('first use Effect');
getRequest(`/api/v1/tablet/campaigns/) // getRequest return Promise Obj
.then(result => {
console.log(result); // [{...},{...},・・・,{...}]
setCampaigns(result);//this function works
console.log(campaigns); // but setCampaigns function works async
});
}, []);
If you want to access it immediately you need to write below code that means when change campaigns object automatically trigger useeffect function
React.useEffect(() => {
console.log(campaigns); // you can see your state data
});
}, [campaigns]);

Categories

Resources