javascript optimize .map to spread operator - javascript

I am using a recursive function to make async calls if there is an odata nextlink. It works fine as it is by using map to push the items into teamsArray. The problem hover is that I am looping through each item instead of merging the objects together. I tried to use the following but with no avail:
teamsArray = {}
teamsArray = { ...teamsArray, ...latstestResults}
Current code that does work but is not optimized:
export const fetchAllTeams = () => {
return dispatch => {
dispatch(fetchAllTeamsRequest());
};
};
export const fetchAllTeamsRequest = () => {
return dispatch => {
dispatch(getAllTeamStarted());
let teamsArray = [];
getAllTeams("", teamsArray, dispatch);
};
};
const getAllTeams = (url, teamsArray, dispatch) => {
if (url === "") {
url = "https://graph.microsoft.com/v1.0/me/memberOf?$top=10";
}
const getTeams = adalGraphFetch(fetch, url, {})
.then(response => {
if (response.status != 200 && response.status != 204) {
dispatch(fetchAllTeamsFailure("fout"));
return;
}
response.json().then(result => {
if (result["#odata.nextLink"]) {
const teams = objectToArray(result.value);
teams.map(team => {
teamsArray.push(team);
});
getAllTeams(result["#odata.nextLink"], teamsArray, dispatch);
} else {
const latestResult = objectToArray(result.value);
latestResult.map(team => {
teamsArray.push(team);
});
console.log("the teams", teamsArray);
dispatch(fetchAllTeamsSucces(result));
}
});
})
.catch(error => {
dispatch(fetchAllTeamsFailure(error));
});
};

Something like this might work for you.
I refactored the paged fetching into an async function that calls itself if there are more items to fetch, then eventually resolves with the full array of results.
Dry-coded, so there may be bugs and YMMV, but hope it helps.
export const fetchAllTeams = () => {
return dispatch => {
dispatch(fetchAllTeamsRequest());
};
};
export const fetchAllTeamsRequest = () => {
return async dispatch => {
dispatch(getAllTeamStarted());
try {
const teamsArray = await getPaged(
"https://graph.microsoft.com/v1.0/me/memberOf?$top=10",
);
dispatch(fetchAllTeamsSucces(teamsArray));
} catch (err) {
dispatch(fetchAllTeamsFailure(err));
}
};
};
const getPaged = async (url, resultArray = []) => {
const response = await adalGraphFetch(fetch, url, {});
if (response.status != 200 && response.status != 204) {
throw new Error("failed to fetch teams");
}
const result = await response.json();
objectToArray(result.value).forEach(team => resultArray.push(team));
if (result["#odata.nextLink"]) {
// Get more items...
return getPaged(resultArray, result["#odata.nextLink"]);
}
return resultArray; // All done, return the teams array.
};

Related

Jest mocked api call failing to return data

I have the following cusotm React hook:
...
useEffect(() => {
const handleMonitoringData = async (isDefaultProduct?: boolean) => {
const result = await getMonitoringData(intermediaryId);
if (result) {
const sortedResult = result.sort((a, b) =>
a.product?.name > b.product?.name ? 0 : -1
);
setMonitoringData(sortedResult);
if (isDefaultProduct) selectProduct(sortedResult[0]);
}
};
if (isSuperUser) {
setMonitoringData([]);
selectProduct(null);
if (hasRendered) {
handleMonitoringData();
} else {
toggleHasRendered(true);
}
} else {
handleMonitoringData(true);
}
}, [intermediaryId]);
...
and my attempt at testing the initial monitoring data load (precisely the else statement => handleMonitoringData(true)) like so:
jest.mock('#api/Monitoring', () => ({
getMonitoringData: () => [mockedData],
}));
describe('useFundRaising custom hook', () => {
it('should work', async () => {
function TestComponent() {
const { monitoringData } = useFundRaising();
return <div>{console.log('data: ', monitoringData)}</div>;
}
const res = await render(<TestComponent />);
});
});
getMonitoringData:
export const getMonitoringData = async (
intermediaryId?: string
): Promise<MonitoringData[]> => {
const URL = intermediaryId
? `${MONITORING_DATA_URL}/${intermediaryId}`
: MONITORING_DATA_URL;
const result = await Http.get<MonitoringData[]>(URL);
return result;
};
the test is currently failing:
[![enter image description here][2]][2]
Have you tried to mock like this:
const mockGetMonitoringData = jest.fn().mockResolvedValue(mockedData);
jest.mock('#api/Monitoring', () => ({
getMonitoringData: () => mockGetMonitoringData(),
}));
as the getMonitoringData is an async method.

