I have a three-check box type,
When I check any box I call refetch() in useEffect().
The first time, I check all boxes and that returns the expected data!
but for some cases "rechange the checkboxes randomly", the returned data from API is "undefined" although it returns the expected data in Postman!
So I Guess should I need to provide a unique queryKey for every data that I want to fetch
so I provide a random value "Date.now()" but still return undefined
Code snippet
type bodyQuery = {
product_id: number;
values: {};
};
const [fetch, setFetch] = useState<number>();
const [bodyQuery, setBodyQuery] = useState<bodyQuery>({
product_id: item.id,
values: {},
});
const {
data: updatedPrice,
status,
isFetching: loadingPrice,
refetch,
} = useQuery(
['getUpdatedPrice', fetch, bodyQuery],
() => getOptionsPrice(bodyQuery),
{
enabled: false,
},
);
console.log('#bodyQuery: ', bodyQuery);
console.log('#status: ', status);
console.log('#updatedPrice: ', updatedPrice);
useEffect(() => {
if (Object.keys(bodyQuery.values).length > 0) {
refetch();
}
}, [bodyQuery, refetch]);
export const getOptionsPrice = async (body: object) => {
try {
let response = await API.post('/filter/product/price', body);
return response.data?.detail?.price;
} catch (error) {
throw new Error(error);
}
};
So after some elaboration in the chat, this problem can be solved by leveraging the useQuery key array.
Since it behaves like the dependency array in the useEffect for example, everything that defines the resulted data should be inserted into it. Instead of triggering refetch to update the data.
Here the key could look like this: ['getUpdatedPrice', item.id, ...Object.keys(bodyQuery.values)], which will trigger a new fetch if those values change and on initial render.
Related
I have a very basic app where I'm trying to fetch some data, and update the cache. For example purposes I tried to update the data to an empty array, but on the dev tools and the console logs I keep getting the old data
function App() {
const queryClient = new QueryClient();
const { isLoading, error, data } = useQuery('repoData', fetcher, {
onSuccess: (data) => {
queryClient.setQueryData('repoData', () => []);
},
});
console.log('data', data);
return (
<div className="App">
<Home />
</div>
);
}
what would be the correct way to update the cache?
Why would you want to update the cache of the same item you have just successfully fetched? React-Query will put the result of the fetcher into the data field returned from useQuery - you don’t need to do anything in onSuccess for that
That's is an example from official documentation.
const queryClient = useQueryClient()
const mutation = useMutation(editTodo, {
onSuccess: data => {
queryClient.setQueryData(['todo', { id: 5 }], data)
}
})
mutation.mutate({
id: 5,
name: 'Do the laundry',
})
// The query below will be updated with the response from the
// successful mutation
const { status, data, error } = useQuery(['todo', { id: 5 }], fetchTodoById)
https://react-query.tanstack.com/guides/updates-from-mutation-responses
The data resolved from your fetcher function will populate the cache from react-query with your chosen query key.
This data is available when destructuring the useQuery hook or is available with the onSuccess callback.
It can be usefull to manually update the data as shown here:
https://stackoverflow.com/a/68949327/1934484
// fetcher function
function getProducts() {
// http call
const { data } = await http.get<{ products: ProductT[] }>(/products);
return data.products;
}
// data returned will be an array of products
const { data } = useQuery('products', getProducts, {
onSuccess: (data) => {
// data returned will be an array of products
},
});
I have a component in react that fetches a story that a user wrote from the backend. I then store the json in the storyState and when I log it, the needed properties like "title" are in there, but when I try to access the property title, I get this error:
TypeError: Cannot read properties of undefined (reading 'title')
Here is the code:
import { useState, useEffect } from 'react';
import styled from 'styled-components';
export const StoryPage = () => {
const [storyState, setStoryState] = useState({
story: {},
isLoaded: false,
error: null,
});
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const id = urlParams.get('storyid');
useEffect(() => {
fetch('http://localhost:5000//getstory?storyid=' + id)
.then((response) => {
console.log('response is', response);
if (response.status !== '200') {
let err = Error;
err.message = 'Invalid response code: ' + response.status;
setStoryState({ error: err });
}
return response.json();
})
.then((json) => {
console.log('json is', json);
setStoryState({
story: json.story,
isLoaded: true,
});
});
}, []);
console.log('storystate.story', storyState.story);
return (
<>
<h1>test</h1>
<h1>{storyState.story.title}</h1>
</>
);
};
export default StoryPage;
I checked StackOverflow and tried using JSON.parse, which didn't work unfortunately.
When I do < h1 >{storyState.story}</ h1 > I get the whole object in string form with all the properties as expected.
Here is my console:
Issue is at this line -
if (response.status !== "200") {
}
It should be 200 instead of "200". You can check the console.log("response is", response);, it's numeric 200 not string.
Note: Always use the below construct to setState for objects -
{ ...previousState, { newkey: newvalue } }
The above uses the previous values of object and replaces with new values.
In the very first render, data is not available, hence you have to render
<h1>{storyState.story.title}</h1> only when the data is fetched and available so add an if statement in the render or you can use optional chaining too
<h1>{storyState?.story?.title}</h1>
There could be a delay in fetching data from server, you can add a loader in it which checks if there is a value in storySTate then render the data or add another useEffect function which logs file whenever there is a change in storyState hook eg:
useEffect(() => {
console.log(storyState.story)
}, [storyState])
I need to display some data in my component, unfortunately the first call to my API returns just part of the information I want to display, plus some IDs. I need another call on those IDs to retrieve other meaningful data. The first call is wrapped in a useEffect() React.js function:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
'/myapi/' + auth.authState.id
);
setData(data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
And returns an array of objects, each object representing an appointment for a given Employee, as follows:
[
{
"appointmentID": 1,
"employeeID": 1,
"customerID": 1,
"appointmentTime": "11:30",
"confirmed": true
},
... many more appointments
]
Now I would like to retrieve information about the customer as well, like name, telephone number etc. I tried setting up another method like getData() that would return the piece of information I needed as I looped through the various appointment to display them as rows of a table, but I learned the hard way that functions called in the render methods should not have any side-effects. What is the best approach to make another API call, replacing each "customerID" with an object that stores the ID of the customer + other data?
[Below the approach I've tried, returns an [Object Promise]]
const AppointmentElements = () => {
//Loop through each Appointment to create a single row
var output = Object.values(data).map((i) =>
<Appointment
key={i['appointmentID'].toString()}
employee={i["employeeID"]} //returned a [Object premise]
customer={getEmployeeData((i['doctorID']))} //return a [Object Promise]
time={index['appointmentTime']}
confirmed = {i['confirmed']}
/>
);
return output;
};
As you yourself mentioned functions called in the render methods should not have any side-effects, you shouldn't be calling the getEmployeeData function inside render.
What you can do is, inside the same useEffect and same getData where you are calling the first api, call the second api as well, nested within the first api call and put the complete data in a state variable. Then inside the render method, loop through this complete data instead of the data just from the first api.
Let me know if you need help in calling the second api in getData, I would help you with the code.
Update (added the code)
Your useEffect should look something like:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get('/myapi/' + auth.authState.id);
const updatedData = data.map(value => {
const { data } = await fetchContext.authAxios.get('/mySecondApi/?customerId=' + value.customerID);
// please make necessary changes to the api call
return {
...value, // de-structuring
customerID: data
// as you asked customer data should replace the customerID field
}
}
);
setData(updatedData); // this data would contain the other details of customer in it's customerID field, along with all other fields returned by your first api call
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
This is assuming that you have an api which accepts only one customer ID at a time.
If you have a better api which accepts a list of customer IDs, then the above code can be modified to:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get('/myapi/' + auth.authState.id);
const customerIdList = data.map(value => value.customerID);
// this fetches list of all customer details in one go
const customersDetails = (await fetchContext.authAxios.post('/mySecondApi/', {customerIdList})).data;
// please make necessary changes to the api call
const updatedData = data.map(value => {
// filtering the particular customer's detail and updating the data from first api call
const customerDetails = customersDetails.filter(c => c.customerID === value.customerID)[0];
return {
...value, // de-structuring
customerID: customerDetails
// as you asked customer data should replace the customerID field
}
}
);
setData(updatedData); // this data would contain the other details of customer in it's customerID field, along with all other fields returned by your first api call
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
This will reduce the number of network calls and generally preferred way, if your api supports this.
I am trying to push a user's choice as a string to their array of choices and return the updated document.
The route and function work successfully however it returns the User with an empty choice array. I believe the problem lies somewhere in the controller function but I cannot figure it out.
Any help is greatly appreciated!
To help, here is a screenshot of my console where you can see an empty choice array being returned.
Here is an image of my console.log
This is where I call the function
handleAnswerInput = (question) => {
let answerTextSelected = question.Text;
let answerTypeSelected = question.Type;
let usersName = this.state.user._id
this.setState({
count: this.state.count + 1
})
saveUserandScore(usersName, answerTextSelected)
.then(
this.loadQuestion(this.state.count)
)
console.log(answerTextSelected)
console.log(answerTypeSelected)
};
This is the controller function (updated from suggestions)
const saveUserAndTheirScore = (req, res) => {
let filter = { _id: req.params.id }
// let update = { choices: req.params.answer] }
console.log(req.params.id)
console.log(req.params.answer)
User.update(
{ filter },
{
$push: { choices: req.params.answer }
},
{
returnOriginal: false,
},
)
.then(dbData => res.json(dbData))
.catch(err => {
console.log(err);
res.json(err);
});
};
here is the axios call
export const saveUserandScore = (id, answer) => {
return axios.post(`/api/user/${id}/${answer}`);
};
you need to change user schema, in that you might have defined choices type as string. It must be an array.
findOneAndUpdate(filter, update, options, callback) has a returnOriginal option, if set to true (which is the default), it will return the document BEFORE the update. In your case, you might want to set it to false [1].
Unfortunately, the respective option for mongoose is named new [2].
[1] https://mongodb.github.io/node-mongodb-native/3.4/api/Collection.html#findOneAndUpdate
[2] https://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate
I wanna store some objects inside an array if the array doesn't already contain some object with the same id. anyways, everything works fine til i start adding more than one object at a time.
Here is the related code using Vuex:
// filter function to check if element is already included
function checkForDuplicate(val) {
for( let sessionItem of state.sessionExercises ) {
return sessionItem._id.includes(val._id);
}
};
// related array from vuex state.js
sessionExercises: [],
// vuex mutation to store exercises to session exercises
storeSessionExercises: (state, payload) => {
// Pre filtering exercises and prevent duplicated content
if( checkForDuplicate(payload) === true ) {
console.log("Exercise ist bereits für session registriert!");
} else {
state.sessionExercises.push(payload);
}
},
// Related vuex action
storeSessionExercises: ({ commit }, payload) => {
commit("storeSessionExercises", payload)
},
As I wrote before everything works fine as long i ad a single object, checkForDuplicate() will find duplicated objects and deny a push to the array.
now there is a case in which I wanna push a bundle of objects to the array, which i am doing through an database request, looping through the output, extracting the objects and pushing them through the same function as I do with the single objects:
// get user related exercises from database + clear vuex storage + push db-data into vuex storage
addSessionWorkout: ({ commit, dispatch }, payload) => {
axios.post(payload.apiURL + "/exercises/workout", payload.data, { headers: { Authorization: "Bearer " + payload.token } })
.then((result) => {
// loop through output array and
for( let exercise of result.data.exercises ) {
// push (unshift) new exercise creation to userExercises array of vuex storage
dispatch("storeSessionExercises", exercise)
};
})
.catch((error) => {
console.error(error)
});
},
The push does also work as it should, the "filter function" on the other hand doesn't do its job. It will filter the first object and deny to push it to the array, but if there is a second one that one will be pushed to the array even inf the same object (same Id) is already included, what am I not seeing here!? makes me nuts! :D
I understand it like the loop will put each object through the checkForDuplicate() and look if there is an duplicate it should output true, so the object doesn't get pushed into the array. If anybody sees what I currently don't just let me know.
the mistake is your filter function. you want to loop over your sessionExercises and only return true if any of them matches. However, at the moment you return the result of the very first check. Your loop will always only run one single time.
Option 1: only return if matched
function checkForDuplicate(val) {
for( let sessionItem of state.sessionExercises ) {
if (sessionItem._id.includes(val._id)) {
return true;
}
}
return false;
};
Option 2: use es6 filter
storeSessionExercises: (state, payload) => {
var exercises = state.sessionExercises.filter(ex => (ex._id.includes(payload._id)));
if(exercises.length) {
console.log("Exercise ist bereits für session registriert!");
} else {
state.sessionExercises.push(payload);
}
}
I would change the addSessionWorkout action, I would create a new exercises array with the old and new entries and then update the state.
// related array from vuex state.js
sessionExercises: [],
// vuex mutation to store exercises to session exercises
storeSessionExercises: (state, payload) => {
state.sessionExercises = payload;
},
// Related vuex action
storeSessionExercises: ({ commit }, payload) => {
commit("storeSessionExercises", payload)
},
addSessionWorkout: async({
commit,
dispatch,
state
}, payload) => {
const result = await axios.post(payload.apiURL + "/exercises/workout", payload.data, {
headers: {
Authorization: "Bearer " + payload.token
}
})
try {
const newExercices = result.data.exercises.reduce((acc, nextItem) => {
const foundExcercise = acc.find(session => session.id === nextItem.id)
if (!foundExcercise) {
return [...acc, nextItem]
}
return acc
}, state.sessionExercises)
dispatch("storeSessionExercises", foundExcercise)
} catch (e) {
console.error(error)
}
},