How to import type definition from react-query to set type of options for useQuery hook? - react-query

I'm adding react-query to my automations pipe and need generating wrapper around the useQuery for various API calls. I want to expose all options of useQuery to developer using this wrapper. How to correctly define options type to get all the benefits of ts for those?
:any works but not giving autocomplete/checks then:
const usePosts = ({ page, perPage, workspaceId }: payload, ___options?: any) => {
If I add :UseQueryOptions, just to see the error of mismatch, error message is quite big and has a lot inside. Would love to find way to import that type definition from react-query and reuse it.

Just found it, sometimes all needed is to write a question 😅 Maybe someone else will benefit from this:
import { useQuery, QueryOptions } from "#tanstack/react-query";
import sdk from "../sdks/demo";
type payload = {
page?: number;
perPage?: number;
workspaceId: string;
};
const usePosts = (
{ page, perPage, workspaceId }: payload,
___options?: QueryOptions
) => {
let key = `posts-list-${workspaceId}-${page}-${perPage}`;
return useQuery(
[key],
() =>
sdk.postsList({
workspaceId,
page,
perPage,
}),
___options
);
};
export default usePosts;

Related

Google analytics user property react-ga4

I'm using react-ga4.
I wonder how i could send user properties using this library and set it up in the google analytics panel as I think that i'm doing something wrong.
This is how i initialize ReactGA4
ReactGA.initialize(
[
{
trackingId: id,
gaOptions: {
role: userRole,
}
},
]
)
any suggestion?
It depends what user properties that you want to send. You can send custom user properties that not reserved by Google.
For example, I want to send account_verified with boolean value and name with string value in user property.
You can use ReactGA.gtag(...args) in this library, and then you can use it directly or put it into utils/analytics.js and wrap it to export function with parameter, so you can use it whenever it needs.
import ReactGA from "react-ga4";
ReactGA.gtag("set", "user_properties", {
account_verified: true,
});
ReactGA.gtag("set", "user_properties", {
name: "John",
});
or
import ReactGA from "react-ga4";
export const setAccountProperty = (value: boolean) => {
ReactGA.gtag("set", "user_properties", {
account_verified: value,
});
};
export const setNameProperty = (value: string) => {
ReactGA.gtag("set", "user_properties", {
name: value,
});
};
After that, you can check in your Google Analytics in DebugView directly to ensure your user properties works well.

Suspense returns data too fast in #tanstack/react-query v4

I am upgrading a React app from react-query v3 to #tanstack/react-query v4.
Almost everything works, but I'm having a problem with Suspense.
I have a react component:
const WrapperPageEdit: React.FC<MyProps> = ({
pageUuid,
redirect,
}: MyProps) => {
const FormPage = React.lazy(() => import('./FormPage'));
const { data } = usePageView(pageUuid);
if (data?.[0]) {
const pageObjectToEdit= data[0];
const content = pageObjectToEdit.myStuff.content;
return (
<Suspense
fallback={<Trans id="loading.editor">Loading the editor...</Trans>}
>
<FormPage
id={uuid}
content={content}
redirect={redirect}
/>
</Suspense>
);
}
return <p>No data.</p>;
};
And here's my query:
export function usePageView(
uuid: string,
): UseQueryResult<DrupalPage[], Error> {
return useQuery<DrupalPage[], Error>(
queryKeyUsePageView(uuid),
async () => {
return fetchAnon(getPageByPageUuid(uuid));
},
{
cacheTime: YEAR_MILLISECONDS,
staleTime: YEAR_MILLISECONDS,
onSuccess: (data) => {
if (data?.[0]) {
data.map((element) => processResult(element));
}
},
},
);
}
This works in v3 but fails in v4 with the following error:
TypeError: Cannot read properties of undefined (reading 'content')
The reason the property is undefined is because that property is set by the processing in onSuccess (data.map).
The issue appears to be that in v4, the component WrapperPageEdit is refreshed before onSuccess in the usePageView query has finished processing, whereas in v3, the component WrapperPageEdit is not refreshed until the onSuccess data.map is complete.
How can I correctly fix this? I can write some additional code to try to check whether the onSuccess data.map is complete, but since react-query handled this automatically in v3, I'd like to rewrite my code in v4 so that it is the same.
The problem is likely that you are mutating the data in onSuccess. Directly modifying data in callbacks is not a good idea. Instead, do your transformation for example directly in the queryFn:
async () => {
const data = fetchAnon(getPageByPageUuid(uuid));
if (data?.[0]) {
data.map((element) => processResult(element));
}
return data
},
other good places to do data transformation is e.g. the select option, but it should always happen in an immutable way, because otherwise, you are overwriting the cached data inadvertently. React prefers updates to be immutable.

replaceReducer on #reduxjs/toolkit

Although I'm newbie of redux as well as of RTK, I'm just starting redux project with RTK as if you start with Spring Boot not Spring these days.
I want to dynamically inject reducers on demand on redux toolkit(RTK). I realized that I need to keep track of current reducers to make it. I expected my store from RTK would have a reference to them, but unfortunately it seems it doesn't have such a property.
While I found this module that seems to do the job, but it seems I have to go back to the days before RTK was created to make it work.
import {createStore, createReducer, Slice, Reducer, AnyAction, combineReducers} from "#reduxjs/toolkit";
const Store = createStore<any, any, any, any>(createReducer({}, () => {}));
const reducers: {
[key: string]: Reducer<any, AnyAction>;
} = {};
export const injectReducer = (slice: Slice) => {
reducers[slice.name] = slice.reducer;
Store.replaceReducer(combineReducers(reducers));
};
Even more (maybe I just don't know the way) type definitions will go insane.
Is there any way to make this?
I had the same issue, and finally managed to fix it with a few lines;
First, I created the store:
const staticReducers = {
counter: counterReducerSlice,
};
export const store = configureStore({
reducer: staticReducers,
middleware: getDefaultMiddleware => [logger, ...getDefaultMiddleware()],
enhancers: compose([monitorReducerEnhancer]),
});
then making the async reducers directory:
store.asyncReducers = {};
store.injectReducer = (key, asyncReducer) => {
store.asyncReducers[key] = asyncReducer;
store.replaceReducer(createReducer(store.asyncReducers));
};
function createReducer(asyncReducers) {
return combineReducers({
...staticReducers,
...asyncReducers
});
}
finally, in my code, every time I want to add a new reducer to my store, I call store.injectReducer:
store.injectReducer('reducerKey', theReducerSlice);

Querystring support in ion-router (with StencilJs)

I have a StencilJS app with Ionic web components using the following routing setup:
<ion-router useHash={false}>
<ion-route url="/home" component="page-home"/>
<ion-route url="/login" component="page-login"/>
<ion-route url="/main" component="page-main">
</ion-router>
When I receive a 401 error from the api, I would like to redirect to the login page, with the current url as a redirect querystring parameter (in order to redirect the user back to where he came from), like so:
const navCtrl: HTMLIonRouterElement = document.querySelector("ion-router");
fetch('https://someapi.com/users/1').then(
res => navCtrl.push('/main'),
err => navCtrl.push(`/login?redirect=${window.location.pathname}`)
);
When I do this an I receive an error from the api, and I want to navigate to the login page with the current route as querystring parameter, Stencil throws the following error: [ion-router] the path does not match any route.
How can I use the querystring with the ion-router?
IMO it's currently a bug in Ionic (see https://github.com/ionic-team/ionic/issues/19707).
To work around it for the moment, you could probably use
navCtrl.push('/login');
history.replaceState(undefined, '', `?redirect=${window.location.pathname}`);
Thanks for the nudge in the right direction! The code you provided did not change the url, because navCtr.push returns a promise. You have to wait for it to finish before you can change the state. I ended up with this working workaround:
const navCtrl: HTMLIonRouterElement = document.querySelector("ion-router");
fetch('https://someapi.com/users/1').then(
res => navCtrl.push('/main'),
err => {
navCtrl
.push(`/login`)
.then(() => history.replaceState(undefined, '', `?redirect=${encodeURIComponent(window.location.pathname)}`))
}
);
It's strange that such widely used frameworks like Ionic and Stencil don't support common querystrings. I hope they will fix this issue soon.
i build a native function to get every parameter from the querystring,
export const RouterGetUriParam = (name) => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
if (urlParams.has(name)) {
return urlParams.get(name);
} else {
return null;
}
};
you can use it in stencil like this:
#Prop() withMore: boolean = (RouterGetUriParam("withMore") == 'true')
the urlparam looks like ?withMore=true

Ember - Sideloading data within RESTAPISerializer

I am trying to sideload data with RESTAPISerializer, but there seems to be some problem.
The goal is to store articles with tags. The articles appear in the store just fine, and I can look at them with Ember inspector. But I can't seem to figure out how to store the tags, they never show up in the inspector.
I created a model called blog-tag to store the tags. They come in a specific format, which I can't change, and it looks like this:
tid:term
... and I am getting an array of them with each article.
The article model includes a hasMany relationship with blog-tags. I am trying to pass them in through the serializer, and have been using various formats (JSON, javascript arrays, etc.) I wrote a custom serializer for blog tags as well, but it doesn't seem to do much.
Can someone explain what I am missing?
The article model:
import DS from 'ember-data';
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
export default Model.extend({
title: attr(),
blogTags: hasMany('blog-tag', { async: true })
});
The blog-tag model:
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
export default Model.extend({
tid: attr('number'),
term: attr('string'),
});
The article serializer:
import RESTAPISerializer from 'ember-data/serializers/rest';
export default RESTAPISerializer.extend({
normalizeResponse: function(store, primaryModelClass, payload, id, requestType) {
var normalizedPayload = {articles: []};
payload.forEach(function(item) {
var tags = item.tags.split("|"), blogTags = [];
// storing blog tags in an array, have also done it as a
// JSON structure
var blogTags = tags.map((tag) => {
var item = tag.split(":"),
vals = {};
vals.tid = item[0];
vals.term = item[1];
return vals;
});
var article = {
id: item.nid,
title: item.title,
blogTags: blogTags,
};
normalizedPayload.articles.push(article);
});
return this._super(store, primaryModelClass, normalizedPayload, id, requestType);
},
});
Solved my own problem. Answering the question for the benefit of anyone else running into the same issue.
What I was trying to do is embed the tags from the articles serializer. This mean I needed to use the EmbeddedRecordsMixin.
http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html
It was actually pretty simple to do this. Made the following changes to the serializer described above, the code worked as-is with these additions.
// app/serializers/articles.js
import RESTSerializer from 'ember-data/serializers/rest';
import EmbeddedRecordsMixin from 'ember-data/serializers/embedded-records-mixin';
export default RESTSerializer.extend(EmbeddedRecordsMixin, {
attrs: {
blogTags: { embedded: 'always' }
}
});