How to get GraphQL response data within TinyMCE file_picker_callback - reactjs

I am working on image upload with React TinyMCE editor. I have two graphql endpoints to process my image upload. This is my file_picker_callback
file_picker_callback: function() {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.onchange = function() {
const file = this.files[0];
const isFileValid = validateUpload(file);
if (isFileValid) {
setLastUploadedFile(file);
getImageSignedUrl({
variables: {
id,
locale: 'en-US',
},
});
setIsImageValid(false);
}
};
}
This is the first graphql endpoint that I make
const [getImageSignedUrl] = useLazyQuery(GET_SIGNED_IMAGE_URL, {
fetchPolicy: 'no-cache',
onCompleted: async ({ uploadUrlForCustomContentImage }) => {
const res = await uploadImage(lastUploadedFile, uploadUrlForCustomContentImage?.signedUrl);
if (res.status === 200) {
confirmImageUpload({
variables: {
input: {
id,
locale: 'en-US',
fileName: uploadUrlForCustomContentImage?.fileName,
},
},
});
}
},
});
After 'getImageSignedUrl' is finished in onCompleted block I make a second graphql 'confirmImageUpload' call which should return my imageUrl that I was planning to use within file_picker_callback to insert into input field.
Second endpoint
const [confirmImageUpload] = useMutation(CONFIRM_IMAGE_UPLOAD, {
fetchPolicy: 'no-cache',
});
However I am having trouble accessing data within file_picker_callback after confirmImageUpload is finished executing. I tried to update my local state in onCompleted block but its not able to pick up the change within file_picker_callback.
This is the first time I am working with React TinyMCE editor so if anyone has any suggestions please let me know

Related

Sending Expo Push Notifications to both Android and iOS devices from Reactjs Dashboard

I have an expo app which is installed on both Android and iOS devices.
These apps use Firebase as a backend. I have also created a dashboard in Reactjs which is also using same Firebase as a backend.
I want to send notification to a specific user whenever i perform some event on Reactjs Dashboard.
e.g: I change the order_status from "pending" to "delivered" then a firebase event changes the order_status for that user in firebase collection so i want the user to know his order has been dispatched.
How can i achieve this for both Android and iOS ?
How can i achieve this for both Android and iOS ?
SOLUTION:
App side code to setup receiving notifications:
const [expoPushToken, setExpoPushToken] = useState('');
const [notification, setNotification] = useState(false);
const notificationListener = useRef();
const responseListener = useRef();
async function sendPushNotification(expoPushToken) {
const message = {
to: expoPushToken,
sound: 'default',
title: 'Original Title',
body: 'And here is the body!',
data: { someData: 'goes here' },
};
console.log(expoPushToken);
await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-encoding': 'gzip, deflate',
'Content-Type': 'application/json',
},
body: JSON.stringify(message),
});
}
async function registerForPushNotificationsAsync() {
let token;
if (Device.isDevice) {
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;
console.log(token);
} else {
alert('Must use physical device for Push Notifications');
}
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
return token;
}
useEffect(() => {
registerForPushNotificationsAsync().then(token => setExpoPushToken(token));
// This listener is fired whenever a notification is received while the app is foregrounded
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
setNotification(notification);
});
// This listener is fired whenever a user taps on or interacts with a notification (works when app is foregrounded, backgrounded, or killed)
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
console.log(response);
});
return () => {
Notifications.removeNotificationSubscription(notificationListener.current);
Notifications.removeNotificationSubscription(responseListener.current);
};
}, []);
// write below code outside export App function
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: false,
}),
});
You might need to install expo-notifications using:
npx expo install expo-notifications
From Above code you can get push Token which at first you can manually use to test notifications, then eventually you can store all user's device push tokens in some firebase DB or custom DB against their uid.
Then later use these tokens to send them individual notifications.
Server Side Code:
install this library first npm i node-fetch
push Token looks like this : ExponentPushToken[KA2CcEFolWMq_9TmIddctr]
import fetch from "node-fetch";
async function sendPushNotification(expoPushToken) {
const android = "pushToken";
const ios = "pushToken";
const message = {
to: ios,
sound: 'default',
title: 'Original Title',
body: 'And here is the body!',
data: { someData: 'goes here' },
};
await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-encoding': 'gzip, deflate',
'Content-Type': 'application/json',
},
body: JSON.stringify(message),
});
}
call this function in the end sendPushNotification();
Here is the code for expo nodejs sdk, check for other languages/frameworks
This is more like pseudo code
import Expo from 'expo-server-sdk';
// let sentNotificationId = //id for entry in db
// generating custom objects to store them in DB
const pushNotificationsItems = [{
// info like userId
// sentNotificationId, entry id etc
// data mostly needed by the app
to: notification.token,
notificationBody: {
// expo push notification related info
to: notification.token, // expo token
title: `Some title`,
body: message,
data: {
//custom data to be used in app
}
}
}
]
// make db entries with data you need
// checking valid expo messages
let tickets = [];
let notificationMessages = [];
for (const notification of pushNotificationsItems) {
if (!Expo.isExpoPushToken(notification.to)) {
continue
}
notificationMessages.push(notification.notificationBody)
}
// send actual notification using expo client
const expo = new Expo({});
const chunks = expo.chunkPushNotifications(notificationMessages);
//console.log('message chunks', chunks)
const resolvedPromises = await Promise.allSettled(map(chunks, async chunk => {
const ticketChunks = await expo.sendPushNotificationsAsync(chunk);
tickets.push(...ticketChunks)
}));
// saving the response if needed
if (tickets.length) {
const mappedTickets = map(tickets, t => {
return {
data: t,
notificationSentId: sentNotificationId,
organisationId,
createdAt: getCurrentUTCISOString() || '',
}
})
await this.prisma.notificationResponse.createMany({
data: [...mappedTickets]
})
}
Hope it helps you in some way

