Previous useEffect async function call overrides current async function call - javascript

I have a component that displays learning time from an api for the current user. each time the user view changes, contactInfo will have the data for the current user and it will trigger the useEffect.
export const Learning = () => {
// ... var declarations
async function getLearningData(obj) {
var params_oneWk = getParamsForLearningAvg(obj, 1);
var params_fourWk = getParamsForLearningAvg(obj, 4);
var params_12Wk = getParamsForLearningAvg(obj, 12);
setLastwkLoader(false);
setFourwkLoader(false);
setTwelvewkLoader(false);
const cachedData =
JSON.parse(localStorage.getItem("LearningTime")) || null;
const loggedUser =
JSON.parse(localStorage.getItem("loggedInUser")) || null;
if (
cachedData &&
loggedUser &&
loggedUser?.email === contactInfo?.email &&
params_oneWk.from === cachedData.from
) {
setLastwkLoader(true);
setOneWeekAvg(cachedData.week1);
setFourwkLoader(true);
setFourWeekAvg(cachedData.week4);
setTwelvewkLoader(true);
setTwelveWeekAvg(cachedData.week12);
return;
}
if (params_oneWk.user_email) {
let resp1 = await getLearningAvg(params_oneWk, 1, appurl);
setLastwkLoader(true);
setOneWeekAvg(resp1);
let resp2 = await getLearningAvg(params_fourWk, 4, appurl);
setFourwkLoader(true);
setFourWeekAvg(resp2);
let resp3 = await getLearningAvg(params_12Wk, 12, appurl);
setTwelvewkLoader(true);
setTwelveWeekAvg(resp3);
console.log("params_12Wk ", params_12Wk);
console.log("resp3 ", resp3);
if (loggedUser && params_oneWk.user_email == loggedUser.email) {
localStorage.setItem(
"LearningTime",
JSON.stringify({
week1: resp1,
week4: resp2,
week12: resp3,
from: params_oneWk.from,
})
);
}
}
}
useEffect(() => {
console.log("changing contactInfo");
getLearningData(contactInfo);
}, [contactInfo]);
};
The problem is that the api that fetches learning time is kinda slow and hence I'm caching it for the loggedIn user. Since caching data comes faster than that of the api, sometimes the api call gets resolved after the cachedData is shown on the UI and it overrides the currentData and shows wrong data. Why is the previous async function doesn't stop executing as soon as the dependency changes in the useEffect? Where am I going wrong?

Related

I get All NFTs ID when I only should get the one that belong to the current account

I get All NFTs token ID when I only should get the one that belongs to the current account.
I'm Also getting the token ID for the current account but also all the other Tokens ID.
How do i resolve this ?
This is the code
const { enableWeb3, account, isWeb3Enabled, Moralis, deactivateWeb3 } = useMoralis();
useEffect(() => {
if(isWeb3Enabled) return
if(typeof window !== 'undefined'){
if(window.localStorage.getItem('connected')){
enableWeb3();
}
}
}, [isWeb3Enabled])
useEffect(() => {
Moralis.onAccountChanged((account) => {
if(account == null){
window.localStorage.removeItem('connected')
deactivateWeb3();
}
})
}, [])
const Web3 = require("web3");
const web3 = new Web3(new Web3.providers.HttpProvider("https://dxt.dexit.network"));
const contractAddress = "0x075D8f52dC5B1F35005dBa8Db40734CBcaBEbd8F"; // replace with the actual contract address
const abi = require('../abi.json'); // replace with the actual ABI of the contract
const contract = new web3.eth.Contract(abi.abi, contractAddress);
async function getNFTs(address) {
const events = await contract.getPastEvents("Transfer", {
filter: { to: address },
fromBlock: 0,
toBlock: "latest"
});
return events.map(event => event.returnValues.tokenId);
}
const test2 = async () => {
console.log(await getNFTs(account));
}
test2();
This is the result As you can see i get all NFTs but I only want token ID 45
return events.map(event => event.returnValues.tokenId);
you are mapping all the events that have returnValues.tokenId truthy. That means if returnValues.tokenIdhas any value you will map them into the new array. This is the logic of .map in javascript, you are mapping all the elements that satisfy the given condition.
You are most likely emitting an event when you create an NFT.
event NftItemCreated(
// you have returnValues
// I dont know what ReturnValuesStruct is, maybe you have address recorded here
ReturnValuesStruct returnValues,
// you should also add this field
address creator,
);
Now you will be logging the creator address of the NFT. Then you should write this logic
async function getNFTs(address) {
const events = await contract.getPastEvents("Transfer", {
filter: { to: address },
fromBlock: 0,
toBlock: "latest"
});
const addressNfts=events.map((event)=>{
if (event.creator===address){
return event.returnValues.tokenId
}
})
return addressNfts
}

