ReactJS: axios POST with recursive function - reactjs

CreateFolder.js
Hi!! I'm trying loop an js tree object to create a folder for the childs elements with de Egnyte API.
I think I have posed the problem wrong.
My recursive funcion is cheking if the parent have childs, if this true, loop his childs, if the child isn't a parent, i want to create the folder with this child id.
The problem is when I see the console, first there are all prints of the '-----ADD-----', after that, there are all prints of the '-----ADDED' so I don't understant why they aren't simultaneously
The API works fine, this create my 6 folders, the problem of this code is when I loop my original TREE that have 400 childs, and I need to create this 400 folders but the callbacks doesn't work, this creates about 60 folders, and there are much 403 errors. I think there are much calls in a short time.
I tried to call the function addFolder with a time out or doing the function async but doesn't work too.
I explained my problem well? Can someone help me?
Thank you so much!!
import { ConnectingAirportsOutlined } from "#mui/icons-material"
import axios from "axios"
import { useEffect, useState } from "react"
import { useSelector } from "react-redux"
export default function CreateFolders() {
console.log('======CreateFolders=====')
// const state = useSelector(state => state)
// const tree = state.assets.tree
const tree = [
{
id: '1000',
name: 'GRUPO GENERADORES',
child: [
{
id: '1100',
name: 'MOTORES',
child: [
{
id: '1100.1',
name: 'MOTOR 1'
},
{
id: '1100.2',
name: 'MOTOR 2'
},
{
id: '1100.3',
name: 'MOTOR 3'
},
]
},
{
id: '1200',
name: 'ALTERNADORES',
child: [
{
id: '1200.1',
name: 'ALTERNADOR 1'
},
{
id: '1200.2',
name: 'ALTERNADOR 2'
},
{
id: '1200.3',
name: 'ALTERNADOR 3'
}
]
}
]
}
]
useEffect(() => {
getTreeItems(tree)
}, [tree])
const api = 'https://test.egnyte.com/pubapi/v1/fs/Shared/test/'
const headers = {
"headers": {
"Authorization": "Bearer XXXXXXXXXX",
"Content_type": "application/json"
}
}
const body = {
"action": "add_folder"
}
const addFolder = async (id) => {
const endpoint = api + id
await axios.post(endpoint, body, headers).then(res => {
console.log('OK')
return
}).catch((error) => {
console.log('ERROR')
})
}
const getTreeItems = treeItems => {
return treeItems.map(item => {
if (item.child && item.child.length > 0) {
getTreeItems(item.child)
}
if (item.id.includes('.')) {
console.log('-------ADD-------')
console.log(item.id)
addFolder(item.id)
console.log('-------ADDED')
}
})
}
return (
<>
<h2>CreateFolders</h2>
</>
)
}

The description you provided seems like you reached the rate limit (per second/daily).
Try to explore the response headers in failed requests to get more information about:
What kind of quotas have you exceeded (per second/daily)
When you can retry the request (e.g Retry-After header)
There are 2 solutions you can try:
Use bulk operation (create all the entities by single request) if the API supports it. This is a preferable solution
Retry the request after the timeout you get from the failed request header (e.g Retry-After header)

I solved my problem with the setTimeout with an index, because my function was executing the whole thing after 3 seconds, and with this index, add more time to every item, and executes each item 3 seconds after the previous one
My function after:
folders.map((id) => {
setTimeout(() => {
addFolder(id)
}, 3000)
})
My function before:
folders.map((id, index) => {
setTimeout(() => {
addFolder(id)
}, 3000 * index)
})
It's silly but I didn't know how the setTimeout worked

Related

GraphQL inMemoryCache causes React app to stop working when keyArgs set to false