useState([]) dosen't work when I update an array

I have a problem. The variable interests is filled up but interestsInput not. What exactly is the problem here? I want to show the interests of a person in a textfield, but it doesnt show the input in the array.
const[firstLoad, setFirstLoad] = useState(true);
const [interestsInput, setInterests] = useState([]);
const selectedTags = (tags) => {
setTags(tags)
};
useEffect(() => {
if(firstLoad) {
getInterests();
}
}, [interestsInput]);
const getInterests = () => {
axios
.get("http://localhost:4000/interests",
{ }
)
.then((res) => {
if (res.status === 200) {
var interests = [];
for (var i = 0; i < firstInterest.length; i++) {
//console.log(myStringArray[i]);
//console.log(firstInterest[i]['text'])
//console.log(firstInterest[i])
interests[i] = firstInterest[i]['text'];
setInterests([...interestsInput, interests[i]])
}
console.log(interests);
//setInterests([]);
//setInterests([...interests]);
console.log(interestsInput)
}
})
.catch((error) => {
console.log(error);
});
}
Set the new interests outside of the loop, not inside it.
To make things concise, use .map to turn the array of objects into an array of texts.
const getInterests = () => {
axios
.get("http://localhost:4000/interests",
{}
)
.then((res) => {
if (res.status === 200) {
setInterests([...interestsInput, ...firstInterest.map(int => int.text)]);
} else {
throw new Error(res);
}
})
.catch((error) => {
console.log(error);
});
};
Also like user CertainPerformance suggested, you'll need to setState outisde the loop.
// this is redundant, you can achieve this by useEffect with empty array
// const[firstLoad, setFirstLoad] = useState(true);
const [interestsInput, setInterests] = useState([]);
const selectedTags = (tags) => {
setTags(tags)
};
const getInterests = () => {
axios
.get("http://localhost:4000/interests")
.then((res) => {
if (res.status === 200) {
var interests = [];
for (var i = 0; i < firstInterest.length; i++) {
interests[i] = firstInterest[i]['text'];
setInterests([...interestsInput, interests[i]])
}
}
})
.catch((error) => {
console.log(error);
});
}
// Ideally you'd want to put use effects after you have defined your constants & handlers
useEffect(() => {
// no need of firstLoad condition
getInterests();
}, []); // this will only run once after first render

Using Async Function to return more results

