My useQuery hook is refetching everytime its called. I thought it is suppose to hand back the cache? - react-query

I'm a little confused here. I thought react-query, when using useQuery will hand back 'cache' n subsequent calls to the same "useQuery". But everytime I call it it, it refetches and makes the network call.
Is this the "proper way" to do this? I figured it would just auto hand me the "cache" versions. I tried extending staleTime and cacheTime, neither worked. Always made a network call. I also tried initialData with the cache there.. didn't work.
SO, I am doing the following, but seems dirty.
Here is the what I have for the hook:
export default function useProducts ({
queryKey="someDefaultKey", id
}){
const queryClient = useQueryClient();
return useQuery(
[queryKey, id],
async () => {
const cachedData = await queryClient.getQueryData([queryKey, id]);
if (cachedData) return cachedData;
return await products.getOne({ id })
}, {
enabled: !!id
}
);
}
This is initiated like so:
const { refetch, data } = useProducts(
{
id
}
}
);
I call "refetch" with an onclick in two diff locations.. I'd assume after I retrieve the data.. then subsequent clicks will hand back cache?

I’m afraid there are multiple misconceptions here:
react query operates on stale-while-revalidate, so it will give you data from the cache and then refetch in the background. You can customize this behavior by setting staleTime, which will tell the library how long the data can be considered fresh. No background updates will happen.
when you call refetch, it will refetch. It’s an imperative action. If you don’t want it, don’t call refetch.
you don’t need to manually read from the cache in the queryFn - the library will do that for you.

Related

Lazy Loading Rows to (MUI) DataGridPro (example help)

Regarding example: https://mui.com/x/react-data-grid/row-updates/#lazy-loading
Im confused on where the example data ends and where the necessary params begin. Essentially, what in this can I comment out/remove/replace to hook my database up?
Example data is being loaded into the gird; I assume through:
import { createFakeServer, loadServerRows } from '#mui/x-data-grid-generator';
Is this only for the example or also needed for the LazyLoading to work? In other words, is a "fake server" being created that MUI is referencing or...?
I've written code to query my database and return rows, I have it setup with variables to accept a startAt and limit which I can link with the MUI datagrid, Im just not sure where to.
For example in the below, are filterModel, sortModel, columnsWithDefaultColDef, etc. needed? is this just for querying example data? I see that "dataServerSide" at the bottom, so am I leaving everything and only updating dataServerSide elsewhere or?
const fetchRow = React.useCallback(
async (params) => {
const serverRows = await loadServerRows(
dataServerSide,
{
filterModel: params.filterModel,
sortModel: params.sortModel,
},
{
minDelay: 300,
maxDelay: 800,
useCursorPagination: false,
},
columnsWithDefaultColDef,
);
return {
slice: serverRows.returnedRows.slice(
params.firstRowToRender,
params.lastRowToRender,
),
total: serverRows.returnedRows.length,
};
},
[dataServerSide],
);
Hope someone can help 🤓
I've tried creating external state management to inject/update rows but it doesn't use the lazy loading functionality, its jerry rigged to fire at onRowsScrollEnd. Id like to use the lazyLoading feature, despite it being experiential.

Where to call reset method for RTKQ useMutation()?

Since RTK 1.7.0, mutation result object has a reset() method to unsubscribe from the state update of a mutation from another component:
const [mutate, { isLoading, reset }] = useMutation({ fixedCacheKey });
I use it to get isLoading state from another component's mutation. But I don't know where to call the cleanup reset():
In the click handler:
await mutate()
reset()
Or in the cleanup
useEffect(() => () => reset(), [reset])
If it's this case, then do I need to do this for both (original and subscriber) components? And this is weird: the identity of reset function changes after state update, so the isLoading is cleared right after it changes.
I think the docs about this function is not very clear. Hope someone can improve it.
I'm using React 17.0.2, RTK 1.7.1.
Mutations never share state between usage in different components in the fist place - unless you force that by using fixedCacheKey. Are you maybe trying to solve a non-problem here?

How to get data from an API only once (on app creation, outside component or view) in Vue3 SPA, with Pinia store