How to perform action or render component before/after every http request in React?

I want to know if there is a way to create a kind of middleware in React?
What i want is to have an alert component show if there is a failing result for an http request.
Right now, i am making http request on login,registration,etc and i am importing my alert component in every page and setting the Alert component props like type, message, visibility everywhere i need the component, but i think maybe there is a better way of doing this.
Here is my code:
...imports
export const RegisterPage = () => {
const [alertConfig, setAlertConfig] = useState({
type: "",
message: "",
show: false,
});
...code
const onSubmitHandler = async (e) => {
e.preventDefault();
if (!isFormValid()) return;
const formData = new FormData();
formData.append("password", formValues.password);
if (formValues.provider.startsWith("8")) {
formData.append("contact", formValues.provider);
} else {
formData.append("email", formValues.provider);
}
setIsLoading(true);
try {
const response = await fetch(
`${process.env.REACT_APP_API_URL}/auth/register`,
{
method: "POST",
body: formData,
}
);
const data = await response.json();
if (data.status === "success") {
const { token, user } = data.data;
dispatch(setCurrentUser(user, token));
navigate("/choose-actor");
} else {
setAlertConfig({
type: "warning",
message: data.message,
show: true,
});
}
} catch (error) {
console.log(error);
setAlertConfig({
type: "danger",
message: "Ocorreu algum erro",
show: true,
});
} finally {
setIsLoading(false);
}
};
return
...html
{alertConfig.show && <Alert {...alertConfig} />}
...more html
As you can see, i am changing the configuration for the alert inside inside the function that executes the http request, and i have to do the save for every page that performs this action.
I looking for a design patter where i dont have to repeat myself.
Hope my question is clear.

Lazy Query graphql response data is not accessible inside react after executing the query

I need to call a query when Download button is pressed and then handle the response.I only need to execute the query when download button is pressed and not when the react component loads. Hence I used 'useLazyQuery' option from graphql.
I need something like this:
const [downloadAllRecipients,{ loading, error, data }] = useLazyQuery(QUERY,
{
variables: {
campaignId: props.campaignId,
},
fetchPolicy: 'network-only',
},
);
//This is the download button onPress event handler.
const downloadDataButton = async () => {
const res = await downloadAllRecipients({ variables: { campaignID: props.campaignId }})
console.log(res) // handle response
}
//Output:-
//undefined
When executing the above query, res is undefined. However, the graphql query was seen in the network layer. I have a react frontend and am not able to access the data returned by the query.Please help!
Thanks in advance!
You will get the data in onCompleted event.
So your code should look like following,
const [downloadAllRecipients,{ loading, error, data }] = useLazyQuery(QUERY,
{
variables: {
campaignId: props.campaignId,
},
fetchPolicy: 'network-only',
onCompleted: res => { setRes(res); console.log(res);}
},
);

apollo client 3 cache doesn't update after mutation

First of all, i'd like to apologize if this is a duplicate but none of the existing answers in similar questions helped me out.
I am using nextjs 9.5.3 and apollo client 3.2.2 . I have a form where a user fills it out to update their profile details. On submission, the data saved is saved in the database and a response returned back to the client. The issue is that the response is unable to update the cache but it can be found inside ROOT_MUTATION according to apollo devtools.
I use a query to initially load the user's data then update the cache with the result during the mutation. Below are the local query and mutation.
// fragments
export const editUserProfileFragment= gql`
fragment EditUserProfileFields on ProfileInterface {
id
type
slug
name
location {
name
address
country
latitude
longitude
}
settings
createdAt
isActive
}
`;
// query
export const editUserProfileQuery = gql`
query EditUserProfile($slug: String) {
Profile(slug: $slug) {
...EditUserProfileFields
}
}
${editUserProfileFragment}
`;
// mutation
export const editUserProfileMutation = gql`
mutation EditUserProfile($id: ObjectID!, $profile: ProfileInput!) {
editProfile(id: $id, profile: $profile) {
...EditUserProfileFields
}
}
${editUserProfileFragment}
`;
Here's how i use the query and mutation:
// the query
const { error, data, loading } = useQuery(editUserProfileQuery, {
variables: { slug },
fetchPolicy: "network-only",
})
// data returns a `Profile` object
// the mutation
const [editUserProfileMutate] = useMutation(editUserProfileMutation)
...
// save data
try {
const response = await editUserProfileMutate({
variables: { id, profile: inputdata },
// update: (cache, { data }) => {
// const cachedData: any = cache.readQuery({
// query: editUserProfileQuery,
// variables: { slug: newProfile.slug }
// });
// const cacheId = cache.identify(data.editProfile) // to see the id, i wanted to try cache.modify() but didn't how to proceed.
// console.log('update.cachedData', cachedData);
// console.log('update.cachedData.Profile', cachedData.Profile);
// console.log('update.data', data);
// const newData = { ...cachedData.Profile, ...data.editProfile }; // i first used [] but
// console.log('newData', newData);
// // cache.writeQuery({
// // query: editUserProfileQuery,
// // variables: { slug: newProfile.slug },
// // data: { editProfile: newData }
// // })
// // cache.modify({
// // id: cacheId,
// // })
// },
// tried the below but didn't work
// refetchQueries: [{
// query: editProfilePageQuery,
// variables: { slug: newProfile.slug },
// }],
// awaitRefetchQueries: true
});
const updatedProfile = response.data.editProfile;
console.log('updatedProfile', updatedProfile);
....
} catch (error) {
....
} // trycatch
Also the below apollo client is mainly based on nextjs with-apollo example:
...
let apolloClient;
...
const cache = new InMemoryCache({
// https://www.apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces
dataIdFromObject: result => `${result.__typename}:${result._id || result.id || result.name || result.slug || Math.floor(Math.random() * 1000000)}`,
possibleTypes: {
ProfileInterface: ["Star", "User"],
},
// #see https://www.apollographql.com/docs/react/caching/cache-field-behavior/#merging-non-normalized-objects from console warnings
typePolicies:
User: {
fields: {
location: {
merge(_existing, incoming) {
return incoming;
},
},
},
},
},
});
function createClient() {
const link = makeLink();
return new ApolloClient({
cache,
link,
connectToDevTools:typeof window !== 'undefined',
ssrMode: typeof window === 'undefined',
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createClient()
if (initialState) {
const existingCache = _apolloClient.extract()
console.log('existingCache', existingCache);
// _apolloClient.cache.restore({ ...existingCache, ...initialState }) // commented out on purpose
_apolloClient.cache.restore(initialState)
}
if (typeof window === 'undefined') return _apolloClient
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo({ initialState }), [initialState])
return store
}
So after going through my backend source code, i found out that i wasn't returning the updated values from the database but rather the old ones instead. I fixed it and it works as it should.

Apollo GraphQL Caching Questions (cacheRedirects and readFragment)

I'm new to Apollo and Apollo caching. I've been writing a demo project and have run into two issues:
Can't get cacheRedirects to work properly:
In my demo, I "Load All" COVID19 test data, which queries an API and returns an array of results for 3 countries, including Canada. Then I try to load an individual result ("Load Canada") to get it to use my cacheRedirects resolver. I think I have this set up correctly, but it always goes back to the API to do the query rather than reading from the cache.
Here is my Apollo client with cache:
const client = new ApolloClient({
uri: "https://covid19-graphql.now.sh",
cache: new InMemoryCache({
dataIdFromObject: obj => {
console.log("in cache: obj:", obj);
let dataId = null;
switch (obj.__typename) {
case "Country":
dataId = obj.__typename + "_" + obj.name;
break;
default:
dataId = defaultDataIdFromObject(obj);
}
console.log("in cache: dataId:", dataId);
return dataId;
},
cacheRedirects: {
Query: {
country: (_, args, { getCacheKey }) => {
console.log("in cacheRedirects:", _, args);
const cacheKey = getCacheKey({ __typename: "Country", ...args });
console.log("cacheKey:", cacheKey);
return cacheKey;
}
}
}
})
// connectToDevTools: true
});
I can't figure out how to perform a readFragment:
I've tried so many different configurations within this code, but I never get any results.
Here is my function:
const ReadFragment = () => {
console.log("in ReadFragment");
try {
const data = client.readFragment({
id: GET_DATA.countryData,
fragment: gql`
fragment mostRecent on country {
id
text
complete
}
`
});
if (data) {
console.log(data);
return (
<div>
From Fragment: {JSON.stringify(data)}
</div>
);
} else {
return <div>From Fragment: not found</div>;
}
} catch (error) {
// console.error(error);
return <div>From Fragment: not found</div>;
}
};
Bonus Question: I don't seem to be able to get the Apollo Client Developer Tools extension to work in Chrome browser. Does this still work? My code never seems to connect to it. (uncomment out the connectToDevTools: true.) It seems that being able to examine the contents of the cache would be very useful for development and learning. Is there an alternate way to view the cache contents?
The Apollo GraphQL maintain the cache itself and certainly you don't have to -
export declare type FetchPolicy = 'cache-first' | 'network-only' | 'cache-only' | 'no-cache' | 'standby';
If you look into the fetchPolicy declaration then there are several options to do that -
Network Only
const { data } = useQuery(GET_LIST, {
fetchPolicy: 'network-only'
});
Cache First
const { data } = useQuery(GET_LIST, {
fetchPolicy: 'cache-first'
});
Cache Only
const { data } = useQuery(GET_LIST, {
fetchPolicy: 'cache-only'
});
Similarly the rest options also can be looked upon based on requirement.
If you want to maintain state and do that kinda work then write resolvers for those queries -
const client = new ApolloClient({
uri: apollo.networkInterface,
cache,
resolvers: {
Mutation: {
searchQuery: (launch, _args, { cache }) => {
console.log(cache); // cache can be queried here
// read from cache if planning to modify the data
//const { searchQuery } = cache.readQuery({ query: GET_LIST });
cache.writeQuery({
query: GET_LIST,
data: {searchQuery:_args.searchQuery}
});
},
},
},
})
The Chrome client does work but for that the connection with the graphql server has to be done. Else it will not show up in chrome.

Resources