I have a dataset with 3000 rows in and in Wix builder. Wix has a limit of 1000 returns however they suggest a workaround here: https://www.wix.com/velo/forum/coding-with-velo/dataset-query-for-3000-items
I am trying to add this to my code however I don't know where it needs to be inserted, can someone please help me? My code is below:
import wixData from 'wix-data';
$w.onReady(function () {
uniqueDropDown1();
});
function uniqueDropDown1 (){
wixData.query("Products")
.limit(1000)
.ascending("Manufacturer")
.find()
.then(results => {
const uniqueTitles = getUniqueTitles(results.items);
$w("#dropdown1").options = buildOptions(uniqueTitles);
});
function getUniqueTitles(items) {
const titlesOnly = items.map(item => item.Manufacturer);
return [...new Set(titlesOnly)];
}
function buildOptions(uniqueList) {
return uniqueList.map(curr => {
return {label:curr, value:curr};
});
}
}
export function dropdown1_change(event) {
uniqueDropDown2();
$w("#dropdown2").enable();
function uniqueDropDown2 (){
wixData.query("Products")
.contains("Manufacturer", $w("#dropdown1").value)
.limit(1000)
.ascending("Model")
.find()
.then(results => {
const uniqueTitles = getUniqueTitles(results.items);
$w("#dropdown2").options = buildOptions(uniqueTitles);
});
function getUniqueTitles(items) {
const titlesOnly = items.map(item => item.Model);
return [...new Set(titlesOnly)];
}
function buildOptions(uniqueList) {
return uniqueList.map(curr => {
return {label:curr, value:curr};
});
}
}
}
export function dropdown2_change(event) {
uniqueDropDown3();
$w("#dropdown3").enable();
function uniqueDropDown3 (){
wixData.query("Products")
.contains("Model", $w("#dropdown2").value)
.limit(1000)
.ascending("Part")
.find()
.then(results => {
const uniqueTitles = getUniqueTitles(results.items);
$w("#dropdown3").options = buildOptions(uniqueTitles);
});
}
function getUniqueTitles(items) {
const titlesOnly = items.map(item => item.Part);
return [...new Set(titlesOnly)];
}
function buildOptions(uniqueList) {
return uniqueList.map(curr => {
return {label:curr, value:curr};
});
}
}
and I think I need to somehow incorporate the following, but I have absolutely no idea how:
async function fetchData() {
const firstPage = await wixData.query('collection')
.limit(1000)
.find();
const secondPage = await wixData.query('collection')
.limit(1000)
.skip(1000)
.find();
const thirdPage = await wixData.query('collection')
.limit(1000)
.skip(2000)
.find();
const allItems = firstPage.items.concat(secondPage.items).concat(thirdPage.items);
return allItems;
}
thanks in advance for all your help. The dataset is called products and the columns are Manufacturer, Model and Part.
Try this out.
I refactored the code to make use of a findAll function, that downloads all the items (inspired by retrieveAllItems found at WixDataQuery API reference)
import wixData from 'wix-data';
$w.onReady(function () {
uniqueDropDown1();
});
// New function. Downloads all items sequentially in batches of 1000
async function findAll(wixDataQuery) {
let allItems = [];
let results = await wixDataQuery.limit(1000).find();
allItems.push(results.items);
while (results.hasNext()) {
results = await results.next();
allItems.push(results.items);
}
return allItems;
}
function uniqueDropDown1() {
// Changed this
findAll(
wixData.query("Products")
.ascending("Manufacturer")
).then(results => {
const uniqueTitles = getUniqueTitles(results);
$w("#dropdown1").options = buildOptions(uniqueTitles);
});
function getUniqueTitles(items) {
const titlesOnly = items.map(item => item.Manufacturer);
return [...new Set(titlesOnly)];
}
function buildOptions(uniqueList) {
return uniqueList.map(curr => {
return { label: curr, value: curr };
});
}
}
export function dropdown1_change(event) {
uniqueDropDown2();
$w("#dropdown2").enable();
function uniqueDropDown2() {
// Changed this
findAll(
wixData.query("Products")
.contains("Manufacturer", $w("#dropdown1").value)
.ascending("Model")
).then(results => {
const uniqueTitles = getUniqueTitles(results);
$w("#dropdown2").options = buildOptions(uniqueTitles);
});
function getUniqueTitles(items) {
const titlesOnly = items.map(item => item.Model);
return [...new Set(titlesOnly)];
}
function buildOptions(uniqueList) {
return uniqueList.map(curr => {
return { label: curr, value: curr };
});
}
}
}
export function dropdown2_change(event) {
uniqueDropDown3();
$w("#dropdown3").enable();
function uniqueDropDown3() {
// Changed this
findAll(
wixData.query("Products")
.contains("Model", $w("#dropdown2").value)
.ascending("Part")
).then(results => {
const uniqueTitles = getUniqueTitles(results);
$w("#dropdown3").options = buildOptions(uniqueTitles);
});
}
function getUniqueTitles(items) {
const titlesOnly = items.map(item => item.Part);
return [...new Set(titlesOnly)];
}
function buildOptions(uniqueList) {
return uniqueList.map(curr => {
return { label: curr, value: curr };
});
}
}