is it possible to call a firebase function onValue() in another onValue() if yes how to do

technologies used: React Native / Expo / Firebase
I explain to you, I recover a list of key with my first onValue, in this one I forEach for each key and in this one I make another onValue in order this time to recover the information of the keys, but I realize that it creates a problem, one event calls another and it creates duplicate calls. Moreover I could not intervene on the second listening to delete it since it is only initialized locally, so how can I call this in the right way? thank you in advance for your answers, I'm new to react native with firebase!
Here is a snippet of the code:
useFocusEffect( // first time the screen is loaded, the gardener list is empty, so we need to get the gardener list from firebase
React.useCallback(() => {
setGardeners([])
const gardenerRef = ref(db, 'users/' + auth.currentUser.uid + '/gardeners');
const gardenerListener = onValue(gardenerRef, (snapshot) => {
const data = snapshot.val();
if (data != null) {
setIsFirst(false)
Object.keys(data).forEach(e => {
const temp = ref(db, 'gardeners/' + e);
onValue(temp, (snapshot) => {
const data = snapshot.val();
if (data != null && data.metadata != undefined) {
setGardeners(gardeners => [...gardeners, { id: e, name: data.metadata.name, isIrrig: data.irrig }]);
}
})
gardeners.length > 0 ? setCurrentGardener(gardeners[0].id) : setCurrentGardener("")
setC(currentGardener != "" ? currentGardener : "")
})
} else {
setIsFirst(true)
}
})
and here is the way i turn off the event during disassembly, for me it looks correct, now if you think i did it wrong, don't hesitate
return () => {
setGardeners([])
off(gardenerRef, gardenerListener)
// console.log("unmounted")
}
}, []))

CoinbaseWalletSDK does not work with server side rendering

I was trying to load CoinbaseWalletSDK in my NextJS application, but it always throw an error of ReferenceError: localStorage is not defined due to it was imported before the window is loaded. I tried dynamic loading but it doesn't work. The following is what I am using at this moment.
export async function getServerSideProps({
params,
}: {
params: { project_id: string };
}) {
const project_id = params.project_id;
let project: any = fakeProjects[0];
if (project_id && typeof project_id === 'string' && !isNaN(parseInt(project_id))) {
const id = project_id;
project = fakeProjects.find(p => p.id === parseInt(id));
// Fetch project detail here
let item = await (
getNFTStatsByProjectId(
parseInt(project_id)
)
);
if (project && item && item['nftTotal'] && item['nftSold']) {
if (item.nftSold > item.nftTotal) {
item.nftSold = item.nftTotal;
}
project.nftTotal = item.nftTotal;
project.nftSold = item.nftSold;
}
}
const { coinbaseEth } = (await import('../../components/services/coinbase'));
return {
props: {
project: project,
coinbaseEth: coinbaseEth
},
};
}
And this is what I have in the coinbase service:
// TypeScript
import CoinbaseWalletSDK from '#coinbase/wallet-sdk'
import Web3 from 'web3'
const APP_NAME = 'Practice App'
const APP_LOGO_URL = process.env.WEBSITE_URL + '/logo.png'
const DEFAULT_ETH_JSONRPC_URL = 'https://mainnet.infura.io/v3/' + process.env.INFURA_PROJECT_ID
const DEFAULT_CHAIN_ID = 1
// Initialize Coinbase Wallet SDK
export const coinbaseWallet = new CoinbaseWalletSDK({
appName: APP_NAME,
appLogoUrl: APP_LOGO_URL,
darkMode: false
})
// Initialize a Web3 Provider object
export const coinbaseEth = coinbaseWallet.makeWeb3Provider(DEFAULT_ETH_JSONRPC_URL, DEFAULT_CHAIN_ID)
// Initialize a Web3 object
export const web3 = new Web3(coinbaseEth as any)
The new CoinbaseWalletSDK is where the error was thrown if that's a concern.
Based on my research, I will need to get it imported after the page is fully loaded (which is the point when "window" become available, as well as "localStorage"), which I have no clue how to achieve. Can anyone help me out on this?
I solved it by loading it later. What I did was to assign this variable with this function.
setTimeout(async () => {
coinbaseEth = (await import('../../components/services/coinbase')).coinbaseEth;
}, 1000)
I choose not to use useEffect because the value will be lost on render, which prevents the function to work properly.

Creating UseEffect react hook which should go through JSON data and switch the state