so I have a file called index.js that has the following code (React Project btw):
const [servers, setServers] = useState([]);
const [bulkEditServerIds, setBulkEditServerIds] = useState([]);
const [serverLimitQuery] = useState(1);
const [serverSkipQuery, setServerSkipQuery] = useState(0);
const {
data: serversData,
loading: serversLoading,
error: serversError,
fetchMore,
} = useQuery(GET_SERVERS_QUERY, {
variables: {
serverIds: [],
serverLimit: serverLimitQuery,
skipServers: serverSkipQuery,
},
onCompleted: () => {
setBulkEditServerIds(serversData.servers.allServerIds);
setServers(serversData.servers.servers);
},
});
the GET_SERVERS_QUERY is :
import { gql } from '#apollo/client';
export const GET_SERVERS_QUERY = gql`
query servers($serverIds: [ID], $serverLimit: Int, $skipServers: Int) {
servers(
serverIds: $serverIds
serverLimit: $serverLimit
skipServers: $skipServers
) {
servers {
id
_id
name
}
serversAssignedToAppsPercentage
serversWithMandatoryInfoPercentage
allServerIds
}
client {
name
}
}
applications(page: 1, limit: 0, searchCriteria: {}) {
applications {
_id
id
name
}
}
}
`;
This query me some dummy data called serversData:
{
applications: {
__typename: 'ApplicationListPage',
applications: [
{
__typename: 'Application',
_id: '63dcd69706c4370de6aee607',
id: '63dcd69706c4370de6aee607',
name: 'Application - 1',
},
{
__typename: 'Application',
_id: '63dcd69d06c4370de6af1274',
id: '63dcd69d06c4370de6af1274',
name: 'Application - 10',
},
],
},
client: {
__typename: 'Client',
name: 'FutureProof',
},
servers: {
allServerIds: ['1', '2', '3', '4'],
serversAssignedToAppsPercentage: 100,
serversWithMandatoryInfoPercentage: 99,
__typename: 'ServerListPage',
servers: [
{
__typename: 'Server',
id: '63dcd62e06c4370de6acd80d',
_id: '63dcd62e06c4370de6acd80d',
name: 'VM-77',
},
],
},
};
Now all of this is fine but in this project i am trying to implement pagination, as seen through serverLimit (how many servers each time) and skipServers (this is used in the api for moongoose). For pagination,
in the index.js file, i have a button that calls this function:
<button onClick={getNewData} />
const getNewData = () => {
setServerSkipQuery(serverSkipQuery + serverLimitQuery);
};
This updates the state for sercerSkipQuery which triggers this useEffect Hook that tracks
sercerSkipQuery state:
useEffect(() => {
if (serverSkipQuery !== 0) {
fetchMore({
variables: {
serverIds: [],
serverLimit: serverLimitQuery,
skipServers: serverSkipQuery,
},
});
}
}, [serverSkipQuery]);
How This calls GraphQL's fetchMore function with the new variables.
Now if you look at the pagination docs (https://www.apollographql.com/docs/react/pagination/core-api/)
It says you need to have a type policy for the query
so this is for typepolicy for the servers query:
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
servers: {
// Don't cache separate results based on
// any of this field's arguments.
keyArgs: true,
// Concatenate the incoming list items with
// the existing list items.
merge(existing = {}, incoming) {
const a = {
...existing,
...incoming,
servers: [...incoming.servers, ...(existing.servers || [])],
};
return a;
},
},
},
},
},
}),
so the merge funciton only updates the servers field:
for reference when the page intially loads, if i console log existing and incoming, i get:
existing:
{}
incoming:
{
"__typename": "ServerListPage",
"servers": [
{
"__ref": "63dcd62e06c4370de6acd80d"
}
],
"serversAssignedToAppsPercentage": 100,
"serversWithMandatoryInfoPercentage": 99,
"allServerIds": ['1', '2', '3', '4'],
}
the issue is, because in the cache i have keyArgs set to true, everytime i click the button, graphql creates a new cache because the arguement variables have changed. This causes me to lose the existing data and gives me a duplicate of the new incoming data that comes. In other words, both existing and incoming become this when i call click the button for more data:
{
"__typename": "ServerListPage",
"servers": [
{
"__ref": "63dcd63306c4370de6acdd88"
}
],
"serversAssignedToAppsPercentage": 100,
"serversWithMandatoryInfoPercentage": 99,
"allServerIds": ['1', '2', '3', '4'],
}
so we clearly lose the previous data we had. Therfore i need to set keyArgs to false so we only have one single cache even when our query variables change. but as soon as I do that and I click the button, i get an error pop up. The API shows no error and the devtools console just says component error with no other information
i also noticed that if i set keyArgs to false and click the button:
the state for serverSkipQuery does not update and fetchMore does not get run (I checked this by console logging serverSkipQuery just before the if statement in the useEffect hook that tracks serverSkipQuery). This only happens if i set keyArgs to false, if i set it to true, eveything works but it does not get cached properly

