Same function for all queries onSuccess react-query - reactjs

I have a use-case where I would like to run the same function onSuccess for all mutations and queries globally instead of having to set the same function on each individual query (i have a lot of queries)
I have a bunch of queries like so
const q1 = useQuery(
"q1",
async () => {
return await axios
.get(`/some/path`)
.then((res) => res.data)
.catch((e) => CustomError(e));
},
{
onSuccess: () => generic(),
}
);
const q2 = useQuery(
"q2",
async () => {
return await axios
.get(`/some/path`)
.then((res) => res.data)
.catch((e) => CustomError(e));
},
{
onSuccess: () => generic(),
}
);
const q1 = useQuery(
"q3",
async () => {
return await axios
.get(`/some/path`)
.then((res) => res.data)
.catch((e) => CustomError(e));
},
{
onSuccess: () => generic()
}
);
function generic() {
return "should be set globally and run on ever OnSuccess event"
}
However, I would like to set this globally for all quires, something like this
const queryCache = new QueryClient({
defaultConfig: {
queries: {
onSuccess: () => {
return "should be set globally and run on ever OnSuccess event";
},
},
},
});
const q1 = useQuery("q1", async () => {
return await axios
.get(`/some/path`)
.then((res) => res.data)
.catch((e) => CustomError(e));
});
const q2 = useQuery("q2", async () => {
return await axios
.get(`/some/path`)
.then((res) => res.data)
.catch((e) => CustomError(e));
});
const q1 = useQuery("q3", async () => {
return await axios
.get(`/some/path`)
.then((res) => res.data)
.catch((e) => CustomError(e));
});
I have searched the docs for about an hour for this type of functionality but cannot find anything

I was able to find out how to solve this for my use-case, it was a case of setting the OnSuccess function using setDefaultOptions.
turns out this code
const queryCache = new QueryClient({
defaultConfig: {
queries: {
onSuccess: () => {
return "should be set globally and run on ever OnSuccess event";
},
},
},
});
wasn't doing anything, instead i set the defaults through the function
const queryCache = new QueryClient();
queryCache.setDefaultOptions({
queries: {
refetchOnWindowFocus: false,
onSuccess: () => console.log("Got IM!"),
},
});
This triggers console.log("Got Im!") onSuccess for every time i call my API which is the desired outcome for my use-case.
i can see that const queryCache = new QueryClient(); does have a constructor that takes defaultOptions however, for whatever reason they do not set.
EDIT
turns out it does work passing it to the constructor, its just this code was written when using an older version of react-query when the key was defaultConfig instead of defaultOptions. This code also works (aswell as the solution above)
const queryCache = new QueryClient({
defaultOptions: {
queries: {
onSuccess: () => console.log("Got IM!"),
},
},
});

There is an open PR for that exact use case: https://github.com/tannerlinsley/react-query/pull/2404
It adds the possibility to have a global onSuccess callback on the queryCache.

Related

TypeError: Cannot read properties of undefined (reading 'setRestaurants')

