React-query: How to avoid triggering function call if query is empty? - reactjs

I am using react-query to call an API. The call works well and is performed each time a query value is updated in an input field.
Unfortunately, it also triggers an API call even when the query is empty.
For example, when the user loads the app, the input (and hence query) will be blank.
How to trigger API calls only when there is a query?
Code
// API call
export async function myQuery(query) {
try {
const res = await ax.get("myapiurl", {
params: { query },
});
return res.data;
} catch {
return null;
}
}
// react-query
const { status, data } = useQuery(
["myquery", { query }],
() => myQuery(query)
);

There is an enabled flag in react-query for this exact use case.
Usage example
const { status, data } = useQuery(
["myquery", { query }],
() => myQuery(query).
{ enabled: !!query }
);
Docs for reference

You can achieve that with a simple if sentence:
// apicall
export async function myQuery(query) {
try {
const res = await ax.get("myapiurl", {
params: { query },
});
return res.data;
} catch {
return null;
}
}
// react-query
const { status, data } = useQuery(
["myquery", { query }],
() => {
if (query) {
return myQuery(query)
}
);

Related

Apollo Client Canceling Requests when more than one hook is used

I have a hook (useDashboardData) that calls another hook (useItems) that's just a wrapper for two Apollo client queries.
inside the first hook useDashboardData, i'm also calling another hook useOtherItems that also calls another Apollo client query.
export const useDashboardData = () => {
const { item, isItemsLoading } = useItems();
const { list, isOtherItemsLoading } = useOtherItems();
const dashboardData = {
items: {
itemsLoading: isItemsLoading,
itemsData: item,
},
otherItems: {
otherItemsLoading: isOtherItemsLoading,
otherItemsData: list,
},
};
return {
dashboardProps: {
dashboardData: dashboardData,
},
};
};
useItems.tsx
export const useItems = () => {
const { user } = useAuthorization();
const {
data: itemData,
loading: items Loading,
} = useCustomApolloGetItemsQuery({
skip: !user.id,
variables: { user.id },
});
const {
data: moreItemData,
loading: moreItemsLoading,
} = useAnotherApolloGetItemsQuery({
skip: !user.id,
variables: { user.id },
});
const combinedItems = combineItemData(itemData, moreItemData);
return { combinedItems, ItemsLoading };
useOtherItems.tsx
export const useOtherItems = () => {
const { user } = useAuthorization();
const { data: list, loading: isOtherItemsLoading } = useGetInvoiceListQuery({
skip: !user.id,
variables: {
userId: user.id,
},
});
return { list, isOtherItemsLoading };
For some reason, anytime I introduce the second hook, the previous requests get canceled. which one is arbitrary but it's consistently canceled.
I'm pretty sure it's due to the first hook request resolving earlier and causing a re-render before the request in the second hook is resolved.
I need to figure out the right pattern to deal with this.
**note I have made sure the Apollo Client is only instantiated once so it's not that.

Apollo client fetchMore does not update variables

I have used a query in this manner:
const { data, refetch, loading, fetchMore } = useQuery<{ personComments: PersonCommentConnection }>(
PERSON_GET_COMMENT,
{
fetchPolicy,
variables,
skip: !isBrowser,
onCompleted: () => setInitializedQuery(true),
}
)
and a fetch more function
const fetchMorePersonComments = async () => {
setFetchingMore(true)
await fetchMore({
variables: {
after: data?.personComments.pageInfo.endCursor,
},
updateQuery: (
previousEntries,
{ fetchMoreResult, variables }
): { personComments: PersonCommentConnection } => {
if (!fetchMoreResult) {
return previousEntries
}
if (fetchMoreResult) {
fetchMoreResult.personComments.edges = [
...previousEntries.personComments.edges,
...fetchMoreResult.personComments.edges,
]
}
return fetchMoreResult
},
})
setFetchingMore(false)
}
I used infinite scrolling to call fetchmore comments but when it's been called It only refetches the first query instead of adding the after query.
Note I've successfully created queries similar to this one but only fails for this
Do you have any ideas why during in the fetchmore query it does not update the query variables.

React RTK query Mutation can return value?

Is is possible to get the response of endpoint in React RTK Query Mutation .
I have a one mutation which insert into DB and I need to get the ID which inserted. in my api :
addRecord: build.mutation({
query(data) {
return {
url: base + 'myControler/SaveDataAsync',
method: 'post',
data: data,
}
},
}),
and in my component after import my hook I call it like
const [addRecord] = useAddRecordMutation();
and then in my submit function is use it like
const handleSubmitCustom = async (values) => {
await addRecord(values);
}
which I need the return value of await addRecord(values);
You can just do
const handleSubmitCustom = async (values) => {
try {
const returned = await addRecord(values).unwrap();
} catch (error) {
// you can handle errors here if you want to
}
}

Async/await not working in a for-of loop defined in createAsyncThunk

I'm having trouble trying to get an async await to work inside a for loop when using createAsyncThunk. I expected that dispatch(performRunAllCells()) will call the API updateBrowser() synchronously for each cell in the editor.cells array in order. Instead, the dispatch resulted in updateBrowser() being called asynchronously all at once. What is happening here?
export const performRunAllCells = createAsyncThunk(
'editor/runAllCells',
async (_, { dispatch, getState, rejectWithValue }) => {
const { notebook: { selectedDataset } } = getState() as {notebook: {selectedDataset: string}};
const { editor } = getState() as {editor: EditorState};
try {
let results: DataEntity | undefined;
for (const cell of editor.cells) {
dispatch(setCellStatus({ id: cell.id, execStatus: '*' }));
results = await updateBrowser(selectedDataset, cell.editorContent);
dispatch(setCellStatus({ id: cell.id }));
}
return results;
} catch (e) {
return rejectWithValue(e.response.data);
}
},
);
Edit
Currently I'm testing updateBrowser() with a setTimeout:
export async function updateBrowser(selectedDataset: string, editorContent: string): Promise<DataEntity> {
return new Promise((resolve) => {
setTimeout(() => {
console.log('test');
resolve({
raw: editorContent, html: 'Test', console: 'Test',
});
}, 3000);
});
}
I was able to know if it's synchronous/asynchronous through the console log above. Currently, it is printing multiple "test" at once.
Nevermind. I made a mistake somewhere else and the code wasn't actually being called. It is working now after fixing it.

How can I ensure that the Next.js router.query is not undefined?

I'm using next.js and the import { useRouter } from 'next/router'.
However on initial load, router.query.id is undefined. It quickly fills in, but that initial load is a killer.
I'm trying to figure out how to do it, and tried:
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
// const res = await fetch(`https://.../posts/${params.id}`)
// const post = await res.json()
console.log(params)
// Pass post data to the page via props
return { props: { params } }
}
but this returns an error:
Error: getStaticPaths is required for dynamic SSG pages and is missing for '/app/d/[id]'.
I can't use getStaticPaths, since [id] is variable and can be any number of things.
So what's the best way to handle this?
I would do smt like this(without staticProps):
function Page(props) {
const router = useRouter();
const { query = {} } = router || {};
const { id = 0 } = query || {};
useEffect(()=> {
if(id) {
(async ()=> {
const res = await fetch(`https://.../posts/${id}`)
const post = await res.json();
})();
}
}, [id]);
}
And this is what official doc. says:
// You also have to define a Post component in the same file (pages/posts/[id].js)
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
return <h1>Posts are here</h1>;
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return { props: { post } }
}
UPDATE:
After a bit research, have figure out this solution with staticProps:
export default function Post({ post }) {
return <h1>Post is here</h1>;
}
export async function getStaticPaths() {
return {
paths: [
{ params: { id: '*' } }
],
fallback: true
};
}
export async function getStaticProps(context) {
const res = await fetch(`https://api.icndb.com/jokes/random/${context.params.id}`);
const post = await res.json()
return { props: { post } }
}

Resources