Why my setState returns an array, but the state is a promise when the component rerender?

The code below try to check if an url is reachable or not.
The urls to check are stored in a state called trackedUrls
I update this state with an async function checkAll.
The object just before being updated seems fine, but when the component rerender, it contains a promise !
Why ?
What I should change to my code ?
import React from "react"
export default function App() {
const [trackedUrls, setTrackedUrls] = React.useState([])
// 1st call, empty array, it's ok
// 2nd call, useEffect populate trackedUrls with the correct value
// 3rd call, when checkAll is called, it contains a Promise :/
console.log("trackedUrls :", trackedUrls)
const wrappedUrls = trackedUrls.map(urlObject => {
return (
<div key={urlObject.id}>
{urlObject.label}
</div>
)
})
// check if the url is reachable
// this works well if cors-anywhere is enable, click the button on the page
async function checkUrl(url) {
const corsUrl = "https://cors-anywhere.herokuapp.com/" + url
const result = await fetch(corsUrl)
.then(response => response.ok)
console.log(result)
return result
}
// Checks if every url in trackedUrls is reachable
// I check simultaneously the urls with Promise.all
async function checkAll() {
setTrackedUrls(async oldTrackedUrls => {
const newTrackedUrls = await Promise.all(oldTrackedUrls.map(async urlObject => {
let isReachable = await checkUrl(urlObject.url)
const newUrlObject = {
...urlObject,
isReachable: isReachable
}
return newUrlObject
}))
// checkAll works quite well ! the object returned seems fine
// (2) [{…}, {…}]
// { id: '1', label: 'google', url: 'https://www.google.Fr', isReachable: true }
// { id: '2', label: 'whatever', url: 'https://qmsjfqsmjfq.com', isReachable: false }
console.log(newTrackedUrls)
return newTrackedUrls
})
}
React.useEffect(() => {
setTrackedUrls([
{ id: "1", label: "google", url: "https://www.google.Fr" },
{ id: "2", label: "whatever", url: "https://qmsjfqsmjfq.com" }
])
}, [])
return (
<div>
<button onClick={checkAll}>Check all !</button>
<div>
{wrappedUrls}
</div>
</div>
);
}
Konrad helped me to grasp the problem.
This works and it's less cumbersome.
If anyone has a solution with passing a function to setTrackedUrls, I'm interested just for educational purpose.
async function checkAll() {
const newTrackedUrls = await Promise.all(trackedUrls.map(async urlObject => {
let isReachable = await checkUrl(urlObject.url)
const newUrlObject = {
...urlObject,
isReachable: isReachable
}
return newUrlObject
}))
setTrackedUrls(newTrackedUrls)
}
You can only put data into setState.

Apollo MockedProvider: “Failed to match x mocks for this query” where x is off by 1. Never sees the query I ask for

