Mocking React Query useQueryClient to test cached data - javascript

I use a custom hook to share an increment function accross my app (it increments quantities in a shopping cart).
What that function does :
gets a data object from a React Query cache
increments the data quantity property
makes some API call via React Query useMutation then, on success, updates the React Query cache
After that, the components reads the React Query cart cache and updates the UI with the new quantity.
The hook does the job and finally the cache is updated as expected.
Now, I try to test the hook in a component to check that the UI is updated accordingly.
The API call is mocked using msw. Its returned value is used to update the cache :
rest.put(`${api}/carts/1`, (req, res, ctx) => {
return res(ctx.json({ data: [{ id: 1, quantity: 2 }] }));
})
I also mocked the react-query queryClient.setQueryData and getQueryData functions so I can test their returns values.
jest.mock("react-query", () => ({
...jest.requireActual("react-query"),
useQueryClient: () => ({
setQueryData: jest.fn(),
getQueryData: jest
.fn()
.mockReturnValueOnce({ data: [{ id: 1, quantity: 1 }] })
.mockReturnValueOnce({ data: [{ id: 1, quantity: 2 }] }),
}),
}));
Finally, I test the UI that should updates with the new quantity, but the mocked getQueryData always return the original quantity: 1, even with multiple call.
Now I'm not sure I have the right approach for that test.

Suppose I just want to mock getQueryData for a particular test case and leave the other functions like invalidateQueries, cancelQuery, and setQueryData as it is, then how can modify this mock function?
This is what I wrote. But getting this
TypeError: queryClient.cancelQueries is not a function
jest.mock("#tanstack/react-query", () => ({
...jest.requireActual("#tanstack/react-query"),
useQueryClient: () => ({
// setQueryData: jest.fn(() => ({ data: [{ label: 'Blue', id: 34 }] })),
// cancelQueries: jest.fn(),
// invalidateQueries: jest.fn(),
...jest.requireActual("#tanstack/react-query").useQueryClient(),
getQueryData: jest
.fn()
.mockReturnValueOnce({ data: [{ id: 1, quantity: 1 }] })
.mockReturnValueOnce({ data: [{ id: 1, quantity: 2 }] }),
}),
}));

Why would you need to mock setQueryData and getQueryData ? Mocking the network layer with msw should be all you need. If you wrap your rendered hook in a QueryClientProvider with a queryClient, that will be populated with the mocked data returned from msw, and queryClient.getQueryData will be able to read it without mocking it.

Related

How to assign first state data to second state in react?

When i am setting the data from one state to another state. It's saying undefined. Currently i am working on tinder like card swipe functionality with Right and left button click.
I am passing the user id from card to to button. Such as Right swipe and left swipe button.
//Scenario first
If have declared the array of object static, it's works like a charm, then it does not says any error.
////Scenario Second
If i am setting the data dynamically with API to SetState and assigning the state variable array data to another state variable, it says undefined.
I am trying to solve this issue from 3 days, but nothing worked, i am new in React js. Help would be appreciate.
Here is my code
const AllData= [
{
id: 1,
name: 'XYZ'
},
{
id: 2,
name: 'ABC'
},
{
id: 3,
name: 'ABC 2'
},
{
id: 4,
name: 'ABC 3'
},
{
id: 5,
name: 'ABC 4'
}
] //It works if set static array
const [AllData, setAllData] = useState([]); //it does not works
const GetAllUserData = async () =>{
const bodyParameters ={
session_id : SessionId
};
const {data : {data}} = await axios.post(GETALLUSER_API , bodyParameters);
setAllData(data);
}
// Setting current user from all data
const [currentUser, setCurrentUser] = useState(AllData[0])
console.log(currentUser); // undefined says
Here, AllData will be added to the state after the GetAllUserData is done executing, it is asynchronous function, so AllData will be available after some time, you have to update the currentUser you have to do like this.
useEffect(() => {
if (AllData.length) {
setCurrentUser(AllData[0]);
}
}, [AllData]);
It didn't worked because you may have returned before the setAllData could get called initialize AllData.
Being a api call it take some time to return data.
A good work around for this situation is to put a loader until it get the data and when you eventually receive the data then only render the content on the screen.

