How to set loading true in graphql query.? - reactjs

I am using graphQl in this i want to set loading = true for 1 second to show loader after that it will reset by response how will i do that
i am using below code right now,
const loadData = graphql(initialData, {
options: ({ params: { Id }, authData: { userPermissions } }) => ({
variables: {
Id,
Settings: hasPermissions(userPermissions, [USER_PERMISSIONS.du]),
},
fetchPolicy: APOLLO_FETCH_POLICIES.NETWORK_ONLY,
errorPolicy: APOLLO_ERROR_POLICIES.ALL,
notifyOnNetworkStatusChange: true,
}),
// skip: props => props.loading = true,
props: ({ data }) => {
const { error, loading, refetch, networkStatus, buy, timeZones, manager } = data;
return {
error:error,
loading: networkStatus === 1 && !loading ? true : loading,
networkStatus,
onReload: refetch,
timeZones,
manager: get(manager, 'itUsers', []),
};
},
});
Any help is appreciated.

Well, you can use custom fetch. Something like this might work:
const customFetch = (url, {delay, ...opts}) => {
return Promise.all([
fetch(url, opts),
new Promise(resolve => setTimeout(resolve, delay || 0)),
]).then(([res, _]) => res)
}
const uploadLink = createUploadLink({
uri,
fetch: customFetch,
})
const client = new ApolloClient({
cache,
link: uploadLink,
})
//////////////////////////////////////////////
// Then you can add delay option via context
//////////////////////////////////////////////
const loadData = graphql(initialData, {
options: ({ params: { Id }, authData: { userPermissions } }) => ({
variables: {
Id,
Settings: hasPermissions(userPermissions, [USER_PERMISSIONS.du]),
},
fetchPolicy: APOLLO_FETCH_POLICIES.NETWORK_ONLY,
errorPolicy: APOLLO_ERROR_POLICIES.ALL,
notifyOnNetworkStatusChange: true,
///////////////////////////////////////////
// add context with delay
context: {
fetchOptions: {delay: 1000},
///////////////////////////////////////////
},
}),
// skip: props => props.loading = true,
props: ({ data }) => {
const { error, loading, refetch, networkStatus, buy, timeZones, manager } = data;
return {
error:error,
loading: networkStatus === 1 && !loading ? true : loading,
networkStatus,
onReload: refetch,
timeZones,
manager: get(manager, 'itUsers', []),
};
},
});
I have not tested it.

Related

RTK Query not Invalidating Data When App is Hosted, but works on Localhost