I'm working on a project where I am trying to fetch a list of restaurants from a database and display them on the screen.
When I run the code below I get this error:
TypeError: Cannot read properties of undefined (reading
'setRestaurants')
at CustomerDashPage.js:39
at async fetchList (CustomerDashPage.js:39)
at async getList (CustomerDashPage.js:32)
I know the fetch from the database works as I can console.log restaurants after I get them and all the tags from the database are the same as what is initially in the useState.
const [restaurants, setRestaurants] = useState([
{
Restaurant_id: "R763567026",
Restaurant_menuID: 0,
Restaurant_name: "Boston Pizza",
Restaurant_address: "271 Blackmarsh Rd",
Restaurant_postal: "P1Z 7A5",
Restaurant_username: "firstrest",
Restaurant_orders: ["O415052628", "O321764897", "O252073901", "O724516036"],
Restaurant_menuID: "M859068353",
Restaurant_category: "Japanese",
Restaurant_availability: true,
Restaurant_openHour: "11h00",
Restaurant_closeHour: "22h00",
},
]);
useEffect(() => {
const getList = async () => {
const fetchRest = await fetchList('R763567026');
}
getList();
}, [])
const fetchList = async (id) => {
try {
const resp = await fetch("/restaurant/id/" + id)
.then((resp) => resp.json())
.then(data => this.setRestaurants(data)).then(console.log(restaurants))
.catch(err => console.log(err));
}
catch (err) {
throw err;
console.log(err);
}
return true;
}
//Controls what happens when a restaurant is selected.
const selectRestaurant = async (id) => {
console.log(id);
};
return (
<div>
<Header />
<RestaurantList
itemList={restaurants}
component={RestaurantCard}
onSelect={selectRestaurant}
>
{" "}
</RestaurantList>
</div>
);
};
export default CustomerDash;
Any help would be much appreciated
As Abu mentioned in his answer, you need to call setRestaurants, not this.setRestaurants. Also, since you are using async/await syntax, you don't need all of those .then() calls.
const fetchList = async (id) => {
const response = await fetch(`/restaurant/id/${id}`).catch((err) => throw err);
const json = await response.json();
setRestaurants(json);
console.log(restaurants);
return true;
};
It's functional component so use setRestaurants instead of this.setRestaurants
const fetchList = async (id) => {
try {
const resp = await fetch("/restaurant/id/" + id)
.then((resp) => resp.json())
.then(data =>
setRestaurants(data))
.catch(err => console.log(err));
}
catch (err) {
throw err;
console.log(err);
}
}
After updating state, you won't get state value instantly. so your console.log(restaurants) won't work.

How to wait for value before running fetch?

Edit: I ended up using axios instead of fetch and it works great. Just removed the response.json() and switch fetch to axios.get.
my first post here with what is probably a pretty easy question. I am trying to get the lat and long values to actually be something before being fed into the URL. Most of the time I get an error returned for a bad request because the lat and long values haven't propagated yet.
Thanks!
Code is here (edited out API keys):
const fetchData = async () => {
navigator.geolocation.getCurrentPosition(function (position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
const url =
await `https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=DELETED`;
await fetch(url)
.then((response) => {
return response.json();
})
.then((result) => {
setData(result);
})
.catch(console.error);
};
fetchData();
}, [lat, long]);
It seems that lat and long are set in the useEffect using them. You should probably set them before using them in another useEffect.
useEffect(() => {
navigator.geolocation.getCurrentPosition(function (position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
}, [])
useEffect(() => {
const fetchData = async () => {
const url = `https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=DELETED`;
await fetch(url)
.then((response) => {
return response.json();
})
.then((result) => {
setData(result);
})
.catch(console.error);
};
if (lat && long) {
fetchData();
}
}, [lat, long]);
Either you have to store those values in your function or you have to wait until the state is updated. State is asynchronous and this is why you get this error.

Calling axios request one after the other?

I have tow functions in my ReactJs application called,
AuthService.addUser(newUser);
AuthService.userCategories(usercategories);
I want to run these two functions separately, which means the Axios request of the second function should be called after the Axios request of the first function when clicked the submit button. How do I approach the solution? Thanks in advance.
I tried in this way. Is this correct?
const handleSubmit = (e) => {
e.preventDefault();
AuthService.addUser(newUser);
AuthService.userCategories(usercategories);
};
Here are my two functions
addUser: (user) => {
//console.log(post);
axios
.post(CONSTANTS.HOSTNAME + "/api/users/register", user)
.then((res) => {
//save to local storage
const { token } = res.data;
localStorage.setItem("jwtToken", token);
isAuthenticated.next(true);
setAuthToken(token);
Swal.fire({
icon: "success",
title: "Signup Successful!",
showConfirmButton: false,
timer: 1500,
}).then(() => {
window.location.href = "/";
//decode token to get user data
const decoded = jwt_decode(token);
currentUser.next(decoded);
console.log(decoded);
});
})
.catch((err) => {
console.log(err.response.data);
Swal.fire({
icon: "error",
title: "Oops...",
text: err.response.data,
});
// alert(JSON.stringify(err.response.data));
});
},
userCategories: (userCategories) => {
axios
.post(CONSTANTS.HOSTNAME + "/api/users/usercategories", userCategories)
.then((res) => {
console.log(res.data);
});
},
just use promise if function return promise:
const handleSubmit = async (e) => {
e.preventDefault();
await AuthService.addUser();
await AuthService.userCategories();
};
or make promise from function and run async
function one() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('resolve one')
return resolve("i am after five seconds")
},
2000);
});
}
function two() {
return new Promise((resolve, reject) => {
console.log('resolve two')
return resolve("i am after three seconds")
});
}
const handleSubmit = async () => {
console.log('run handleSubmit')
await one();
await two();
}
handleSubmit()

