How can I use useMemo dependent on the webapi - reactjs

I have a webapi invoked that is working properly:
const [pItem, setPItem] = useState([]);
const [weight, setWeight] = useReducer(weightHandler, 0.0);
useEffect(() => {
setLoading(true);
let mounted = true;
(async function () {
await getPlantInfoById(itemId)
.then(item => {
if (mounted) {
setPItem(item)
setLoading(false);
}
})
})();
return () => { mounted = false; }
}, [itemId])
Here pItem contains data now I have another filled called weight(which can be changed by a user) .
So I need some calculations according to the weight changes:
const PaymentCalculator = function () {
const [item] = [...pItem];
const priceWithDiscount = DiscountCalc(item.price, item.discount);
const divideWeight = weight / item.weight;
const result = (divideWeight * priceWithDiscount) * 1000;
return result;
}
const use = useMemo(() => PaymentCalculator(), [weight])
But it seems PaymentCalculator invoked before useEffect !!
How can I fix this?

If you examine the contents of paymentCalculator you'll see you've more than just weight as a dependency.
const PaymentCalculator = function () {
const [item] = [...pItem];
const priceWithDiscount = DiscountCalc(item.price, item.discount);
const divideWeight = weight / item.weight;
const result = (divideWeight * priceWithDiscount) * 1000;
return result;
}
pItem is also a dependency!
Initially pItem is an empty array, and since all hooks are called on each render cycle, this would mean that item is undefined on the initial render and accessing item.price and item.discount will throw an error for attempting to "access X of undefined".
Add pItem to the dependency array and provide a fallback value.
const paymentCalculator = function() {
const [item = {}] = [...pItem];
const priceWithDiscount = discountCalc(item.price, item.discount);
const divideWeight = weight / item.weight;
const result = (divideWeight * priceWithDiscount) * 1000;
return result;
}
...
const use = useMemo(() => PaymentCalculator(), [pItem, weight]);

Related

How to wait for useQuery data to finish loading to start working with it NextJS