I am working on this app that is so data intensive. I have implemented RTK Query and my issue is with the invalidation of tags after mutation changes via API call. It works well on localhost where all the tags are invalidated as needed, but when I host the app, no invalidation happens even after an API call is successful and data has been changed on the server. Hard-refreshing the app doesn't help, until I have to clear the browser cache for the changes to reflect on the UI. I also notice that the network API calls are being fired, but updating the stale data on the cache does not take place. I will add here all the necessary code that may help to debug this issue.
store.js
import { configureStore } from "#reduxjs/toolkit";
import { apiSlice } from '../api/apiSlice';
import authReducer from "./auth/authSlice";
export const store = configureStore({
reducer: {
[apiSlice.reducerPath]: apiSlice.reducer,
auth: authReducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(apiSlice.middleware)
});
apiSlice.js
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react';
import { Mutex } from 'async-mutex';
import { logout, setCredentials } from '../features/auth/authSlice';
import { baseUrlDev, baseUrlPro } from './baseUrl';
const baseURL =
window.location.hostname.includes("dev") || window.location.hostname.includes("localhost")
? baseUrlDev
: baseUrlPro;
const mutex = new Mutex();
const baseQuery = fetchBaseQuery({
baseUrl: baseURL,
credentials: 'include',
timeout: 15000,
prepareHeaders: (headers, {getState}) => {
const token = getState().auth.token || JSON.parse(localStorage.getItem("authenticatedUser"))?.accessToken;
if (token) {
headers.set("Authorization", `Bearer ${token}`)
}
return headers;
}
});
const baseQueryWithReauth = async (args, api, extraOptions) => {
await mutex.waitForUnlock();
let result = await baseQuery(args, api, extraOptions)
if (result?.error?.originalStatus === 403) {
if (!mutex.isLocked()) {
const release = await mutex.acquire();
try {
console.log('sending refresh token');
// send refresh token to get a new access token
const refreshResult = await baseQuery('/auth/refresh', api, extraOptions);
// console.log(refreshResult);
if(refreshResult?.data) {
const email = api.getState().auth.email || JSON.parse(localStorage.getItem("authenticatedUser"))?.email;
const role = api.getState().auth.role || JSON.parse(localStorage.getItem("authenticatedUser"))?.role;
const name = api.getState().auth.name || JSON.parse(localStorage.getItem("authenticatedUser"))?.name;
// store the new token
api.dispatch(setCredentials({
accessToken: refreshResult.data.accessToken,
email,
role,
name,
branch: refreshResult.data.branch,
company: refreshResult.data.company
}));
// retry the original query with new access token
result = await baseQuery(args, api, extraOptions);
} else {
await baseQuery('/auth/logout', api, extraOptions);
api.dispatch(logout());
}
} finally {
release();
}
} else {
await mutex.waitForUnlock();
result = await baseQuery(args, api, extraOptions);
}
}
return result;
}
export const apiSlice = createApi({
baseQuery: baseQueryWithReauth,
tagTypes: [
'Branch', 'Company', 'Customer', 'Driver', 'Parcel', 'ParcelTransaction',
'ParcelType', 'Staff', 'Town', 'TransactionChannel', 'User', 'VehicleOwner',
'Vehicle', 'VehicleType'
],
refetchOnMountOrArgChange: 5,
refetchOnFocus: true,
endpoints: builder => ({})
})
I also set the refetchOnFocus to be true on the baseQuery but it doesn't work at all. I was thinking this would help, but the cache is persistent, even if the system remains dominant for more than 30 mins. I mean it should even refetch data on the minimum, but it continues to use the stale cache data.
parcelSlice.js
import { createSelector, createEntityAdapter } from "#reduxjs/toolkit";
import { apiSlice } from '../../api/apiSlice';
const SLICE_URL = '/parcels';
const parcelsAdapter = createEntityAdapter({
sortComparer: (a, b) => b.parcelCode.localeCompare(a.parcelCode)
})
const initialState = parcelsAdapter.getInitialState()
export const parcelApiSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
getParcels: builder.query({
query: () => `${SLICE_URL}`,
transformResponse: responseData => {
return parcelsAdapter.setAll(initialState, responseData)
},
providesTags: (result, error, id) => ['Parcel', 'ParcelTransaction']
}),
getParcel: builder.query({
query: (id) => `${SLICE_URL}/${id}`,
providesTags: (result, error, id) => ['Parcel', 'ParcelTransaction'],
}),
getParcelsAvailableForDispatch: builder.query({
query: () => `${SLICE_URL}/available-for-dispatch`,
providesTags: (result, error, id) => ['Parcel', 'ParcelTransaction'],
}),
getParcelsAssignedToVehicle: builder.query({
query: (vehicleID) => `${SLICE_URL}/assigned-to-vehicle/?vehicleID=${vehicleID}`,
providesTags: (result, error, id) => ['Parcel', 'ParcelTransaction'],
}),
getParcelsAwaitingRecipients: builder.query({
query: () => `${SLICE_URL}/parcels-awaiting-recipients`,
providesTags: (result, error, id) => ['Parcel', 'ParcelTransaction'],
}),
addParcel: builder.mutation({
query: parcelData => ({
url: `${SLICE_URL}`,
method: 'POST',
body: {
...parcelData
}
}),
invalidatesTags: ['Parcel', 'ParcelTransaction']
}),
issueParcel: builder.mutation({
query: parcelID => ({
url: `${SLICE_URL}/issue-parcel/${parcelID}`,
method: 'PATCH',
body: {
id: parcelID
}
}),
invalidatesTags: ['Parcel', 'ParcelTransaction']
}),
updateParcel: builder.mutation({
query: ({id, parcelData}) => ({
url: `${SLICE_URL}/${id}`,
method: 'PATCH',
body: {
...parcelData
}
}),
invalidatesTags: (result, error, arg) => [
{ type: 'Parcel', id: arg.id }
]
}),
})
});
export const {
useGetParcelsQuery,
useGetParcelQuery,
useGetParcelsAvailableForDispatchQuery,
useGetParcelsAssignedToVehicleQuery,
useGetParcelsAwaitingRecipientsQuery,
useAddParcelMutation,
useIssueParcelMutation,
useUpdateParcelMutation,
} = parcelApiSlice;
// returns the query result object
export const selectParcelsResult = parcelApiSlice.endpoints.getParcels.select();
// Creates memoized selector
const selectParcelsData = createSelector(
selectParcelsResult,
parcelsResult => parcelsResult.data // normalized state object with ids & entities
);
//getSelectors creates these selectors and we rename them with aliases using destructuring
export const {
selectAll: selectAllParcels,
selectById: selectParcelById,
selectIds: selectParcelIds
// Pass in a selector that returns the parcels slice of state
} = parcelsAdapter.getSelectors(state => selectParcelsData(state) ?? initialState)
An example use case is when I want to issue a parcel to a customer using the issueParcel mutation on the parcelSlice, the thing is, the current parcel status should change the status to delivered and update the UI by refetching data once the API mutation call has been made and the mutation is successful. However, this only happens in locahost, but does not happen when I host the app in the server. This is part of the code that I am using on the parcel's details component.
parcelDetails.jsx
const ParcelDetails = () => {
const { id } = useParams();
const navigate = useNavigate();
const theme = useTheme();
const { data: parcelDetails, isLoading, isError, error, refetch } = useGetParcelQuery(id);
const [issueParcel] = useIssueParcelMutation()
const authenticatedUser = JSON.parse(localStorage.getItem("authenticatedUser"));
const breadcrumbs = [
{ name: "Parcel", path: "/parcel" },
{ name: parcelDetails?.parcelCode }
];
const staffName = parcelDetails?.staff?.ownuser?.firstName + " " + parcelDetails?.staff?.ownuser?.lastName;
const handleIssueParcel = (parcelID) => {
Swal.fire({
title: 'Are you sure you want to issue this parcel?',
html: '<p>ParcelCode: ' + parcelDetails.parcelCode + '</p> <br />',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes, issue Parcel!'
}).then(async (result) => {
if (result.isConfirmed) {
try {
Swal.fire({
title: "Issuing Parcel",
html: "Please wait..."
})
Swal.showLoading()
await issueParcel(parcelID);
refetch();
Swal.hideLoading()
Swal.fire(`parcel ${parcelDetails.parcelCode} issued successfully!`, '', 'success');
} catch (error) {
console.log(error);
}
} else if (result.isDenied) {
Swal.fire('Parcel not issued.', '', 'info');
}
})
}
return (<>Parcel Display UI</>)
}
export default ParcelDetails
I even tried to force refetch() of the data after every API call is successful, but this does not work when the app is hosted on the server.
I believe it is something small that I am missing out. I will appreciate your review and advice on the same.
I finally solved this by adding this to the baseQuery. Credits to this question that was facing a similar problem as mine.
const baseQuery = fetchBaseQuery({
baseUrl: baseURL,
credentials: 'include',
timeout: 15000,
prepareHeaders: (headers, {getState}) => {
headers.set('Accept', 'application/json');
headers.set('Cache-Control', 'no-cache');
headers.set('Pragma', 'no-cache');
headers.set('Expires', '0');
const token = getState().auth.token || JSON.parse(localStorage.getItem("authenticatedUser"))?.accessToken;
if (token) {
headers.set("Authorization", `Bearer ${token}`)
}
return headers;
}
});