I'm trying to build filtering for job locations ("remote"/"in-person"/"hybrid") for my personal project and I was trying to troubleshoot it for quite some time (very new to programming). I am sure I made mistakes in my main fetchLocationData function and passing URLSearchParams but I am not gonna be surprised if there are more mistakes.....
const fetchLocationData = async () => {
const data = await getJobs();
return data.json();
};
useEffect(() => {
let condition = fetchLocationData();
switch (jobCondition) {
case 'On-site': condition.filter((con) => con.jobLocation === 'in_person');
break;
case 'Remote': condition.filter((con) => con.jobLocation === 'remote');
break;
case 'Hybrid': condition.filter((con) => con.jobLocation === 'hybrid');
break;
default:
condition = null;
}
if (condition != null) {
const params = new URLSearchParams({
jobs: jobFilter || null,
location: locationFilter || null,
since: datePosted || null,
conditions: `${condition.con}`,
});
history.push({ pathname: '/', search: `${params.toString()}` });
return;
}
const params = new URLSearchParams({
jobs: jobFilter || null,
location: locationFilter || null,
since: null,
conditions: null,
});
history.push({ pathname: '/', search: `${params.toString()}` });
}, [jobCondition]);
Rather than creating switch statement you can somewhat optimize your code this way.
let condition = fetchLocationData();
let obj={'On-site':'in_person','Remote':'remote','Hybrid':'hybrid'}
condition =condition.filter((con) => con.jobLocation === obj[jobCondition]);
There's a few issues with your code. I rewrote it and I think this is what you're looking for.
I did not know what kind of data getJobs() returned but I'm presuming it's an array of objects because you are filtering through it while trying return object data.
First thing first, move stuff out of useEffect. Create it's own function to handle all of functionality. Let's call it jobFilterHandler.
This jobFilterHandler will be wrapped in a useCallback. This is being done because while the function itself will be called in a useEffect, the data is being fetched and processed in this function. So we want this to return a memoized result to prevent unnecessary data fetching and re-renders.
I will add comments in the code to explain what it does.
const jobFilterHandler = useCallback(async (jobCondition) => {
// This function takes your filter_condition as an argument and is
// asynchronous as marked above.
const jobs = await getJobs(); // We retrieve the list of jobs from the API and wait for it.
// Here we define the possible filter options to match with the raw data.
const condition_options = {
"On-site": "in_person",
Remote: "remote",
Hybrid: "hybrid"
};
// From the data we received, we filter the jobs that match our filter.
const filtered_jobs = await jobs.filter(
(job) => job.jobFilter === condition_options[jobCondition]
);
// If there are no filtered jobs, the function ends.
if (!filtered_jobs) {
return;
}
// Else the filtered jobs will be returned.
return filtered_jobs;
}, []);
useEffect(() => {
// << here you need to write your code to fetch your jobCondition value >>
jobFilterHandler(jobCondition).then((jobs) => {
// << do whatever you want with the filtered jobs data here >>
console.log(jobs);
});
}, [jobCondition, jobFilterHandler]);
Uncommented code here.
const jobFilterHandler = useCallback(async (filter_condition) => {
const jobs = await getJobs();
const condition_options = {
"On-site": "in_person",
Remote: "remote",
Hybrid: "hybrid"
};
const filtered_jobs = await jobs.filter(
(job) => job.jobFilter === condition_options[filter_condition]
);
if (!filtered_jobs) {
return;
}
return filtered_jobs;
}, []);
useEffect(() => {
jobFilterHandler(jobCondition).then((jobs) => {
console.log(jobs);
});
}, [jobCondition, jobFilterHandler]);

Node and React are not in sync