I am working with React Query for the first time in this project and I like it so far. I am still fairly new to it and am running into an issue that I cannot seem to solve. When I call the useQuery hook, it calls the data fine and it even prints out the data after it is done loading. The only problem is that I am printing the data inside the react function outside the render and so it prints whenever. My issue is how to wait for the data to finish loading so that I can format the information and render it. There is more that I need to do so that the information is useful, and so that requires adding more information, such as an API call. Should I put this information inside of the useQuery hook, useEffect, or is there another way to wait for the data to start loading so that I can work with it?
useQuery hook
const {data, status} = useQuery(['firestoreData'],
async () => {
const q = query(collection(db, loc));
const snapshot = await getDocs(q)
let arr = []
snapshot.forEach(doc => {
arr.push(doc.data())
})
// arr.forEach(doc => {
// const tickerSymbol = doc.stockTicker;
// if (averagePriceMap.has(tickerSymbol)) {
// console.log("Has")
// let data = averagePriceMap.get(tickerSymbol)
// const type = doc.type;
// let newShares = parseFloat(data.shares);
// let newPrice = parseFloat(data.price);
// const oldAmount = newShares * newPrice
// if (type === "buy") {
// newShares = newShares + parseFloat(doc.shares);
// newPrice = (oldAmount + parseFloat(doc.price))/newShares;
// } else {
// newShares = newShares - parseFloat(doc.shares);
// newPrice = (oldAmount - parseFloat(doc.price))/newShares;
// }
// const newData = {
// price: newPrice,
// shares: newShares,
// stockTicker: tickerSymbol,
// id: doc.id
// }
// averagePriceMap.set(tickerSymbol, newData)
// } else {
// console.log("Doesnt Have")
// avgMap.set(tickerSymbol, doc)
// // const
// // setAverageMap(new Map(averageMap.set(String.valueOf(tickerSymbol), doc)))
// }
// })
return Promise.all(arr)
}
)
console.log(data)
The commented-out information is me trying to implement what Im doing inside of my useEffect in order to format the data.
UseEffect hook
useEffect(() => {
// console.log(data)
if (!currentUser) {
router.push('/login');
} else {
if(dataFetchedRef.current) return;
dataFetchedRef.current = true;
const setData = async() => {
// let data = documents
// while (status === 'loading') {
// setTimeout(() => {return}, 100)
// }
let avgMap = new Map()
// const loc = getLoc();
// const q = query(collection(db, loc));
// const snapshot = await getDocs(q)
data?.forEach(doc => {
// snapshot.forEach(async doc => {
// const obj = doc.data()
const documentObj = {
price: doc.data().price,
shares: doc.data().shares,
ticker: doc.data().stockTicker,
type: doc.data().type,
documentID: doc.id,
}
console.log(documentObj)
data.push(documentObj)
let tic = doc.stockTicker;
console.log(tic)
if (avgMap.has(tic)) {
console.log("Has")
let data = avgMap.get(tic)
const type = data.type;
let newShares = parseFloat(data.shares);
let newPrice = parseFloat(data.price);
const oldAmount = newShares * newPrice
if (type === "buy") {
newShares = newShares + parseFloat(doc.shares);
newPrice = (oldAmount + parseFloat(doc.price))/newShares;
} else {
newShares = newShares - parseFloat(doc.shares);
newPrice = (oldAmount - parseFloat(doc.price))/newShares;
}
const newData = {
price: newPrice,
shares: newShares,
stockTicker: tic,
id: doc.documentID
}
avgMap.set(tic, newData)
setAverageMap(new Map(averageMap.set(String.valueOf(tic), newData)))
} else {
console.log("Doesnt Have")
avgMap.set(tic, doc.data())
setAverageMap(new Map(averageMap.set(String.valueOf(tic), doc.data())))
}
})
console.log(avgMap)
const retList = listDocuments
const refPrice = averagePrice
const refEquitiesList = equities
avgMap.forEach(async (value, key) => {
const newPrice = await getStockPrice(key).then(result => result)
const currentPrice = parseFloat(newPrice.toFixed(2))
const pl = (parseFloat(value.shares)*(currentPrice - parseFloat(value.price))).toFixed(2)
const fixedPrice = (value.price).toFixed(2)
const totalEq = (parseFloat(value.shares) * currentPrice).toFixed(2)
let insertAvg = {
ticker: key,
shares: value.shares,
averagePrice: fixedPrice,
currentPrice,
profitLoss: pl,
dividendYield: "Coming Soon"
}
setTicker([...tickers, key]);
setReturns([...returns, pl])
retList.push(insertAvg)
refPrice.key = insertAvg
refEquitiesList.push({
name: key,
value: totalEq
})
// setListDocuments([...listDocuments, insertAvg])
// console.log(retList)
// console.log(listDocuments)
})
let arr = (listDocuments)
console.log(tickers);
console.log(returns)
console.log(arr)
console.log(averagePrice)
listDocuments.map(function(doc) {
console.log(doc)
})
console.log(equities)
// setAverageMap(avgMap)
}
console.log(averageMap)
setData()
console.log(documents)
console.log(listDocuments)
setLoading(false)
}
}, [documents, listDocuments])
When the useEffect first runs, it is returning undefined because data still has not loaded. That is fine, but how do I wait for data to load before running my formatting on it?
I recommend you writing the fetching function for useQuery in a separate place for improved readability.
For instance,
const fetchDocs = (query) => {
return getDocs(query);
}
If you want to refetch when db changes, then add the one inside the query key array. I personally don't recommend processing response data from react-query with useEffect because you can do it inside onSuccess function in react-query.
const {data, isLoading, status} = useQuery(['firestoreData', db], fetchDocs(query(collection(db, loc))), {
onSuccess: (data) => {
// do something here, not in useEffect
}
});
...
if (isLoading) return <div>loading...</div>;
return <div>{/* your code */}</div>
// isFetching can be used for loading bar when refetching data
https://tanstack.com/query/v4/docs/react/guides/queries
Also the useEffect's dependency array, [documents, listDocument] probably makes your code rerun when it is unnecessary.

background change every second

I need to make background change every second. if i use setinterval. the background changes too fast.
here's my code:
const { url, id, isButtonPrev, isButtonNext } = useOwnSelector(state => state.sliderReducer);
const img = useRef<HTMLImageElement>(null);
const dispatch = useOwnDispatch();
Here's function which chang background
const setBackGround = (index: number | null = null) => {
console.log(index)
if(img.current) {
img.current.src = `${url}${id < 10 ? `0${id}` : `${id}`}.jpg`;
img.current.onload = () => {
document.body.style.backgroundImage = `url(${img.current?.src})`;
if (index) dispatch(setId(index));
dispatch(isButton(''));
}
}
}
then I call this function:
setBackGround();
setInterval(() => {
setBackGround(id + 1);
}, 1000);
but background change very fast
I also tried to use the useEffect hook. But it didn’t help either
useEffect( () => {
const intervalID = setInterval(() => {
setBackGround(id + 1);
}, 1000);
return clearInterval(intervalID);
}, []);
useRef returns an object like {current: "value"}.Therefore, you need to use it as follows.
const imgRef = useRef<HTMLImageElement>(null);
if(imgRef.current){
imgRef.current.src = url;
}

storing a specific field of every products in a collection firebase in react