React-query useInfiniteQuery: getNextPageParam not working

I'm stuck using useInfiniteQuery.
The first call works fine, but the next page is not called with getNextPageParam
const getProductItems = async (par) => {
console.log("axios :", par);
const res = await axios.get(`/api/v1/products`, {
params: par,
});
return {
result: res.data,
};
};
export default function useGetProductItems(params) {
const { data, isLoading, fetchNextPage, hasNextPage, isFetching } =
useInfiniteQuery(
["getItems"],
({ pars = params }) => getProductItems(pars),
{
getNextPageParam: (res) => {
console.log(res);
const nextParams = {
...res.result.pageInfo,
page: res.result.pageInfo.page + 1,
};
console.log("next :", nextParams);
return nextParams;
},
select: (data) => {
return data.pages[0].result.data;
},
}
);
return {
data,
isLoading,
fetchNextPage,
hasNextPage,
isFetching,
};
}
And the Query Client setting is like this
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
queryCache: new QueryCache({
onError: errorHandler,
}),
mutationCache: new MutationCache({
onError: errorHandler,
}),
});
As I am new to react-query, I am also wondering if there is any data that must be received from the API.
plz answer for me
You can access pageParam and send it as argument to your fetching function. Also it'd be a good idea to check if there really is a next page before incrementing the actual page number in getNextPageParam. Something like this:
const { data, isLoading, fetchNextPage, hasNextPage, isFetching } =
useInfiniteQuery(
['getItems'],
({ pageParam = 1 }) => getProductItems(pageParam), // pageParam defaults to the first page
{
getNextPageParam: lastPage => {
return lastPage.result.pageInfo.page < lastPage.result.pageInfo.totalPages // Here I'm assuming you have access to the total number of pages
? lastPage.result.pageInfo.page + 1
: undefined // If there is not a next page, getNextPageParam will return undefined and the hasNextPage boolean will be set to 'false'
},
select: data => {
return data.pages[0].result.data
},
}
)
I don't have information about how is your API endpoint built, but typically the request should look, for example, like this:
const getProductItems = async (page) => {
const res = await axios.get(`/api/v1/products?page=${page}`);
return {
result: res.data,
};
};

