I'm try to return user to previous step after some updates on backend via useQuery apollo hook
export const useOrderUnlock = () => {
const [isLoading, setIsLoading] = useState(false);
const isBlocked = useSelector(getCheckinIsBlockedForPayment);
const orderId = useSelector(getCheckinOrderId);
const dispatch = useDispatch();
const { goToNextStep } = useSteps();
const [resetOrderPaymentBlock, { loading: resetOrderPaymentLoading, data: resetOrderPaymentData }] = useMutation<
ResetOrderPaymentBlock,
ResetOrderPaymentBlockVariables
>(ResetOrderPaymentBlockMutation.ResetOrderPaymentBlock);
const { refetch, loading: refetchLoading, data: refetchData } = useCheckinOrder(orderId, {
skip: true,
variables: { id: orderId }
});
useEffect(() => {
if (refetchLoading || resetOrderPaymentLoading) {
setIsLoading(refetchLoading || resetOrderPaymentLoading);
}
}, [refetchLoading, resetOrderPaymentLoading]);
useEffect(() => {
console.log(refetchData, refetchLoading); // <-- didn't call after on response
if (refetchData?.CheckinOrder) {
console.log('order refetched, fill and go to passengers');
dispatch(fillCheckinOrder(refetchData.CheckinOrder));
goToNextStep(CheckinStep.Passengers);
console.log('go to passengers');
}
}, [refetchData, refetchLoading]);
useEffect(() => {
if (resetOrderPaymentData?.ResetOrderPaymentBlock) {
console.log('order unlocked, refetch');
refetch();
}
}, [resetOrderPaymentLoading, resetOrderPaymentData]);
const unlock = useCallback(() => {
if (isBlocked) {
console.log('starting to unlock');
resetOrderPaymentBlock({ variables: { id: orderId } });
} else {
goToNextStep(CheckinStep.Passengers);
console.log('go to passengers');
}
}, [isBlocked]);
return {
unlock,
isLoading
};
};
but there is problem with refetch call,
useEffect(() => {
console.log(refetchData, refetchLoading); // <-- didn't call after on response
if (refetchData?.CheckinOrder) {
console.log('order refetched, fill and go to passengers');
dispatch(fillCheckinOrder(refetchData.CheckinOrder));
goToNextStep(CheckinStep.Passengers);
console.log('go to passengers');
}
}, [refetchData, refetchLoading]);
so my question is why refetchData didn't update and why this useEffect hook didn't call?
My useCheckinOrder hook looks like:
export const useCheckinOrder = (
orderId: string,
options?: QueryHookOptions<GetCheckinOrder, GetCheckinOrderVariables>
) => {
return useQuery<GetCheckinOrder, GetCheckinOrderVariables>(CheckinOrderQuery.GetCheckinOrder, {
variables: {
id: orderId,
...options.variables
},
...options
});
};
there is what console print:
starting to unlock
order unlocked, refetch
The default fetchPolicy of apollo client is cache-first which means it does not perform a network request if the data is in the cache (see: https://www.apollographql.com/docs/tutorial/queries/#customizing-the-fetch-policy)
Try changing the fetchPolicy to cache-and-network
Related
I have a component that gets a value from the local storage and does a useQuery to get some data:
const DashboardComponent = () => {
const [filterState, setFilter] = useState(false);
const returnFilteredState = async () => {
return await localforage.getItem<boolean>('watchedAndReviewedFilterd') || false;
};
useEffect(() => {
returnFilteredState().then((value) => {
setFilter(value);
});
}, []);
const {error, loading, data: {moviesFromUser: movies} = {}} =
useQuery(resolvers.queries.ReturnMoviesFromUser, {
variables: {
userId: currentUserVar().id,
filter: filterState,
},
});
The problem is that the ReturnMoviesFromUser query is called twice. I think it's because of the filterState variable. If I set the filter: true the ReturnMoviesFromUser is only called once.
I need help. I don't understand why my dispatch action doesn't work. I've redux store currency list and current currency.
My reducer:
export const currencyReducer = (
state: typeState = initialState,
action: TypeActionCurrency
): typeState => {
switch (action.type) {
case types.CURRENCY_FILL_LIST:
return { ...state, list: action.payload }
case types.CURRENCY_SET_CURRENT:
return {
...state,
current:
state.list.find(currency => currency._id === action.payload) ||
({} as ICurrency),
}
default:
return state
}
}
My actions:
export const setCurrencyList = (currencies: ICurrency[]) => ({
type: types.CURRENCY_FILL_LIST,
payload: currencies,
})
export const setCurrentCurrency = (_id: string) => ({
type: types.CURRENCY_SET_CURRENT,
payload: _id,
})
My useEffect:
useEffect(() => {
if (!list.length) {
const fetchCurrencies = async () => {
try {
const data = await $apiClient<ICurrency[]>({ url: '/currencies' })
dispatch(setCurrencyList(data))
if (!current._id) dispatch(setCurrentCurrency(data[0]._id))
} catch (error) {
console.log(error)
}
}
fetchCurrencies()
}
}, [])
I want make request when load page and write currency list to Redux store, if we don't have current currency we write default currency from data.
There is one more strange thing, my redux extension shows that the state has changed, but when I receive it via the log or useSelector, it is empty
enter image description here
Thanks!
I am not 100% sure but it should work.
const [loader, setLoader] = useState(false);
const list = useSelector(state => state.list)
useEffect(() => {
if (!list.length) {
const fetchCurrencies = async () => {
try {
setLoader(true)
const data = await $apiClient<ICurrency[]>({ url: '/currencies' })
dispatch(setCurrencyList(data))
if (!current._id) dispatch(setCurrentCurrency(data[0]._id))
} catch (error) {
console.log(error)
} finally {
setLoader(false)
}
}
fetchCurrencies()
}
}, [])
useEffect(() => {
console.log(list);
}, [loader])
I am using Expo to send a daily notification at 4:30 pm, without access back end (just local).
It is working well, but not right, once it is sending many pushs to the same user.
I don't know if there is something about useEffect params, but if I do not consider anything inside of [ ], notification is not sent.
import React, { useState, createContext, useEffect, useRef } from 'react'
import { Linking, Platform } from 'react-native'
import AsyncStorage from '#react-native-community/async-storage'
import * as Notifications from 'expo-notifications'
import api from '../api'
import { userStatusStorage } from '../../utils/defaultValues'
import { useAuth } from './auth'
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: false,
}),
});
export const NotificationContext = createContext({});
export function NotificationProvider({ children }) {
const [idUser, setIdUser] = useState()
const [userObj, setUserObj] = useState({})
const [status, setStatus] = useState('')
const [notificationStatusPermission, setNotificationStatusPermission] = useState(true)
const [token, setToken] = useState('')
const [expoPushTokenDevice, setExpoPushTokenDevice] = useState('')
const [notificationSent, setNotificationSent] = useState({})
const { userId, idStorage, user, userStorage, statusStorage, userStatus } = useAuth()
const notificationListener = useRef();
const responseListener = useRef();
useEffect(() => {
if (userId) {
setIdUser(userId)
} else {
setIdUser(idStorage)
}
}, [userId, idStorage])
useEffect(() => {
if (Object.keys(userStorage).length !== 0) {
setUserObj(JSON.parse(userStorage))
} else {
setUserObj(user)
}
}, [userStorage])
useEffect(() => {
if (status !== '') {
return
} else if (userStatus) {
setStatus(userStatus)
} else {
setStatus(statusStorage)
}
}, [statusStorage, userStatus])
useEffect(() => {
try {
Notifications.getDevicePushTokenAsync().then(token => setExpoPushTokenDevice(token));
} catch (e) {
setExpoPushTokenDevice('token');
}
registerForPushNotificationsAsync().then(token => setToken(token));
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
setNotificationSent(notification);
});
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
// called at the moment that user tap on notification bar
const url = response.notification.request.content.data.url;
Linking.openURL(url);
});
return () => {
Notifications.removeNotificationSubscription(notificationListener);
Notifications.removeNotificationSubscription(responseListener);
};
}, []);
useEffect(() => {
sendNotification()
}, [idUser])
async function sendNotification() {
if (notificationStatusPermission) {
await Notifications.scheduleNotificationAsync({
content: {
title: `Olá ${userObj.name.split(' ')[0]}`,
body: `Seu status atual é ${status}!`,
data: {
data: idUser,
url: 'gogogo://'
},
},
trigger: { hour: 16, minute: 30, repeats: true },
});
}
}
async function registerForPushNotificationsAsync() {
let token;
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
alert('Failed to get push token for push notification!');
return;
}
token = (await Notifications.getExpoPushTokenAsync()).data;
setToken(token)
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
return token;
}
async function loadStatus() {
const res = await api.get('myStatusDaily')
await AsyncStorage.setItem(userStatusStorage, res.data[0].name)
setStatus(res.data[0].name)
setNotificationStatusPermission(res.data[0].notificationPermission)
}
return (
<NotificationContext.Provider
value={{
status,
notificationStatusPermission,
loadStatus,
}}
>
{children}
</NotificationContext.Provider>
)
}
How can I garantee that will be sent just one notification per day?
In Apollo Client v3 React implementation, I am using hooks to use subscription. When I receive data from subscription I would like to refetch query but only if query has been previously executed and is in cache. Is there a way to achieve this?
I have started by having a lazy query and then checking the cache manually when subscription data received and then trying to execute lazy query and refetch. It works but it just feels clunky...
export const useMyStuffLazyRefetch = () => {
const [refetchNeeded, setRefetchNeeded] = useState<boolean>(false);
const client = useApolloClient();
const [getMyStuff, { data, refetch }] = useLazyQuery<IStuffData>(GET_MY_STUFF);
useEffect(() => {
if (refetchNeeded) {
setRefetchNeeded(false);
refetch();
}
}, [refetchNeeded]);
const refetchIfNeeded = async () => {
const stuffData = client.cache.readQuery<IStuffData>({ query: GET_MY_STUFF });
if (!stuffData?.myStuff?.length) return;
getMyStuff();
setRefetchNeeded(true);
}
return {
refetchIfNeeded: refetchIfNeeded
};
}
useLazyQuery has a prop called called, this is a boolean indicating if the query function has been called,
so maybe you can try this:
export const useMyStuffLazyRefetch = () => {
const [refetchNeeded, setRefetchNeeded] = useState<boolean>(false);
const client = useApolloClient();
const [getMyStuff, { data, refetch, called }] = useLazyQuery<IStuffData>(GET_MY_STUFF);
useEffect(() => {
if (refetchNeeded) {
setRefetchNeeded(false);
if (called) {
refetch();
}
else {
getMyStuff()
}
}
}, [refetchNeeded, called]);
const refetchIfNeeded = async () => {
const stuffData = client.cache.readQuery<IStuffData>({ query: GET_MY_STUFF });
if (!stuffData?.myStuff?.length) return;
getMyStuff();
setRefetchNeeded(true);
}
return {
refetchIfNeeded: refetchIfNeeded
};
}
In case this can help to somebody. I have created a separate hook so the usage is less of an eyesore.
This is the hook to refetch if data is in cache. If the data is not in the cache, Apollo Client errors instead of returning something like undefined or null
import { useState, useEffect } from "react";
import { OperationVariables, DocumentNode, LazyQueryHookOptions, useApolloClient, useLazyQuery } from "#apollo/client";
export default function useLazyRefetch <TData = any, TVariables = OperationVariables>(query: DocumentNode, options?: LazyQueryHookOptions<TData, TVariables>) {
const [refetchNeeded, setRefetchNeeded] = useState<boolean>(false);
const [loadData, { refetch }] = useLazyQuery(query, options);
const client = useApolloClient();
useEffect(() => {
if (refetchNeeded) {
setRefetchNeeded(false);
refetch();
}
}, [refetchNeeded]);
const refetchIfNeeded = (variables: TVariables) => {
try {
const cachecData = client.cache.readQuery<
TData,
TVariables
>({
query: query,
variables: variables
});
if (!cachecData) return;
loadData({ variables: variables });
setRefetchNeeded(true);
}
catch {}
};
return {
refetchIfNeeded: refetchIfNeeded
};
}
And the hook usage example:
const { refetchIfNeeded } = useLazyRefetch<
IStuffData,
{ dataId?: string }
>(GET_MY_STUFF);
//... And then you can just call it when you need to
refetchIfNeeded({ dataId: "foo" });
typescript is complaining in your
useEffect(() => {
if (refetchNeeded) {
setRefetchNeeded(false);
refetch();
}
}, [refetchNeeded]);
refetch() says - Cannot invoke an object which is possibly 'undefined'.ts(2722)
const refetch: ((variables?: Partial<TVariables> | undefined) => Promise<ApolloQueryResult<TData>>) | undefined
and in [refetchNeeded] dependency -
React Hook useEffect has a missing dependency: 'refetch'. Either include it or remove the dependency array.eslintreact-hooks/exhaustive-deps
const refetchNeeded: boolean
GET requests canceling fine in this example:
export default function Post (props) {
const _cancelToken = axios.CancelToken.source()
useEffect(() => {
const _loadAsyncData = async () => {
await axios.get('/post'), { cancelToken: _cancelToken.token })
}
_loadAsyncData()
return () => {
_cancelToken.cancel()
}
}, [])
return ()
}
But when I need save form via POST request, my code looks like:
export default function Form (props) {
const _cancelToken = axios.CancelToken.source()
const _zz = { qq: 'QQ' }
const handleCreate = async e => {
e.preventDefault()
_zz.qq = 'ZZ'
await axios.post('/form'), {}, { cancelToken: _cancelToken.token })
}
useEffect(() => {
return () => {
console.log(_zz.qq)
_cancelToken.cancel()
}
}, [])
return ()
}
Request not cancel and my _zz.qq always 'QQ' instead 'ZZ'. It's working fine without hooks, but I like hooks and want to use hooks for new components.
I want to cancel request when componentWillUnmount.
This is because you're losing the changes between renders. During the handleCreate call the variable changes only for that render. When the useEffect is run on a subsequent render/unmounting, you're resetting _zz to { qq: 'QQ' }. In order to get around this you need to use references.
export default function Form (props) {
const cancelToken = useRef(null)
const zz = useRef({ qq: 'QQ' })
const handleCreate = async e => {
e.preventDefault()
cancelToken.current = axios.CancelToken.source()
zz.current = { qq: 'ZZ' }
await axios.post('/form'), {}, { cancelToken: cancelToken.current.token })
}
useEffect(() => {
return () => {
console.log(zz.current) //this should now be {qq : 'ZZ'}
if (cancelToken.current) {
cancelToken.current.cancel()
}
}
}, [])
return null
}