I was able to achieve the following -> When a user clicks on a particular date in component A the data gets sent to the Node (Sails API) where all the necessary calculations are done, and before component B is rendered the correct data is ready to be shown.
The problem is when a user returns back from component B to component A and chooses a different date, he/ she gets the exact same result (old value) because even though the new value is sent to the backend API, Node isn't doing the recalculations with the new value.
I'm only able to achieve the correct result after I manually refresh the page, or make changes to the server so it forces the recalculation.
I think I need to mention that I'm passing data using Redux, so maybe the issue occurs on that part.
I would consider some type of auto refresh, animated loading, anything.
Yup, so stuck :/
Is it even possible to make them in total sync?
UPDATE --> Here is the code:
BACKEND
getDetails: (req, res) => {
authentication.authenticate().then((auth) => {
const sheets = google.sheets('v4');
sheets.spreadsheets.values.get({
auth: auth,
spreadsheetId: config.spreadsheetSettings.spreadsheetId, // id of spreadsheet
range: config.spreadsheetSettings.employeeSheetId, // name of employee spreadsheet and range- get all cells
}, (err, response) => {
if (err) {
res.serverError(err);
return;
}
const rows = response.values; // response-all cells
const updatedData = employeeService.mapEmployeeSheetToJson(rows);
// FETCHING THE VALUE FROM REST API
let myArr = [];
(function() {
axios.get(`http://localhost:1337/api/`)
.then(res => {
let kajmak = res.data.slice(-1)[0]
let test = kajmak[Object.keys(kajmak)[0]]
myArr.push(test)
}).catch(err => console.error(err));
})();
// MAPING OVER THE ARRY AND DOING THE LOGIC
setTimeout(() => {
myArr.map(xo => {
const result = [];
updatedData.forEach(emp => {// 2013 2012 2014
if (xo > parseInt(moment(emp.startdate).format('YYYYMM'), 10) &&
(xo < parseInt(moment(emp.enddate).format('YYYYMM'), 10))) {
result.push(emp);
}
});
// IF THEY STARTED WORKING BEFORE THE SELECTED DATE AND STILL WORKING
updatedData.forEach(emp => { // 2013 > 2012 & 2013 -
if (xo > parseInt(moment(emp.startdate).format('YYYYMM'), 10) &&
((parseInt(moment(emp.enddate).format('YYYYMM'), 10) == undefined ))) {
result.push(emp);
}
});
// IF THEY STARTED WORKIG BEFORE THE SELECTED DATE,
// BUT STOPPED WORKING BEFORE THE SELECTED DATE
updatedData.forEach(emp => { // 2013 < 2014 || 2013 > 2017
if (xo < parseInt(moment(emp.startdate).format('YYYYMM'), 10) &&
(xo > parseInt(moment(emp.startdate).format('YYYYMM'), 10))) {
result.pop(emp);
}
});
// Getting the names to use for unique sheet req
let finalResult = [];
result.map(x => {
finalResult.push((x.name + ' ' + x.surname))
})
if (rows.length === 0) {
res.err('No data found.');
} else {
res.ok(finalResult);
}
})
}, 1000);
});
}
FRONTEND
getEmployeeSalaryData = () => {
// GETTING THE CLICKED VALUE FROM THE PREVIOUS COMPONENT
const { year } = this.props.history.location.state.item;
const { month } = this.props.history.location.state.item;
const selectedMonth = moment().month(month).format("MM");
const finalSelect = parseInt(year + selectedMonth, 10);
const { employees } = this.props;
// I'M RECIEVING THIS AS PROPS USING REDUX AND THIS IS THE ACTUAL 'FINAL' DATA USED FOR FURTHER CALCS AND RENDERING
const { details } = this.props;
// HERE I'M SENDING THE 'CLICKED' VALUE FROM THE PREVIOUS COMPONENT TO THE BACKEND API
axios.post(`http://localhost:1337/api/`, { 'test' : finalSelect })
.then(res => {
console.log('Data send')
// console.log(res.data);
}).catch(err => console.error(err));
// Making the req
details.map(x => {
EmployeeApi.getEmployee(x)
.then(y => {
//Making sure everything is in the right order
let test = Object.assign(y.data);
let ii = x;
setTimeout(
this.setState(prevState => ({
...prevState.currentEmployee,
fullNames: [...prevState.currentEmployee.fullNames, ii]
})), 100);
let onlyRelevantDate = [];
test.map(item => {
if (finalSelect == parseInt(item.year + moment().month(item.month).format("MM"), 10)) {
onlyRelevantDate.push(item)
}})
this.setState(prevState => ({
currentEmployee: {
...prevState.currentEmployee,
salaryInfo: [...prevState.currentEmployee.salaryInfo, onlyRelevantDate],
fullNames: [...prevState.currentEmployee.fullNames, ii]
}}))
})
});
}
componentWillReceiveProps(nextProps) {
this.getEmployeeSalaryData(nextProps);
}
componentWillMount() {
this.getEmployeeSalaryData(this.props);
}
In component A you should dispatch an action that is a function taking a dispatch function.
//some click handler for when user makes a selection
// the function should be in action creator file but you get the jist
const handleSomeClick = someValue =>
//when you dispatch an action that is a function in redux with thunk then
// the thunk middleware will not call next (no reducers will be called)
// thunk will pass a parameter to this function that is the dispatch
// function so from your function you can dispatch actual object action(s)
dispatch(
dispatch=>
setTimeout(
dispatch({type:"changedValue",data:someValue}),//dispatching the action
someValue*1000//assuming someValue is a number
)
)
Here is an example that has component A set someValue depending on what button is clicked and will highlight that button it'll also set someValue of B asynchronously. This is done in the function changeLater that dispatches an action that is a function so thunk will execute it with the dispatch.
This function will dispatch an action after a timeout. If you click the numbers 5 and then 1 (quickly) you'll see that the highlighted button of A and value after async of B do not match (highlighted of A is 1 and value after async of B is showing 5).
This is because the order of which the user clicks and starts the async process is not the same as the order the async process resolves. You could solve this by only dispatching an action when it's the last resolved promise.
This example shows how it's done by using a promise created by later and only resolve it if it's the last by using a partially applied version of onlyLastRequestedPromise called lastNumberClicked
you can use RxJS to solve this

Categories

Resources