useMutation reload data after refetch

I am trying to reload my table of data once my useMutation has completed.
On page load i am querying:
const { loading: appLoading, data: applicationsData } = useQuery(
applications.operations.GET_APPLICATIONS_BY_COMPANY,
{
client: applications.client,
variables: { companyId: userDetails.companyId },
}
)
when a user selects a button to clone a record:
const [
CloneApplication,
{ loading: cloneLoading, data: cloneData, error: cloneError },
] = useMutation(applications.operations.CLONE_APPLICATION_BY_COMPANY, {
client: applications.client,
onCompleted: (data) => {
setFinalData((prev) => [...prev, data]), console.log('data', data)
},
})
im adding a record to the list but when i refresh its not there. My assumption is instead of adding it to state, I need to refetch the applications and then save that to state which in turn will automatically refresh the table.
My question is how can i do that?
Edit:
const { applications } = apis
const { queryString, parameters } = getTemplatesListApiDetails()
const [finalData, setFinalData] = useState<any>([])
const [templatesList, setTemplatesList] = useState([])
const { loading, data } = useQuery(queryString, parameters)
const { loading: appLoading, data: applicationsData } = useQuery(
applications.operations.GET_APPLICATIONS_BY_COMPANY,
{
client: applications.client,
variables: { companyId: userDetails.companyId },
}
)
const [
CloneApplication,
{ loading: cloneLoading, data: cloneData, error: cloneError },
] = useMutation(applications.operations.CLONE_APPLICATION_BY_COMPANY, {
client: applications.client,
refetchQueries: [
{ query: applications.operations.GET_APPLICATIONS_BY_COMPANY },
],
})
useEffect(() => {
if (data && data.getCompanyTemplates)
setTemplatesList(
userDetails.globalTemplates === false
? data.getCompanyTemplates
: data.getAllTemplates
)
if (applicationsData && templatesList) {
const newFinalData = getFinalData({
applicationsList: applicationsData.getApplicationsByCompany,
templatesList: templatesList,
})
setFinalData(newFinalData)
}
}, [applicationsData, cloneData, data, templatesList])
getFinalData Function
export function getFinalData(request: {
templatesList: GetAllTemplate[]
applicationsList: GetApplicationsByCompany[]
}): FinalDataResponse[] {
const templates = request.templatesList.map((template) => {
const applicationsForTemplate = request.applicationsList.filter(
(app) => app.templateId === template.templateId
)
return { ...template, applications: applicationsForTemplate }
})
const groupedData = _.chain(templates)
.groupBy('templateId')
.map((value, key) => {
const templateName = _.chain(value)
.groupBy('templateName')
.map((value, key) => key)
.value()
const createdDate = _.chain(value)
.groupBy('dateCreated')
.map((value, key) => dayjs(key).format('ll'))
.value()
const lastModified = _.chain(value)
.groupBy('lastModified')
.map((value, key) => dayjs(key).format('ll'))
.value()
return {
templateId: key,
templateName: templateName[0],
createdDate: createdDate[0],
lastModified: lastModified[0],
applications: value[0].applications,
}
})
.value()
const finalData = groupedData.map((object, index) => {
return {
...object,
totalApplications: object.applications.length,
}
})
console.log('returning final data: ', finalData)
return finalData
}
To refetch the data automatically, you need to invalidate the previously cached results. In apollo, this is done using refetchQueries:
useMutation(applications.operations.CLONE_APPLICATION_BY_COMPANY, {
refetchQueries: [{ query: applications.operations.CLONE_APPLICATION_BY_COMPANY}]
})
More ways of solving this here: https://www.apollographql.com/blog/apollo-client/caching/when-to-use-refetch-queries/