How to make params in requests by axios into objects?

I have this code on a ReactJs app:
axios
.get(`/shipping/get-shipping-values`, {
params: {
products: [
{
...product,
quantity,
},
],
postalCode,
cartTotalPrice: getProductPriceNumber(product.price) * quantity,
},
})
.then((response) => {
// do things with response
})
.catch((error) => {
console.log(error);
});
This is the console log of the products param in the express:
[ //Note above the ' that transform this object in an string.
'{"package":{"weight":0.067,"width":5,"height":2,"length":2},"quantity":945}'
]
Console log of products in the frontend:
[ package: { weight: 0.067, width: 5, height: 2, length: 2}, quantity: 945 ]
As you can see, is an array but the object inside this array is an string.
I didn't change the default header of the axios, so is set to aplication/json.
I could use an JSON.parse() to make this string into an object. But I want to know if there is a way to make this automatic. That way I will not need to JSON.parse() every param that I send to the express server.
If you are using expressJS add app.use(express.json()); middleware, before the routes and you can find the entire object inside the req.body object, inside the route handler.
Let me know if this helps.

Store object from API to state

I am trying to use ReactJS to store info provided by a backend API.
My backend API returns a JSON object which contains:
"a": 123
"b": 345
"c": 0
The above object is stored under data from my backend API.
So now I would like to store these values in data separately in React State as follows:
this.state = {
first:'',
second: '',
third: '',
}
//ComponentDidMount happen here
.then(result => {
first: //result.data.data value "a":123 will be stored to State *First*
//and "b":345 will go to second and "c":0 to third
}
debugger;
})
What should I write for that part for storing information to state?
If you are fine with using 0, 1, 2 instead 'first', 'second', 'third', you can map through the values of the object and for each index store it in state
.then(result => Object.values(result).map((value, i) => this.setState({ [i]: value })))
Do notice that the actual keys in the state gonna be in a type of string, so you can access them like this.state['0']
You may try
//ComponentDidMount happen here
.then(result => {
first.setState(result.data.data.a);
second.setState(result.data.data.b);
third.setState(result.data.data.c);
}
debugger;
})

How to put return value into another return value in JS vue