How to mock a fetch call that is within an arrow function?

I'm trying to test the invocation of a function that deletes specific data saved in a database in React. The problem is I want to only mock the fetch call and have everything else run as usual because right now whenever tests are run the data gets deleted in the database.
Here is my code for the delete function:
deleteEvent = async () => {
try {
await fetch(
"api url",
{
method: "DELETE",
}
)
.then((res) => res.json())
.then(
(result) => {
console.log(result);
},
(error) => {
console.log(error);
}
);
} catch (error) {
console.error(error);
}
this.props.history.push("/eventList");
};
And here is my test code:
test("deleteEvent function works", (done) => {
const mockSuccessResponse = {};
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
jest.spyOn(global, "fetch").mockImplementation(() => mockFetchPromise);
const historyMock = { push: jest.fn() };
const wrapper = shallow(<EventState history={historyMock} />);
wrapper.instance().deleteEvent();
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(historyMock.push.mock.calls[0]).toEqual(["/eventList"]);
global.fetch.mockClear();
done();
});
I get number times called: 0 for the expect(global.fetch).toHaveBeenCalledTimes(1);
and a Received: undefined for the expect(historyMock.push.mock.calls[0]).toEqual(["/eventList"]);
Any help would be great
Instead of using spyOn(global, fetch), try this:
global.fetch = jest.fn().mockImplementation(() => mockFetchPromise);
const historyMock = { push: jest.fn() };
const wrapper = shallow(<EventState history={historyMock} />);
wrapper.instance().deleteEvent();
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(historyMock.push.mock.calls[0]).toEqual(["/eventList"]);
global.fetch.mockClear();
done();
});

Mock-axios-adapter not mocking get request

I'm trying to test this function:
export const fetchCountry = (query) => {
return dispatch => {
dispatch(fetchCountryPending());
return axios.get(`${process.env.REACT_APP_API_URL}/api/v1/countries/?search=${query}`)
.then(response => {
const country = response.data;
dispatch(fetchCountryFulfilled(country));
})
.catch(err => {
dispatch(fetchCountryRejected());
dispatch({type: "ADD_ERROR", error: err});
})
}
}
Here is my test:
describe('country async actions', () => {
let store;
let mock;
beforeEach(() => {
mock = new MockAdapter(axios)
store = mockStore({ country: [], fetching: false, fetched: false })
});
afterEach(() => {
mock.restore();
store.clearActions();
});
it('dispatches FETCH_COUNTRY_FULFILLED after axios request', () => {
const query = 'Aland'
mock.onGet(`/api/v1/countries/?search=${query}`).reply(200, country)
store.dispatch(countryActions.fetchCountry(query))
.then(() => {
const actions = store.getActions();
expect(actions[0]).toEqual(countryActions.fetchCountryPending())
expect(actions[1]).toEqual(countryActions.fetchCountryFulfilled(country))
});
});
When I run this test, I get an error UnhandledPromiseRejectionWarning and that fetchCountryPending was not received and that fetchCountryRejected was. It seems as if onGet() is not doing anything. When I comment out the line
mock.onGet('/api/v1/countries/?search=${query}').reply(200, country), I end up getting the exact same result, making me believe that nothing is being mocked. What am I doing wrong?
I couldn't get the .then(() => {}) to work, so I turned the function into an async function and awaited the dispatch:
it('dispatches FETCH_COUNTRY_FULFILLED after axios request', async () => {
const query = 'Aland'
mock.onGet(`/api/v1/countries/?search=${query}`).reply(200, country)
await store.dispatch(countryActions.fetchCountry(query))
const actions = store.getActions();
expect(actions[0]).toEqual(countryActions.fetchCountryPending())
expect(actions[1]).toEqual(countryActions.fetchCountryFulfilled(country))
});

Resources