I’m experiencing the most bizarre Apollo behavior… in my tests, any time I issue a query for which I have a mock, Apollo magically can’t find it.
I have 5 mocks covering queries based on a collectionId and some pagination variables. When I have the component under test look up ID 834940, I get this:
Expected variables: {"page":1,"perPage":20,"collectionId":834940}
Failed to match 4 mocks for this query, which had the following variables:
{"collectionId":374276,"page":1,"perPage":20}
{"collectionId":112805,"page":1,"perPage":20}
{"collectionId":238350,"page":1,"perPage":20}
{"collectionId":-1,"page":1,"perPage":20}
Note that it says “4 mocks”. So it’s not seeing the mock I need, and the query fails. Make sense… until I change the component under test to look up ID 112805, which you can clearly see listed in the 4 mocks Apollo ostensibly knows about.
Expected variables: {"page":1,"perPage":20,"collectionId":112805}
Failed to match 4 mocks for this query, which had the following variables:
{"collectionId":374276,"page":1,"perPage":20}
{"collectionId":834940,"page":1,"perPage":20}
{"collectionId":238350,"page":1,"perPage":20}
{"collectionId":-1,"page":1,"perPage":20}
It can still only see 4 mocks, but the mocks it can see have changed. Now, all of a sudden, 112805 is missing in the mocks it sees. And now it CAN see the mock for 834940, the ID I queried for last time! So basically, whatever I ask for is the query it can’t resolve!!
Has anyone encountered this before?
Context
Collection.tsx:
A functional component with 3 different useQuery calls. Only the third one is failing:
import collectionQuery from './Collection.gql';
export type CollectionProps = {
className?: string;
collection: {
id: number;
name: string;
};
};
export function Collection( props: CollectionProps ) {
/* [useQuery #1] */
/* [useQuery #2] */
// useQuery #3
const {
data, error, loading, refetch,
} = useQuery<CollectionQuery, CollectionQueryVariables>( collectionQuery, {
skip: variables.collectionId === -1,
variables,
} );
return null;
}
queryMocks.ts:
import collectionQuery from './Collection.gql';
import { CollectionQuery_collection } from '../../../graphqlTypes/CollectionQuery';
const page = 1;
const perPage = 20;
export const firstCollection: CollectionQuery_collection = {
id: 374276,
name: 'First Collection',
};
export const secondCollection: CollectionQuery_collection = {
id: 834940,
name: 'Second Collection',
};
export const thirdCollection: CollectionQuery_collection = {
id: 112805,
name: 'Third Collection',
}
export const fourthCollection: CollectionQuery_collection = {
id: 238350,
name: 'Fourth Collection',
};
export const fifthCollection: CollectionQuery_collection = {
id: -1,
name 'Fifth Collection (Error)',
};
export const queryMocks: MockedResponse[] = [
{
request: {
query: collectionQuery,
variables: {
collectionId: firstCollection.id,
page,
perPage,
},
},
result: {
data: {
collection: firstCollection,
},
},
},
{
request: {
query: collectionQuery,
variables: {
collectionId: secondCollection.id,
page,
perPage,
},
},
result: {
data: {
collection: secondCollection,
},
},
},
{
request: {
query: collectionQuery,
variables: {
collectionId: thirdCollection.id,
page,
perPage,
},
},
result: {
data: {
collection: thirdCollection,
},
},
}, {
request: {
query: collectionQuery,
variables: {
collectionId: fourthCollection.id,
page,
perPage,
},
},
result: {
data: {
collection: fourthCollection,
},
},
}, {
request: {
query: collectionQuery,
variables: {
collectionId: fifthCollection.id,
page,
perPage,
},
},
result: {
data: {
collection: fifthCollection,
},
},
},
];
Collection.test.tsx:
import React from 'react';
import { MockedProvider } from '#apollo/client/testing';
import { MemoryRouter } from 'react-router';
import { secondCollection, queryMocks } from './queryMocks';
import { Collection } from './Collection';
it( 'Renders', () => {
render(
<MockedProvider mocks={ queryMocks } addTypename={ false }>
<MemoryRouter>
<Collection collection={ { id: secondCollection.id } } />
</MemoryRouter>
</MockedProvider>,
);
} );
The component is fetching collectionQuery twice, likely due to a state change. This could be from a useState set* call, or it could be from having multiple useQuerys. In the latter case, when one of the 3 queries resolves (with data changing from undefined to defined), it triggers a component re-render, which then calls the other query or queries again.
The reason why this breaks MockedProvider is because each mock can only be used to resolve a single query. So on the first matching API call, the mock is “spent”, reducing the queryMocks length from 5 to 4. Then, when the component re-renders and calls the same query again, Apollo can no longer find a matching mock. So to solve this, you have to either A.) refactor the component to only call the query once, or B.) add two of the same mock to the queryMocks array, like so:
queryMocks.ts:
const secondMock = {
request: {
query: collectionQuery,
variables: {
collectionId: secondCollection.id,
page,
perPage,
},
},
result: {
data: {
collection: secondCollection,
},
},
};
export queryMocks: MockedResponse[] = [
/* [mockOne] */
mockTwo,
mockTwo,
/* [mockThree, ..., mockFive] */
];

React Testing Library custom render function and async await