I want the "topic1" to be the value of my breed name and key, but when I try to put this.topic1 to replace the manual typing, it shows nothing.
Or there are any other method to have my button name same as my retrieve API param, and sent it name when I click it?
new Vue({
el: '#app2',
components: { Async },
data() {
return {
topic1: null,
topic2: null,
currentBreed: 0,
breeds: [
{ name: this.topic1 , key: this.topic1 },
{ name: "German Shepherd", key: "germanshepherd" },
{ name: "Husky", key: "husky" },
{ name: "Pug", key: "pug" },
{ name: "(Error)", key: "error" },
]
}
},
async created() {
try {
this.promise = axios.get(
"https://k67r3w45c4.execute-api.ap-southeast-1.amazonaws.com/TwitterTrends"
);
const res = await this.promise;
this.topic1 = res.data[0].Trends;
this.topic2 = res.data[1].Trends;
} catch (e) {
console.error(e);
}
},
async mounted () {
let test = this.topic1;
},
computed: {
breedKey() {
return this.breeds[this.currentBreed].key;
}
}
})
There are several problems here.
The data function is called just once, when the corresponding Vue instance is created. Within that function you can get a reference to its Vue instance via this. At that point some properties, such as those corresponding to props, will already exist. However, others won't.
The object returned from data is used to create new properties on the instance. In this case you're creating 4 properties: topic1, topic2, currentBreed and breeds. Vue creates those properties based on that returned object, so they won't exist until after the data function is run.
So when you write { name: this.topic1 , key: this.topic1 }, within that data function you're attempting to access a property called topic1 that doesn't exist yet. As such it will have a value of undefined. So you're creating an entry equivalent to { name: undefined , key: undefined },.
Further, there is no link back to topic1. That object won't be updated when the value of topic1 changes.
It's also worth noting a few points about timing.
The data function will be called before the created hook, so the axios call isn't made until after the data properties are populated.
An axios call is asynchronous.
Using await may make the code a little easier to read but the 'waiting' is mostly just an illusion. The remaining code inside the function won't run until the awaited promise is resolved but that won't cause anything outside of the function to wait. await is equivalent to using then.
The component will render just after the created hook is called. This is synchronous, it won't wait for the axios request. The mounted hook will then be called, all before the axios call has completed.
All of this means you may need to adjust your template to handle the case where the axios call hasn't completed yet as it will initially render prior to the values of topic1 and topic2 being available.
Specifically addressing the breeds property you have a few options. One is to inject the values in once the value has loaded:
breeds: [
{ name: "" , key: "" }, // Initially empty values
{ name: "German Shepherd", key: "germanshepherd" },
// ...
const res = await this.promise;
this.topic1 = res.data[0].Trends;
this.topic2 = res.data[1].Trends;
this.breeds[0].name = this.breeds[0].key = this.topic1;
Another is to use a computed property for breeds (you'd remove it from the data for this):
computed: {
breeds () {
return [
{ name: this.topic1 , key: this.topic1 },
{ name: "German Shepherd", key: "germanshepherd" },
{ name: "Husky", key: "husky" },
{ name: "Pug", key: "pug" },
{ name: "(Error)", key: "error" },
]
}
}
As we're using a computed property it will be updated when topic1 changes as it's a reactive dependency.
Using a computed property is probably the most natural solution in this case but there are other tricks you can use to get this to work.
For example, you could use property getters for the two properties in that first breed object (that's JavaScript property getters, nothing to do with Vue):
data () {
const vm = this;
return {
topic1: null,
topic2: null,
currentBreed: 0,
breeds: [
{
get name () {
return vm.topic1;
},
get key () {
return vm.topic1;
}
},
{ name: "German Shepherd", key: "germanshepherd" },
{ name: "Husky", key: "husky" },
{ name: "Pug", key: "pug" },
{ name: "(Error)", key: "error" },
]
}
},
I'm not advocating this approach for your use case but it is an interesting way to do it that can sometimes be useful. The key thing to note is how the dependency on topic1 is evaluated only when the properties name and key are accessed, not when the data function is executed. This allows topic1 to be registered as a dependency of whatever is accessing name and key, e.g. during rendering.

Iterating trough array and sending http request dynamically

Okay, I have this array pokemonColor=['name1', 'name2', 'name3']. How can I iterate trough this array and dynamically send http request so I can update state like this below? Because this what I am trying does not work, it creates a JavaScript object, but too many of them...
This is what I want:
this.state.pokemon:[
{name: name1, img: img1},
{name: name2, img: img2},
{name: name3, img: img3}
]
And this is how I am trying:
componentDidUpdate() {
// console.log(this.state.pokemonColor);
this.state.pokemonColor.forEach(pok => {
axios.get(`https://pokeapi.co/api/v2/pokemon/${pok}/`).then(res => {
// console.log(res.data.sprites.front_shiny);
this.setState({
...this.state,
pokemon: {
name: res.data.name,
img: res.data.sprites.front_shiny
}
});
});
});
// console.log(this.state.pokemon);
}
First of all - this.state.pokemon = [...] mutates the state directly. Your app may crash or at least you will receive a long, red error in your console.
Secondly - you don't have to destructure state inside setState function.
Thirdly - with every axios get request, you are overwriting pokemon field in your state. I'd suggest you to keep your pokemons inside array.
this.setState((prevState) => ({
pokemon: [
...prevState.pokemon,
{
name: res.data.name,
img: res.data.sprites.front_shiny,
},
],
});
Then you will be able to access your pokemons by referencing to this.state.pokemons, which will be an array of objects.

Categories

Resources