I get Error: Can't perform a React state update on an unmounted component even though i created cleanup

An error keeps bothering me on my app says
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I do declare a useEffect in my Context so that I can have a realtime data storing and getting for my app.
Here is my code in Context;
const FetchProvider = ({ children }) => {
const [userList, setUserList] = useState([]);
const [teamList, setTeamList] = useState([]);
const authContext = useContext(AuthContext);
const authAxios = axios.create({
baseURL: process.env.REACT_APP_API_URL,
});
useEffect(() => {
let isCancelled = false;
if (
authContext.isAuthenticated &&
authContext.authState.userInfo !== null
) {
const getUsers = async () => {
try {
const users = await authAxios.get('/admin/get-all-users');
if (!isCancelled) {
setUserList(users.data);
}
} catch (error) {
if (!isCancelled) {
console.log(error);
}
}
};
const getTeams = async () => {
try {
const teams = await authAxios.get('/get-all-teams');
if (!isCancelled) {
setTeamList(teams.data);
}
} catch (error) {
if (!isCancelled) {
console.log(error);
}
}
};
getUsers();
getTeams();
}
return () => {
isCancelled = true;
};
}, [authAxios, authContext]);
return (
<Provider
value={{
authAxios,
userList,
setUserList,
teamList,
setTeamList,
}}
>
{children}
</Provider>
);
};
And I get this error in my Login.jsx and in my even though I don't declare useEffect in submitting and declaring it in .
Here is my code;
const submitCredentials = async (credentials, resetForm) => {
try {
setLoginLoading(true);
const { data } = await publicFetch.post('signin', credentials);
authContext.setAuthState(data);
setSignupSuccess(data.message);
setSignupError('');
setOpen(true);
setTimeout(() => {
setLoginLoading(false);
setredirectOnlogin(true);
resetForm(true);
}, 400);
} catch (error) {
setLoginLoading(false);
const { data } = error.response;
setSignupError(data.message);
setSignupSuccess('');
setOpen(true);
}
return setLoginLoading(false);
};
And I have tried many ways the internet has offered to fix this up but unfortunately it does not fix my problem.
I do have useEffect in my UsersTable.jsx and TeamTables.jsx.
Here is my code in UsersTable.jsx;
useEffect(() => {
let isCancelled = false;
const getUsers = async () => {
try {
const users = await fetchContext.authAxios.get('/admin/get-all-users');
setIsLoaded(true);
if (isLoaded === true) {
if (!isCancelled) {
fetchContext.setUserList(users.data);
}
}
return () => {
isCancelled = true;
};
} catch (error) {
if (!isCancelled) {
console.log(error);
}
}
};
getUsers();
return () => {
isCancelled = true;
};
}, [fetchContext, isLoaded]);
Here is my useEffect code in my TeamTable.jsx;
useEffect(() => {
let isCancelled = false;
const getTeams = async () => {
try {
const teams = await fetchContext.authAxios.get('get-all-teams');
setIsLoaded(true);
if (isLoaded === true) {
if (!isCancelled) {
fetchContext.setTeamList(teams.data);
}
}
} catch (error) {
if (!isCancelled) {
console.log(error);
}
}
};
getTeams();
return () => {
isCancelled = true;
};
}, [fetchContext, isLoaded]);
The isLoaded is used as an AJAX
Well, you can use the React recommended way to fix this issue. All you need to do is wrap your api call within makeCancellable method and cancel them when your component is unmounting.
Ref: https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html
To do that create
const makeCancelable = (promise) => {
let isCancelled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => isCancelled ? reject({isCanceled: true}) : resolve(val),
error => isCancelled ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
isCancelled = true;
},
};
};
create a variable for the request outside your useEffect
let fetchTeamsRequest = null;
and updated your useEffect function like below.
useEffect(() => {
const getTeams = async () => {
if (fetchTeamsRequest) {
try {
await fetchTeamsRequest.promise;
return;
} catch (error) {
return;
}
}
fetchTeamsRequest = makeCancellable(fetchContext.authAxios.get('get-all-teams'));
try {
const teams = await fetchTeamsRequest.promise;
fetchTeamsRequest = null;
setIsLoaded(true);
if (isLoaded === true) {
if (!fetchTeamsRequest.isCancelled) {
fetchContext.setTeamList(teams.data);
}
}
} catch (error) {
if (!fetchTeamsRequest.isCancelled) {
fetchTeamsRequest = null;
console.log(error);
}
}
};
getTeams();
return () => {
if (fetchTeamsRequest) {
fetchTeamsRequest.cancel();
}
};
}, [fetchContext, isLoaded]);