how to call reactQuery refetch in input onchange event in reactjs

In my React application, I need to call does exit API. API call should happen when change event in the input for that, I am using the reactQuery refetch to do that.
I had tried with below code
const [createObj, setCreateObj] = useState(mCreateObj);
const { data: doexit, refetch: doexitRefetch } = useQuery('getDoexit', () => api.doexitAPI(createObj.c_name), { enabled: false });
const handleInput = ({ target: { name, value } }) => { setCreateObj(state => ({ ...state, [name]: value }), []); }
export const doexitAPI= (value) => axios.get(/doexist/${value}, { headers: setHeader }).then(res => res);
useEffect(() => { console.log(createObj) doexitRefetch(); }, [createObj.mx_name])
How to call in input onchange event
You can invalidate your query and handle fetch data again with query keys.
https://react-query.tanstack.com/guides/query-keys#if-your-query-function-depends-on-a-variable-include-it-in-your-query-key
const { data: doexit, refetch: doexitRefetch } = useQuery(['getDoexit', createObj.mx_name], () => api.doexitAPI(createObj.c_name), { enabled: false });

React useState hook with callback

I'm having a hard time converting the following methods with setState
sendMsgToMe = () => {
const { senderIdString, senderId } = this.props; //eslint-disable-line
const { messages, counter } = this.state;
let myNumber;
this.setState({
btnSendMeIsDisabled: true,
});
axios
.get(`${appConfig.URL_REST}user`, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
})
.then((response) => {
myNumber = response.data.phoneNumber;
this.setState(
{
messageToSend: [
{
from: senderIdString ? senderIdString.number : senderId,
to: myNumber,
message: messages[counter],
},
],
},
this.sendMsgAxios
);
});
};
sendMsgToRecipients = () => {
const { recipientsNmbs, senderIdString, senderId } = this.props;
const { messages, counter } = this.state;
this.setState({
btnSendIsDisabled: true,
});
const msgToSend = recipientsNmbs.map((item) => {
return {
from: senderIdString ? senderIdString.number : senderId,
to: item,
message: messages[counter],
};
});
this.setState(
{
messageToSend: msgToSend,
},
this.sendMsgAxios
);
};
into functions. I saw examples with calling it from useEffect with dependency array but they were more plain.
The main issue in this piece of code
this.setState(
{
messageToSend: [
{
from: senderIdString ? senderIdString.number : senderId,
to: myNumber,
message: messages[counter],
},
],
},
this.sendMsgAxios
);
My questions is how exactly should I call it in useEffect or is there any better approach?
You could use a new state callSendMsgAxios that will trigger sendMsgAxios when sendMsgToMe or sendMsgToRecipients are executed, your component will look like:
...
// useReducer is better in this case
const [state, setState] = useState({
messages: '',
counter: '',
btnSendMeIsDisabled: false,
messageToSend: [{
from: '',
to: '',
message: ''
}],
callSendMsgAxios: false,
...
});
useEffect(() => {
if(callSendMsgAxios) {
sendMsgAxios();
setState(prevState => ({ ...prevState, callSendMsgAxios: false }) );
}
}, [callSendMsgAxios]);
...
const sendMsgToMe = () => {
const { senderIdString, senderId } = props;
const { messages, counter } = state;
let myNumber;
setState(prevState => ({
...prevState,
btnSendMeIsDisabled: true,
}));
axios
.get(`${appConfig.URL_REST}user`, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
})
.then((response) => {
myNumber = response.data.phoneNumber;
setState(prevState => (
{
...prevState,
callSendMsgAxios: true,
messageToSend: [
{
from: senderIdString ? senderIdString.number : senderId,
to: myNumber,
message: messages[counter],
},
],
}
));
});
};
...
The code you put in useEffect, is executed, based on the dependencies you send to it.
So first ask yourself,
What should I execute? (Becomes the body of your useEffect)
Does it depend on anything before determining if it should execute or not. (Becomes the dependency)
useEffect(() => {
this.sendMsgAxios(a, b c); // a, b, c could be still thats contained in your state
}, [a, b, c]) // clearly since a, b, c are ones which the effect depends on, you need to pass them in the dependencies array
I'm hoping this will lead to what you want to achieve.

Resources