Is it possible and is it a good practice to avoid fetching data from an API every time the router view is loaded or the component is Mounted?
The thing is that some data rarely changes (like a dropdown options list, imagine allowed animal picks for my app) and it's logical not to send a request every time to a server, instead on app creation would be sufficient.
Tried in App.vue, is that a common thing?
IN APP.vue
import { computed, onMounted, onUpdated, ref } from 'vue';
onMounted(()=>{
axios.get('/data')....
.then((res)=>{
store.property = res.data
...
})
})
I think having it on mount in the App.vue component is acceptable since the App component would not be remounted.
The ideal setup, however, depends on some other parameters like size of application and size of team that's maintaining it. In a large applications you might want to organize things in amore structured and consistent way so you and other folks working on the code know where to find things.
You could consider moving the API call into the pinia action.
store.loadMyData()
// instead of
axios.get('/data')
.then((res)=>{
store.property = res.data;
})
That way you have fewer lines of code in the component. Having "lean" components and moving "business logic" out of components usually makes for better organization which makes it easier to maintain.
Within the action, you can track the state of the API
const STATES = {
INIT: 0,
DONE: 1,
WIP: 2,
ERROR: 3
}
export const useMyApiStore = defineStore('myapi', {
state: () => ({
faves: [],
favesState: STATES.INIT
}),
actions: {
loadMyData() {
this.store.favesState = STATES.WIP;
axios.get('/data')
.then((res) => {
this.store.property = res.data;
this.store.favesState = STATES.DONE;
})
.catch((e) => {
this.store.favesState = STATES.ERROR;
})
},
},
getters: {
isLoaded(){
return this.store.favesState === STATES.DONE;
}
isLoading(){
return this.store.favesState === STATES.WIP;
}
}
})
This is, obviously, more verbose, but allows for the components to be smaller and contain less logic. Then, for example, in your component you can use the getter isLoading to display a loading indicator, or use isLoaded inside another component to determine whether to show it.
Yes, this is a oft used way to load some data into the Vue App.
You could also load data before the Mounting in beforeMount() or created() Lifecycle Hooks (see Vue Lifecycle Diagram) to prevent unnecessary HTML updates.

LitElement with data from Firestore

I've been trying to dynamically insert data from Firestore into my component.
Currently, I'm using the firstUpdated() lifecycle. My code works but it fell like there's a better way of doing this.
This is my current component.
static get properties() {
return {
firebaseData: {type:Object},
}
}
constructor() {
super()
this.firebaseData = {}
}
firstUpdated() {
firestore.doc(`...`).get()
.then(doc => {this.firebaseData = doc.data()})
})
.catch(err => console.error(err))
}
render() {
return html `${firebaseData.title}`
}
I was hope someone with more experience would be open to sharing their knowledge. Thanks in advance!
firstUpdated should be used when you need to interact with shadow DOM elements inside your web component, as they aren't created until then. It's the earliest moment when you can be sure your component DOM exists.
I would prefer to do the firebase call earlier, even in the constructor.
The idea is, your firebase call isn't dependent of the rendering, so you could directly do it at the earliest moment, and as in the callback of the function you update the firebaseData property, a new rendering cycle will be done then.

How to observe added events in Meteor without firing on initialization?

There is a lot of information on this topic out there, but I can't seem to get it working for myself. I am using toastr to display notifications of events to the user in the top right hand corner of my app. I need to add an observer for the 'added' event to a collection, and create the toastr notification when an item is added. The problem is that the observer fires when the collection is initialized. I've tried about a half dozen different ways trying to check of the collection is ready() before I allow the observer code to continue through and show notifications, but I can't get it working consistently, especially when changing pages. Here is some sample code:
MainController = RouteController.extend({
before: [
function() {
deviceEventsInitializing = true;
var alerts = this.subscribe("alerts", Meteor.user()._id);
if (alerts.ready()) {
deviceEventsInitializing = false;
}
Alerts.find().observeChanges({
added: function(id, doc) {
if (deviceEventsInitializing || deviceEventsInitializing == undefined) {
return;
}
doToastrStuff();
}
});
this.next();
}
],
});
This is just my latest attempt. The flow goes like this:
1.) Subscription happens, all the items in the collection hit the observer but deviceEventsInitializing is true so it does nothing.
2.) alerts.ready() fires and deviceEventsInitilizing is set to false.
3.) The added trigger fires again for all the events in the collection, causing toastr to be called for every item.
All I'm interested in is the following:
1.) Some trigger or event where I can set a variable that says the subscription is reloading the collection.
2.) Some trigger or event that tells me that this reloading of the collection is complete so I can set a variable indicating that.
I think you're on the right track, but trying to manage reactivity like this using solely IronRouter can be a nightmare. I've tried and failed before.
Instead, leverage Mongo to limit your reactivity to only alerts you care about. Let's imagine your alerts database looks something like:
{
_id: 1,
hasNotified: false,
...
}
Now, Mongo is deciding what is new vs. not new instead of trying to determine state based on IronRouter timing. In fact, because in Meteor any alerts cursor is natively reactive, you don't even need to observeChanges:
MainController = RouteController.extend({
waitOn: function() {
return this.subscribe("alerts", Meteor.userId());
},
data: function() {
var newAlerts = Alerts.find({hasNotified: false}).forEach(function(doc) {
doToastrStuff();
Alerts.update({_id: doc._id}, {$set: {hasNotified: true}});
});
}
})
With this kind architecture, navigating to other routes, reloading the page, etc. will not re-fire any of your alerts because Mongo stores your alert state.