What I am trying to do is read information from "products" collection and from every product there is I want to obtain "total_points" and "total_voting" and save them into 2 const, where i will divide them and find the average rating where I use it somewhere else. I have linked the firebase data in the link below
Firebase Format Picture
I would do something like this:
const Component = () => {
const [averagePoints, setAveragePoints] = useState(0);
const [averageVoting, setAverageVoting] = useState(0);
useEffect(() => {
const getAveragesFromProducts = async () => {
try {
const totalVoting = 0;
const totalPoints = 0;
const querySnapshot = await
getDocs(collection(db,"products"));
querySnapshot.forEach((doc) => {
const docData = doc.data();
totalPoints += docData.total_points;
totalVoting += docData.total_voting;
});
totalPoints /= querySnapshot.size;
totalVoting /= querySnapshot.size;
setAveragePoints(totalPoints);
setAverageVoting(totalPoints);
} catch (error) {}
};
getAveragesFromProducts();
}, []);
return <div>
Average Points :{averagePoints}
Average Voting :{averageVoting}
</div>
};

useState array destructuring react

I always see this type of code:
const [value, setValue] = useState(null);
since value can be changed using setValue, why are we using const for value.
What const means is that the value for that identifier in that scope cannot be reassigned. You can't do
const x = 5;
x = 6;
or
function foo(arg) {
const theArg = arg;
theArg = 5;
}
But there's nothing stopping you from running the function again, resulting in a new binding for that variable:
function foo(arg) {
const theArg = arg;
}
foo(5);
foo(6);
The above is what React is doing - it runs the whole function component again, which results in useState returning the updated state. No const identifier ever gets reassigned.
Another example:
let i = 0;
const useState = () => [i, newI => { i = newI; TheComponent(); }];
const TheComponent = () => {
const [i, setI] = useState();
console.log('component running with i of', i);
setTimeout(() => {
setI(i + 1);
}, 1000);
};
TheComponent();

How to correctly initialize a state array React JS

I need to initialize a state array with six elements, points, and then update the proper element in the points array when setVote function is called:
const [selected, setSelected] = useState(0)
const setNextItem = (value) => setSelected(value)
const initialValue = new Array(6).fill(0);
const [points, setPoint] = useState(initialValue)
const setVote = (value) => setPoint(value)
// randomly choose the next item
const setNextitem(Math.floor((Math.random() * 5) + 0))
const setVote => {
const copy = [...points]
copy[selected] += 1
return copy;
}
However, when I view points and copy arrays in the console window, points never gets updated with copy. It stay as Array(6) [ 0, 0, 0, 0, 0, 0 ]. Is it always initializing or is the copy array not retuned. What is the correct way to do this?
Here is my final code:
```
const App = () => {
const [selected, setSelected] = useState(0)
const [points, setPoints] = useState(new Array(6).fill(0))
const setNextItem = (value) => setSelected(value)
const setVoteAt = (value) => setPoints(value)
console.log(selected, points)
const next_button = {
name : 'next item',
onClick: () => setNextItem(Math.floor((Math.random() * 5) + 0))
}
const vote_button = {
name : 'vote',
onClick: () => setVoteAt(UpdatePoints(selected))
}
const UpdatePoints = (index) => {
let copy = [...points]
copy[index]++
return copy
}
```
Too many duplicated function and state
Just a little order the code..
react useState is not rective, then you can do useEffect to console.
And then you will see the update array.
const [points, setPoints] = useState(new Array(6).fill(0));
const UpdatePoints = (selected) => {
let copy = [...points];
copy[selected]++;
setPoints(copy);
}
const random = () => Math.floor((Math.random() * 5) + 0)
useEffect(() => console.log(points))
return (
<div onClick={() => UpdatePoints(random())}> ClickMe </div>
);
It looks like it should work for the most part. I'd say these two functions are conflicting, and causing an issue. Are there any errors returned in the console?
const setVote = (value) => setPoint(value)
const setVote => {
const copy = [...points]
copy[selected] += 1
return copy;
}
This last one you may want to rename. Something like setVoteAt which take an index parameter for which vote to set.
const setVoteAt = (index) => {
const copy = [...points]
copy[index] += 1
return copy;
}
setVoteAt(2) // [0, 0, 1, 0, 0, 0]
How to correctly initialize a state array React JS
Use useEffect to initialize it properly. This is similar to componentDidMount.
const [points, setPoints] = useState([])
useEffect(() =>
{
const initialValue = new Array(6).fill(0);
setPoints(initialValue);
}
, []) // initialize only once (on mount) and not on every render
Is it always initializing or is the copy array not retuned. What is the correct way to do this?
setVote() is not doing things correctly. You can update points every time selected is changed by:
useEffect(() =>
{
setPoints(points => {
points[selected] += 1
return points
})
}
, [selected] // only run this effect every time selected changed
)
setNextItem(2) // this will update selected then run the effect on top
// and also increment points[selected]

Resources