cancel multiple promises inside a promise on unmount?

hi i want to cancel promise on unmount since i received warning,
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
My code:
const makeCancelable = (promise: Promise<void>) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
(val) => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)),
(error) => (hasCanceled_ ? reject({ isCanceled: true }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
useEffect(() => {
const initialize = async () => {
const getImageFilesystemKey = (remoteUri: string) => {
const [_, fileName] = remoteUri.split('toolbox-talks/');
return `${cacheDirectory}${fileName}`;
};
const filesystemUri = getImageFilesystemKey(uri);
try {
// Use the cached image if it exists
const metadata = await getInfoAsync(filesystemUri);
if (metadata.exists) {
console.log('resolve 1');
setFileUri(filesystemUri);
} else {
const imageObject = await downloadAsync(uri, filesystemUri);
console.log('resolve 2');
setFileUri(imageObject.uri);
}
// otherwise download to cache
} catch (err) {
console.log('error 3');
setFileUri(uri);
}
};
const cancelable = makeCancelable(initialize());
cancelable.promise
.then(() => {
console.log('reslved');
})
.catch((e) => {
console.log('e ', e);
});
return () => {
cancelable.cancel();
};
}, []);
but i still get warning on fast press, help me please?
You're cancelling the promise, but you are not cancelling the axios call or any of the logic that happens after it inside initialize(). So while it is true that the console won't print resolved, setFileUri will be called regardless, which causes your problem.
A solution could look like this (untested):
const makeCancelable = (promise: Promise<void>) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)),
error => (hasCanceled_ ? reject({ isCanceled: true }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
}
};
};
const initialize = async () => {
const getImageFilesystemKey = (remoteUri: string) => {
const [_, fileName] = remoteUri.split("toolbox-talks/");
return `${cacheDirectory}${fileName}`;
};
const filesystemUri = getImageFilesystemKey(uri);
try {
// Use the cached image if it exists
const metadata = await getInfoAsync(filesystemUri);
if (metadata.exists) {
console.log("resolve 1");
return filesystemUri;
} else {
const imageObject = await downloadAsync(uri, filesystemUri);
console.log("resolve 2");
return imageObject.uri;
}
// otherwise download to cache
} catch (err) {
console.error("error 3", err);
return uri;
}
};
useEffect(() => {
const cancelable = makeCancelable(initialize());
cancelable.promise.then(
fileURI => {
console.log("resolved");
setFileUri(fileURI);
},
() => {
// Your logic is such that it's only possible to get here if the promise is cancelled
console.log("cancelled");
}
);
return () => {
cancelable.cancel();
};
}, []);
This ensures that you will only call setFileUri if the promise is not cancelled (I did not check the logic of makeCancelable).

Categories

Resources