I have the following custom render function for my component.
It has two modes Create and Edit.
Create is synchronous and Edit is asynchronous.
The function is as follows:
const renderComponent = async (
scheduleId = "",
dialogMode = DialogMode.CREATE,
cohorts = MOCK_COHORT_LIST,
jobs = JOB_LIST,
availableForSchedule = domain === Domain.COHORT ? jobs : cohorts,
) => {
render(
<AddEditScheduleDialog
cohorts={cohorts}
jobs={jobs}
availableForSchedule={availableForSchedule}
scheduleToEdit={scheduleId}
handleToggleDialog={mockToggleDialog}
isDialogVisible={true}
domain={domain}
/>,
{
wrapper: queryWrapper,
},
);
if (dialogMode === DialogMode.EDIT) {
expect(screen.getByRole("progressbar")).toBeInTheDocument();
}
await waitFor(() =>
expect(screen.queryByRole("progressbar")).not.toBeInTheDocument(),
);
return {
header: screen.getByRole("heading", {
level: 1,
name: `schedules:addEditDialog.${domain}.${dialogMode}.title`,
}),
name: {
field: screen.getByTestId(`${domain}-name-field`),
button: within(screen.getByTestId(`${domain}-name-field`)).getByRole(
"button",
),
},
frequency: {
field: screen.getByTestId("schedule-frequency-field"),
input: within(
screen.getByTestId("schedule-frequency-field"),
).getByRole("textbox"),
helperText: `schedules:addEditDialog.form.frequency.helperText`,
},
};
};
Sometimes I get intermittent problems finding elements on the screen. Is this because the function returns before the progressbar has been awaited?
Is there a way i can wait for everything to be rendered prior to returning the screen elements that I need?
If your Edit mode is causing the progress bar to be displayed asynchronously you should wait for it by making use of findByRole e.g.
if (dialogMode === DialogMode.EDIT) {
expect(await screen.findByRole("progressbar")).toBeInTheDocument());
}
This is because find* queries use waitFor "under the hood".

Testing redirect routes with Redux-Saga/Fetch-mock in React

I'm trying to test when a redirect of a route happens in redux-saga. All other tests are passing except this one, which I have not figured out how to test.
This is the saga function that I'm testing...
export function* doFetch({ payload: { id } }) {
try {
const station = yield call(stationApi.fetch, id)
yield put(set(station))
const allStations = yield call(stationApi.fetch)
const cashDrawer = yield call(buildCashDrawerState, station, allStations)
yield put(replaceCashDrawerState(cashDrawer))
yield call(redirectPos, station)
} catch (error) {
yield put(notifyError(
'Something went wrong during the station fetch. Please try again later',
{ id: 'station' },
))
}
}
This is the redirect method...
const redirectPos = (station) => {
if (window.location.href.includes('pos') && station.cash_drawer) {
sagamore.router.redirect('pos')
} else if (!station.cash_drawer) {
sagamore.router.redirect('sales/dashboard')
} else {
return
}
}
And this is the test so far...
fdescribe('sagas/station', () => {
afterEach(() => {
fetchMock.restore()
})
fdescribe('doFetch', () => {
it('fetches station and cash drawer', () => {
const id = 1
const station = {
id,
name: 'new name',
cash_drawer: { location_id: 'location', id: 2 },
}
const stations = [
{ id: 10, name: 'Station1', cash_drawer_id: 2 },
{ id: 11, name: 'Station2', cash_drawer_id: 2 },
// { id: 12, name: 'Station3', cash_drawer_id: 3 },
]
fetchMock.mock(
`/api/stations/${id}`,
station,
)
fetchMock.mock(
`/api/stations`,
{ results : stations },
)
const saga = doFetch({ payload: { id } })
let expected
expected = call(stationApi.fetch, id)
expect(saga.next().value).toEqual(expected)
expected = put(set(station))
expect(saga.next(station).value).toEqual(expected)
expected = call(stationApi.fetch)
expect(saga.next().value).toEqual(expected)
saga.next({ results: stations })
expected = put(replaceCashDrawerState({ drawer: {...station.cash_drawer, stations: stations} }))
expect(saga.next({ drawer: {...station.cash_drawer, stations: stations} }).value).toEqual(expected)
// test redirectPos(...)
expected = call(redirectPos, station)
expect(saga.next().value).toEqual(expected)
})
})
})
I'm new to Redux-Saga and testing. I haven't been able to find a way to test this when researching it. Any help or direction would be appreciated.
Generally, when you write unit test the idea is to test every unit in isolation. So in your case, from the saga perspective the logic inside of redirectPos doesn't matter, all you need to test is that it gets called with the right parameter. Then, you can write another test specifically for the redirectPos function where you test the internals.
Testing current location can get a bit tricky, I suggest visiting other SO questions on that topic such as How to mock window.location.href with Jest + Vuejs?

Resources