Here is my problem, in my react app whenever a order is created I want to get a Subscription for that order called orderNotification,
setup in order resolver:
Subscription: {
orderNotification: {
subscribe: (_, __, { pubsub }) => pubsub.asyncIterator(ORDER_NOTIFICATION)
}
}
mutation:
Mutation: {
async createOrder(_, { MakeOrderInput: { state, message, products, total } }, context) {
try {
const userAuth = isAuth(context);
const pubsub = context.pubsub;
const newOrder = new Order({
state,
username: userAuth.username,
user: userAuth.id,
createdAt: new Date().toISOString(),
total,
message,
products,
userAddress: userAuth.address,
});
const index = products.findIndex(x => x.cost === 0);
if (index != -1) {
const u = await User.findById({ _id: userAuth.id });
await User.findByIdAndUpdate({ _id: userAuth.id }, { points: u.points - 20 }, (err, data) => {
if (err) {
console.log(err)
} else {
console.log('fatto')
}
});
}
const order = await newOrder.save();
pubsub.publish(ORDER_NOTIFICATION, {
orderNotification: order
});
return order;
} catch (err) {
// throw new Error(err);
console.log(err)
}
},
all works fine in graphql Playground but when I have to get and show the results in my component the returned data is null:
import React from 'react'
import gql from 'graphql-tag';
import { useSubscription } from '#apollo/client';
import { Box } from 'grommet'
function SubscriptionOrder() {
const { data, loading, error } = useSubscription(SUBSCRIPTION_USER_ORDER, {
onSubscriptionData: (d) => console.log(d),
onSubscriptionComplete: (da) => console.log(da)
});
// return null
// console.log(data)
return (
<>
<Box style={{ marginTop: '96px' }}>
{data && data.orderNotification ? (
<h1>hi: {data.orderNotification.username}</h1>
) : (
<h1>NO DATA</h1>
)
}
</Box>
</>
)
};
const SUBSCRIPTION_USER_ORDER = gql`
subscription orderNotification{
orderNotification {
username
}
}
`;
export default SubscriptionOrder;
so considering that in playground works the error may be in my ApolloClient links configuration:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { ApolloClient } from 'apollo-client';
import { ApolloProvider } from '#apollo/react-hooks';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { onError } from 'apollo-link-error';
import { ApolloLink, Observable, split } from 'apollo-link';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { TokenRefreshLink } from "apollo-link-token-refresh";
import jwtDecode from "jwt-decode";
import { getAccessToken, setAccessToken } from './accessToken';
import dotenv from 'dotenv/config.js'
const cache = new InMemoryCache({});
const httpLink = new HttpLink({
uri: process.env.NODE_ENV === 'development' ? `${process.env.REACT_APP_SERVER_DEV}/graphql` : `${process.env.REACT_APP_SERVER_PRODUCTION}/graphql`,
credentials: "include",
});
const wsLink = new WebSocketLink({
uri: process.env.NODE_ENV === 'development' ? `ws://${process.env.REACT_APP_SERVER_DEV_WS}/graphql` : `ws://${process.env.REACT_APP_SERVER_PRODUCTION_WS}/graphql`,
options: {
reconnect: true,
lazy: true,
inactivityTimeout: 1000,
},
connectionCallback: err => {
if (err) {
console.log('Error Connecting to Subscriptions Server', err);
}
}
});
const splitLink = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === "OperationDefinition" && operation === "subscriptions";
},
wsLink,
httpLink
);
const requestLink = new ApolloLink(
(operation, forward) =>
new Observable(observer => {
let handle;
Promise.resolve(operation)
.then(operation => {
const accessToken = getAccessToken();
if (accessToken) {
operation.setContext({
headers: {
authorization: `Bearer ${accessToken}`
},
fetchOptions: {
credentials: 'include'
}
});
}
})
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer)
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
);
const client = new ApolloClient({
link: ApolloLink.from([
new TokenRefreshLink({
accessTokenField: "accessToken",
isTokenValidOrUndefined: () => {
const token = getAccessToken();
if (!token) {
return true;
}
try {
const { exp } = jwtDecode(token);
if (Date.now() >= exp * 1000) {
return false;
} else {
return true;
}
} catch {
return false;
}
},
fetchAccessToken: () => {
return fetch(process.env.NODE_ENV === 'development' ? `${process.env.REACT_APP_SERVER_DEV}/refresh_token` : `${process.env.REACT_APP_SERVER_PRODUCTION}/refresh_token`, {
method: "POST",
credentials: "include"
});
},
handleFetch: accessToken => {
setAccessToken(accessToken);
},
handleError: err => {
console.warn("Your refresh token is invalid. Try to relogin");
console.error(err);
}
}),
onError(({ graphQLErrors, networkError }) => {
console.log(graphQLErrors);
console.log(networkError);
}),
requestLink,
splitLink,
]),
cache,
connectToDevTools: true,
credentials: 'include',
});
ReactDOM.render(
<React.StrictMode>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</React.StrictMode >,
document.getElementById('root')
);
here is my server:
import { PubSub } from 'graphql-subscriptions';
const pubsub = new PubSub();
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }) => ({ req, res, pubsub }),
introspection: true,
cors: corsOptions,
});
server.applyMiddleware({ app, cors: false });
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
httpServer.listen(PORT, () => {
console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`)
console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`)
})
resolve the payload in the subscription with
Subscription: {
orderNotification: {
subscribe: (_, __, { pubsub }) => pubsub.asyncIterator(ORDER_NOTIFICATION),
resolve: (payload) => {
return payload;
},
}
}
another way to remove null or unwanted entries is withFilter to remove intimation in case of unknown or unwanted events;
writing this answer as an opyion, but the answer above is better
const { withFilter } = require('graphql-subscriptions');
Subscription: {
orderNotification: {
subscribe: withFilter(
() => pubsub.asyncIterator('ORDER_NOTIFICATION'),
(payload, variables) => {
// add any condition here
return (payload && payload !== null);
},
),
},
}
Related
On my website switching between pages is completely fine and works (it doesnt refresh or load due to redux) but the moment the page is refreshed or i manually enter a link to access, it logs me out. Also it just started happening now, yesterday when i was working on some other things in code, it never logged me out when I manually with links/urls navigated thru website or refreshing but now for some reason it doesnt work and I'm 99% sure I havent touched any auth part of the code...
This is my code:
authApiSlice:
import { apiSlice } from "../../app/api/apiSlice";
import { logOut, setCredentials } from "./authSlice";
export const authApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
login: builder.mutation({
query: (credentials) => ({
url: "/auth",
method: "POST",
body: { ...credentials },
}),
}),
sendLogout: builder.mutation({
query: () => ({
url: "/auth/logout",
method: "POST",
}),
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
console.log(data);
dispatch(logOut());
setTimeout(() => {
dispatch(apiSlice.util.resetApiState());
}, 1000);
} catch (err) {
console.log(err);
}
},
}),
refresh: builder.mutation({
query: () => ({
url: "/auth/refresh",
method: "GET",
}),
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
console.log(data);
const { accessToken } = data;
dispatch(setCredentials({ accessToken }));
} catch (err) {
console.log(err);
}
},
}),
}),
});
export const { useLoginMutation, useSendLogoutMutation, useRefreshMutation } =
authApiSlice;
authSlice:
import { createSlice } from "#reduxjs/toolkit";
const authSlice = createSlice({
name: "auth",
initialState: { token: null },
reducers: {
setCredentials: (state, action) => {
const { accessToken } = action.payload;
state.token = accessToken;
},
logOut: (state, action) => {
state.token = null;
},
},
});
export const { setCredentials, logOut } = authSlice.actions;
export default authSlice.reducer;
export const selectCurrentToken = (state) => state.auth.token;
import { Outlet, Link } from "react-router-dom";
import { useEffect, useRef, useState } from "react";
import { useRefreshMutation } from "./authApiSlice";
import usePersist from "../../hooks/usePersist";
import { useSelector } from "react-redux";
import { selectCurrentToken } from "./authSlice";
const PersistLogin = () => {
const [persist] = usePersist();
const token = useSelector(selectCurrentToken);
const effectRan = useRef(false);
const [trueSuccess, setTrueSuccess] = useState(false);
const [refresh, { isUninitialized, isLoading, isSuccess, isError, error }] =
useRefreshMutation();
useEffect(() => {
if (effectRan.current === true || process.env.NODE_ENV !== "development") {
// React 18 Strict Mode
const verifyRefreshToken = async () => {
console.log("verifying refresh token");
try {
//const response =
await refresh();
//const { accessToken } = response.data
setTrueSuccess(true);
} catch (err) {
console.error(err);
}
};
if (!token && persist) verifyRefreshToken();
}
return () => (effectRan.current = true);
// eslint-disable-next-line
}, []);
let content;
if (!persist) {
// persist: no
console.log("no persist");
content = <Outlet />;
} else if (isLoading) {
//persist: yes, token: no
console.log("loading");
} else if (isError) {
//persist: yes, token: no
console.log("error");
content = (
<p className="errmsg">
{`${error?.data?.message} - `}
<Link to="/login">Please login again</Link>.
</p>
);
} else if (isSuccess && trueSuccess) {
//persist: yes, token: yes
console.log("success");
content = <Outlet />;
} else if (token && isUninitialized) {
//persist: yes, token: yes
console.log("token and uninit");
console.log(isUninitialized);
content = <Outlet />;
}
return content;
};
export default PersistLogin;
RequireAuth
import { useLocation, Navigate, Outlet } from "react-router-dom";
import useAuth from "../../hooks/useAuth";
const RequireAuth = ({ allowedRoles }) => {
const location = useLocation();
const { roles } = useAuth();
const content = roles.some((role) => allowedRoles.includes(role)) ? (
<Outlet />
) : (
<Navigate to="/prijava" state={{ from: location }} replace />
);
return content;
};
export default RequireAuth;
It just stopped worked for some reason, it shouldnt log me out when I refresh.
When the user hits the login button, it redirects to the Unsplash login page. After a successful login, the page redirects back to "localhost" with the "code=" parameter in the URL (http://localhost:3000/?code=VbnuDo5fKJE16cjR#=). After that, I need to get the username of the current user and change the background color of his liked images.
Why does the background color only change when the page is reloaded and not after a successful login?
There are too many requests happening at the same time and I don't know how to handle them properly.
Home.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import ImageList from "../components/ImageList";
import SearchBar from "../components/SearchBar";
import Loader from "../helpers/Loader";
import Login from "../components/Login";
function Home() {
const [page, setPage] = useState(1);
const [query, setQuery] = useState("landscape");
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(false);
const clientId = process.env.REACT_APP_UNSPLASH_KEY;
const url = `https://api.unsplash.com/search/photos?page=${page}&query=${query}&client_id=${clientId}&per_page=30`;
const fetchImages = () => {
setLoading(true);
axios
.get(url)
.then((response) => {
setImages([...images, ...response.data.results]);
})
.catch((error) => console.log(error))
.finally(() => {
setLoading(false);
});
setPage(page + 1);
};
useEffect(() => {
fetchImages();
setQuery("");
}, []);
return (
<div>
<Login />
{loading && <Loader />}
<ImageList images={images} />
</div>
);
}
export default Home;
Login.js
import React, { useEffect } from "react"
import { useAppContext } from "../context/appContext";
function Login() {
const { handleClick, getToken, token, getUserProfile } = useAppContext();
useEffect(() => {
if (window.location.search.includes("code=")) {
getToken();
}
if (token) {
getUserProfile();
}
}, [token]);
return (
<div>
<button onClick={() => handleClick()}>Log in</button>
</div>
);
}
export default Login;
appContext.js
import React, { useReducer, useContext } from "react";
import reducer from "./reducer";
import axios from "axios";
import {SET_TOKEN,SET_LIKED_PHOTOS_ID } from "./actions";
const token = localStorage.getItem("token");
const username = localStorage.getItem("username");
const initialState = {
token: token,
username: username,
likedPhotosId: [],
};
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const handleClick = () => {
window.location.href = `${api_auth_uri}?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope.join(
"+"
)}`;
};
const getToken = async () => {
const urlCode = window.location.search.split("code=")[1];
try {
const { data } = await axios.post(
`${api_token_uri}?client_id=${client_id}&client_secret=${client_secret}&redirect_uri=${redirect_uri}&code=${urlCode}&grant_type=${grant_type}`
);
const { access_token } = data;
localStorage.setItem("token", access_token);
dispatch({
type: SET_TOKEN,
payload: { access_token },
});
} catch (error) {
console.log(error);
}
};
const getUserProfile = async () => {
try {
const { data } = await axios.get(`https://api.unsplash.com/me`, {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + state.token,
},
});
const { username } = data;
localStorage.setItem("username", username);
} catch (error) {
console.log(error);
}
};
const getLikedPhotos = async () => {
try {
const { data } = await axios.get(
`https://api.unsplash.com/users/${state.username}/likes`,
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + state.token,
},
}
);
const likedPhotosId = data.map((photo) => photo.id);
dispatch({
type: SET_LIKED_PHOTOS_ID,
payload: { likedPhotosId },
});
} catch (error) {
console.log(error);
}
};
return (
<AppContext.Provider
value={{
...state,
handleClick,
getToken,
getUserProfile,
getLikedPhotos,
}}
>
{children}
</AppContext.Provider>
);
};
const useAppContext = () => useContext(AppContext);
export { AppProvider, initialState, useAppContext };
ImageList.js
import React, {useEffect } from "react";
import "../styles/ImageList.scss";
import { useAppContext } from "../context/appContext";
function ImageList({ images }) {
const { username, likedPhotosId, getLikedPhotos } = useAppContext();
useEffect(() => {
if (username) {
getLikedPhotos();
}
}, [username]);
return (
<div className="result">
{images?.map((image) => (
<div
style={{
backgroundColor: likedPhotosId?.includes(image.id) ? "red" : "",
}}
>
<div key={image.id}>
<img src={image.urls.small} alt={image.alt_description} />
</div>
</div>
))}
</div>
);
}
export default ImageList;
reducer.js
import { SET_TOKEN, SET_LIKED_PHOTOS_ID } from "./actions";
const reducer = (state, action) => {
if (action.type === SET_TOKEN) {
return {
...state,
token: action.payload.access_token,
};
}
if (action.type === SET_LIKED_PHOTOS_ID) {
return {
...state,
likedPhotosId: action.payload.likedPhotosId,
};
}
throw new Error(`no such action : ${action.type}`);
};
export default reducer;
The problem is in your function. You save the username in localStorage but not in your reducer state:
const getUserProfile = async () => {
try {
const { data } = await axios.get(`https://api.unsplash.com/me`, {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + state.token,
},
});
const { username } = data;
localStorage.setItem("username", username);
} catch (error) {
console.log(error);
}
};
The issue here is that react doesn't trigger a rerender of components when you set something in localStorage and in your ImageList component use have a useEffect expecting username to change before calling the getLikedPhotos:
const { username, likedPhotosId, getLikedPhotos } = useAppContext();
useEffect(() => {
if (username) {
getLikedPhotos();
}
}, [username]);
So to fix you need to add an action for setting the username state in your reducer:
import { SET_TOKEN, SET_LIKED_PHOTOS_ID, SET_USERNAME } from "./actions";
const reducer = (state, action) => {
if (action.type === SET_TOKEN) {
return {
...state,
token: action.payload.access_token,
};
}
if (action.type === SET_USERNAME) {
return {
...state,
username: action.payload.username,
};
}
if (action.type === SET_LIKED_PHOTOS_ID) {
return {
...state,
likedPhotosId: action.payload.likedPhotosId,
};
}
throw new Error(`no such action : ${action.type}`);
};
export default reducer;
And then dispatch that action from the getUserProfile:
const getUserProfile = async () => {
try {
const { data } = await axios.get(`https://api.unsplash.com/me`, {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + state.token,
},
});
const { username } = data;
localStorage.setItem("username", username);
dispatch({
type: SET_USERNAME,
payload: { username },
});
} catch (error) {
console.log(error);
}
};
I am moving my package from react-adal to #azure/msal-react. In react-adal I can authorise and able to go my app. I am using same client_id and Tenant_id but seems like getAllAccounts() returns me empty array, it means no user found as a result I am not getting any token. I used exactly same what the doc says. I am not sure what I am making mistake.
Here is my setup
import { Configuration, PopupRequest, PublicClientApplication } from '#azure/msal-browser'
export const msalConfig: Configuration = {
auth: {
clientId: process.env.NEXT_PUBLIC_MSAL_CLIENT_ID || '',
redirectUri: process.env.NEXT_PUBLIC_MSAL_REDIRECT_URL,
authority: `https://login.microsoftonline.com/${process.env.NEXT_PUBLIC_MSAL_TENANT}`,
navigateToLoginRequestUrl: true,
},
cache: {
cacheLocation: 'localStorage', // This configures where your cache will be stored
storeAuthStateInCookie: false,
},
}
export const loginRequest: PopupRequest = {
scopes: ['User.Read'],
}
export const msalInstance = new PublicClientApplication(msalConfig)
const currentAccounts = msalInstance.getAllAccounts()
console.log({ currentAccounts }) // returns empty array
This is how I warp my app with MsalProvider
import { ApolloProvider } from '#apollo/client'
import { MsalProvider } from '#azure/msal-react'
import { defaultClient } from 'apollo'
import { msalInstance } from 'msal-auth-config' // import msalInstance from config
import type { AppProps } from 'next/app'
import React from 'react'
const App = ({ Component, pageProps }: AppProps): JSX.Element => {
return (
<MsalProvider instance={msalInstance}>
<ApolloProvider client={defaultClient}>
<App />
</ApolloProvider>
</MsalProvider>
)
}
export default App
Here I want to return token
const authLink = setContext((_operation, { headers }) => {
const accounts = msalInstance.getAllAccounts()
//console.log({ accounts, headers })
if (accounts.length > 0) {
msalInstance.setActiveAccount(accounts[0])
}
return msalInstance
.acquireTokenSilent(loginRequest)
.then((response) => {
console.log(response) // return undefined
return { headers: { ...headers, Authorization: `Bearer ${response.idToken}` } }
})
.catch((error) => {
if (error instanceof InteractionRequiredAuthError) {
return msalInstance.acquireTokenRedirect(loginRequest)
}
return
})
})
Have you try to make it like this
import { useState, useEffect } from "react";
import { useMsal } from "#azure/msal-react";
import { InteractionStatus } from "#azure/msal-browser";
const { instance, accounts, inProgress } = useMsal();
const [loading, setLoading] = useState(false);
const [apiData, setApiData] = useState(null);
useEffect(() => {
if (!loading && inProgress === InteractionStatus.None && accounts.length > 0) {
if (apiData) {
// Skip data refresh if already set - adjust logic for your specific use case
return;
}
const tokenRequest = {
account: accounts[0], // This is an example - Select account based on your app's requirements
scopes: ["User.Read"]
}
// Acquire an access token
instance.acquireTokenSilent(tokenRequest).then((response) => {
// Call your API with the access token and return the data you need to save in state
callApi(response.accessToken).then((data) => {
setApiData(data);
setLoading(false);
});
}).catch(async (e) => {
// Catch interaction_required errors and call interactive method to resolve
if (e instanceof InteractionRequiredAuthError) {
await instance.acquireTokenRedirect(tokenRequest);
}
throw e;
});
}
}, [inProgress, accounts, instance, loading, apiData]);
if (loading || inProgress === InteractionStatus.Login) {
// Render loading component
} else if (apiData) {
// Render content that depends on data from your API
}
Read more here
you are probably missing the handleRedirectPromise...
once the redirect is done the promise should catch the account... if not try another aquireSilentToken to catch it in the promise below.
instance.handleRedirectPromise().then(resp => {
if (resp && resp.account) instance.setActiveAccount(resp.account);
});
I'm new to graphql I'm building real time chat app. Currently I'm making it offline first.
Using react as front-end.
I'm currently caching the data on localStorage using apollo3-cache-persist. But How do I query the cache data instead of server (when I'm offline) also I want to add messages to the localStorage while I'm offline.
Display the optimistic response when the device is online I want to send the pending data to the server.
my ApolloProvider.js file in client folder
import React from "react";
import {
ApolloClient,
InMemoryCache,
ApolloProvider as Provider,
createHttpLink,
ApolloLink,
split,
} from "#apollo/client";
import { setContext } from "#apollo/client/link/context";
import { RetryLink } from "#apollo/client/link/retry";
import { persistCache, LocalStorageWrapper } from "apollo3-cache-persist";
import { WebSocketLink } from "#apollo/client/link/ws";
import { getMainDefinition } from "#apollo/client/utilities";
import QueueLink from "apollo-link-queue";
let httpLink = createHttpLink({
uri: "http://localhost:4000/",
});
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem("token");
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
};
});
httpLink = authLink.concat(httpLink);
const wsLink = new WebSocketLink({
uri: `ws://localhost:4000/`,
options: {
reconnect: true,
connectionParams: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
},
});
const link = new RetryLink();
const queueLink = new QueueLink();
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
httpLink
);
const cache = new InMemoryCache();
const fun = async () =>
await persistCache({
cache,
storage: new LocalStorageWrapper(window.localStorage),
});
fun();
const client = new ApolloClient({
// link: splitLink,
link: ApolloLink.from([splitLink, queueLink, link]),
cache,
name: "chat-app",
version: "1.0.0",
queryDeduplication: false,
defaultOptions: {
watchQuery: {
fetchPolicy: "cache-and-network",
},
},
});
export default function ApolloProvider(props) {
return <Provider client={client} {...props} />;
}
my messages.js file
import React, { Fragment, useEffect, useState } from "react";
import { gql, useLazyQuery, useMutation, InMemoryCache } from "#apollo/client";
import { Col, Form } from "react-bootstrap";
import { useMessageDispatch, useMessageState } from "../../context/message";
import uuid from "react-uuid";
import Message from "./Message";
const SEND_MESSAGE = gql`
mutation sendMessage($uuid: String, $to: String!, $content: String!) {
sendMessage(uuid: $uuid, to: $to, content: $content) {
uuid
from
to
content
createdAt
hasSeen
hasSent
}
}
`;
const GET_MESSAGES = gql`
query getMessages($from: String!) {
getMessages(from: $from) {
uuid
from
to
content
createdAt
hasSeen
}
}
`;
export default function Messages() {
const { users } = useMessageState();
const dispatch = useMessageDispatch();
const [content, setContent] = useState("");
const selectedUser = users?.find((u) => u.selected === true);
const messages = selectedUser?.messages;
const [getMessages, { loading: messagesLoading, data: messagesData }] =
useLazyQuery(GET_MESSAGES, {
update(cache) {
cache.readFragment({});
console.log("reading");
},
});
const [sendMessage] = useMutation(SEND_MESSAGE, {
update(cache, { data: { sendMessage } }) {
cache.modify({
fields: {
getMessages(existingMsg) {
console.log(existingMsg);
const newMsgRef = cache.writeFragment({
data: sendMessage,
fragment: gql`
fragment sendNewMessage on Mutation {
uuid
to
from
content
hasSeen
hasSent
}
`,
});
return existingMsg.push(newMsgRef);
},
},
});
},
onError: (err) => console.log(err),
});
useEffect(() => {
if (selectedUser && !selectedUser.messages) {
getMessages({ variables: { from: selectedUser.username } });
}
}, [selectedUser]);
useEffect(() => {
if (messagesData) {
dispatch({
type: "SET_USER_MESSAGES",
payload: {
username: selectedUser.username,
messages: messagesData.getMessages,
},
});
}
}, [messagesData]);
const submitMessage = (e) => {
e.preventDefault();
if (content.trim() === "" || !selectedUser) return;
let id = uuid();
sendMessage({
variables: { uuid: id, to: selectedUser.username, content },
optimisticResponse: {
sendMessage: {
__typename: "Mutation",
uuid: id,
from: "User",
to: selectedUser.username,
content,
hasSent: false,
hasSeen: false,
createdAt: Date.now(),
},
},
});
setContent("");
};
// Displaying helper text and styling
let selectedChatMarkup;
if (!messages && !messagesLoading) {
selectedChatMarkup = <p className="info-text"> Select a friend</p>;
} else if (messagesLoading) {
selectedChatMarkup = <p className="info-text"> Loading..</p>;
} else if (messages.length > 0) {
selectedChatMarkup = messages.map((message, index) => (
<Fragment key={message.uuid}>
<Message message={message} />
{index === messages.length - 1 && (
<div className="invisible">
<hr className="m-0" />
</div>
)}
</Fragment>
));
} else if (messages.length === 0) {
selectedChatMarkup = (
<p className="info-text">
You are now connected! send your first message!
</p>
);
}
return (
<Col xs={10} md={8}>
<div className="messages-box d-flex flex-column-reverse">
{selectedChatMarkup}
</div>
<div>
<Form onSubmit={submitMessage}>
<Form.Group className="d-flex align-items-center">
<Form.Control
type="text"
className="message-input rounded-pill p-4 bg-secondary border-0"
placeholder="Type a message.."
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<i
className="fas fa-regular fa-paper-plane fa-2x text-primary ml-2"
onClick={submitMessage}
role="button"
></i>
</Form.Group>
</Form>
</div>
</Col>
);
}
But I'm currently getting this error when I try to send the message
react_devtools_backend.js:3973 Invariant Violation: Could not identify object {"uuid":"4855ffc-6b7b-d7c8-a68-2ae0162f80a","from":"User","to":"Fire","content":"example text","createdAt":1648881891383,"hasSeen":false,"hasSent":false,"__typename":"Mutation"}
Also getting error from the mutation error log
Error: Could not identify object {"__typename":"Message","uuid":"4855ffc-6b7b-d7c8-a68-2ae0162f80a","from":"Alan","to":"Fire","content":"example text","createdAt":"2022-04-02T06:44:51.807Z","hasSeen":false,"hasSent":false}
Apollo uses by default __typename and id fields to normalise cache. So in your case, Apollo won't recognise your uuid property as a cache identifier. You can change the uuid uuid property, or add keyFields to your InMemoryCache config.
I am using firebase-ui-react in a React app for authentication, but the problem is that if someone open the app and don't do any activity up to an hour and after one hour click on a link, all the endpoints get called several times until the the app crash.
the index.jsx looks like this:
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route } from "react-router-dom";
import { Provider } from "react-redux";
import * as Sentry from "#sentry/react";
import store from "./Redux/store";
import App from "./App";
import ReactGA from "react-ga";
import { AuthProvider } from "./Containers/Auth/AuthProvider";
import * as firebaseconfig from "./deployment_config.json";
const fbconfig =
firebaseconfig.default[process.env.REACT_APP_ENV_NAME][
process.env.REACT_APP_CLIENT
];
Sentry.init({ dsn: fbconfig.SENTRY_DSN });
ReactGA.initialize(fbconfig.GOOGLE_ANALYTICS_TRACKING);
ReactDOM.render(
<BrowserRouter>
<AuthProvider fbconfig={fbconfig}>
<Provider store={store}>
<App />
</Provider>
</AuthProvider>
</BrowserRouter>,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
the App.jsx looks like:
import React, { useState, useContext, useEffect } from "react";
import { AuthContext } from "./Containers/Auth/AuthProvider";
import { withRouter } from "react-router-dom";
import { Provider, useDispatch, useSelector } from "react-redux";
//lots of other imports
const App = (props) => {
const { location } = props;
const { user, accessTokenExpired, loading, userRole, uiConfig } = useContext(
AuthContext
);
toast.configure();
const [checkSession, setCheckSession] = useState(false);
const [modal, setModal] = useState(true);
const change_body = useSelector((state) => state.Common.change_body);
const ErrorStatusState = useSelector(
(state) => state.GetGlobalError.ErrorCode
);
const configdata = useSelector((state) => state.GetConfig.data);
const dispatch = useDispatch();
useEffect(() => {
dispatch(GetConfig());
}, []);
axios.interceptors.response.use(
(response) => response,
(error) => {
if (error.message === "Network Error") {
setCheckSession(true);
}
return Promise.reject(error);
}
);
if (loading) {
return <PageLoader />;
}
if (user === null) {
return <Login uiConfig={uiConfig} user={user} userRole={userRole} />;
}
if (userRole === null) {
return <Verification />;
}
if (ErrorStatusState > 0 || configdata.maintenance) {
return (
<>
<Provider store={store}>
<GlobalStyle />
<Header />
<ContentWrapper>
<MainContentWrapper>
<AppWrapper>
<PageWrapper>
<MainWrapper>
<ErrorClassifyRender
ErrorStatus={ErrorStatusState}
maintenance={configdata.maintenance}
/>
</MainWrapper>
</PageWrapper>
</AppWrapper>
</MainContentWrapper>
</ContentWrapper>
</Provider>
</>
);
}
return (
<Provider store={store}>
<GlobalStyle />
<Header />
<ContentWrapper>
<MainContentWrapper>
<Navigation />
<AppWrapper>
<MainApp id="appDiv">
<PageWrapper>
<MainWrapper>
<Routes />
</MainWrapper>
</PageWrapper>
</MainApp>
</AppWrapper>
</MainContentWrapper>
</ContentWrapper>
</Provider>
);
};
export default withRouter(App);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
the AuthProvider looks like:
import { useEffect, createContext, useState } from "react";
import { isAfter } from "date-fns";
import firebase from "firebase/app";
import axios from "axios";
export const AuthContext = createContext();
export const AuthProvider = ({ children, fbconfig }) => {
const baseURL = process.env.REACT_APP_BACKEND_URL || window.location.origin;
const [user, setUser] = useState(null);
const [userRole, setUserRole] = useState(null);
const [isEmailVerified, setIsEmailVerified] = useState(false);
const [loading, setLoading] = useState(true);
const [verificationLoading, setVerificationLoading] = useState(false);
const [verifyRequestBlocked, setVerifyRequestBlocked] = useState(false);
const [emailSent, setEmailSent] = useState(false);
const [isNewUser, setIsNewUser] = useState(false);
const [accessTokenExpired, setAccessTokenExpired] = useState(false);
// Configure Firebase.
const config = {
apiKey: fbconfig.FIREABSE_API_KEY,
authDomain: fbconfig.FIREABSE_AUTH_DOMAIN,
projectId: fbconfig.FIREABSE_PROJECT_ID,
};
// Configure FirebaseUI.
if (!firebase.apps.length) {
firebase.initializeApp(config);
}
const sendEmail = async ({ email, isNewUser = false }) => {
// setLoading(true);
setVerificationLoading(true);
await axios
.post(`${baseURL}/send_email_verification`, {
email,
link: window.location.origin,
})
.then((response) => {
if (response.data.error) {
setUser(response);
setEmailSent(true);
setVerifyRequestBlocked(true);
setVerificationLoading(false);
} else {
setUser(response);
setEmailSent(true);
setVerificationLoading(false);
}
})
.catch((error) => {
setVerifyRequestBlocked(true);
setUser({ data: { email: email } });
setVerificationLoading(false);
setEmailSent(true);
});
setIsNewUser(isNewUser);
};
const uiConfig = {
signInFlow: "popup",
signInOptions: [
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
firebase.auth.EmailAuthProvider.PROVIDER_ID,
],
signInSuccessUrl: "/",
callbacks: {
signInSuccessWithAuthResult: async (authResult) => {
if (authResult.additionalUserInfo.isNewUser) {
await sendEmail({
isNewUser: true,
email: authResult.user.email,
});
} else {
setIsNewUser(false);
setLoading(false);
}
return false;
},
},
};
useEffect(() => {
firebase.auth().onAuthStateChanged(async (user) => {
setLoading(true);
if (user === null) {
setUser(null);
setLoading(false);
return;
}
if (user.emailVerified) {
setIsEmailVerified(user.emailVerified);
await user.getIdToken().then(async (accessToken) => {
setLoading(true);
try {
const result = await axios.post(
`${baseURL}/user`,
{
token: accessToken,
},
{ headers: { Authorization: `Bearer ${accessToken}` } }
);
const expirationTimeInSeconds =
JSON.parse(atob(accessToken.split(".")[1])).exp * 1000;
const dateResult = isAfter(
new Date(),
new Date(expirationTimeInSeconds)
);
if (dateResult) {
setAccessTokenExpired(true);
}
localStorage.setItem("token", accessToken);
localStorage.setItem("user", JSON.stringify(result.data));
setUser({
...result.data,
token: accessToken,
status: result.status,
});
setUserRole(result.data.role);
setLoading(false);
} catch (error) {
setUser(null);
setLoading(false);
}
});
} else {
setUser({ data: user });
setLoading(false);
}
});
}, []);
return (
<AuthContext.Provider
value={{
user,
accessTokenExpired,
emailSent,
verifyRequestBlocked,
isEmailVerified,
loading,
userRole,
uiConfig,
isNewUser,
verificationLoading,
sendEmail,
}}
>
{children}
</AuthContext.Provider>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
and one of the endpoint calls are like:
import React, { useRef, useState, useEffect } from "react";
import styled from "styled-components";
import { useSelector, useDispatch } from "react-redux";
import ReactEcharts from "echarts-for-react";
import {
fetchHistory,
} from "../../Redux";
const History = (props) => {
const barRef = useRef();
const {
brand,
parentStartDate,
parentEndDate,
start_date,
end_date,
selectedId,
tabID,
showTabs,
showComponent,
detailed,
graphs,
tooltipExtra,
} = props;
const predict_data_response = useSelector((state) => state.History.predict_data);
useEffect(() => {
dispatch(
fetchHistoryroi({
brand: brand.name,
parentStartDate,
parentEndDate,
componentName,
tabID,
nestedObjects,
})
);
}, [parentStartDate, parentEndDate]);
const predict_data = ResponseFilter({ responseFilterProps });
const options = {
toolbox: getToolBox(),
textStyle: {
color: colors.white,
},
tooltip: {
formatter: (params) => {
return getTooltip({
params,
avarage,
devider,
componentName: "History",
});
},
backgroundColor: colors.cardBg,
extraCssText: extraCssText,
},
grid: {
left: "7px",
right: "7px",
bottom: "3%",
containLabel: true,
},
xAxis: [
{
axisLabel: {
fontSize: 10,
},
type: "category",
data: labels,
axisTick: {
alignWithLabel: true,
},
},
{
axisLabel: {
fontSize: 10,
},
position: "bottom",
offset: 15,
axisLine: {
show: false,
},
axisTick: {
show: false,
},
data: labelsValues,
},
],
yAxis: [
{
axisLabel: {
fontSize: 10,
formatter: (value, index) => {
return value;
},
},
splitLine: {
lineStyle: {
color: colors.lightGray,
},
},
type: "value",
},
],
series: [
{
type: "bar",
label: {
show: true,
position: ["30%", "30%"],
formatter: ({ data }) => {
const { value } = data;
return value === avarage / devider && avarage !== 0 ? "NA" : "";
},
},
barMaxWidth: 40,
data: nums,
},
],
};
return (
<ContainerWrapper>
<ReactEcharts
ref={barRef}
option={options}
data-test="HistoryGraph"/>
</ContainerWrapper>
);
};
export default History;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
this is the image on what happens when someone click a link after an hour inactiviy
any help please?
Instead of parsing the access token directly, the Firebase SDK provides User#getIdTokenResult() which provides the metadata of the token along with the token itself.
const { token, expirationTime } = await firebase.auth().currentUser.getIdTokenResult();
Instead of storing the access token in the object you are passing to setUser(), a better option would be to add a Axios request interceptor, like you are doing for the responses. This will allow you to trigger requests using Axios as you normally would, but you don't have to add { headers: { Authorization: `Bearer ${accessToken}` } } on every request.
The following useEffect() attaches an interceptor on mount and detaches it on unmount, where the interceptor attaches the user's latest ID token to the request. Here, I've also added a clause where if the token were to expire in the next 5 minutes, it's forcefully refreshed.
useEffect(() => {
const authRequestInterceptor = axios.interceptors.request.use(async (config) {
if (!config.url.startsWith(baseURL))
return config; // this request doesn't target our API, leave untouched
const currentUser = firebase.auth().currentUser;
if (currentUser === null)
return config; // no user signed in, return config unmodified
// getIdTokenResult provides token metadata in the result
// will return current token, unless it's expired where a fresh token is grabbed
let { token, expirationTime } = await currentUser.getIdTokenResult();
const expiresMS = (new Date(expirationTime)).getTime();
if (expiresMS - Date.now() < 300000) {
// if the token will expire in the next 5 minutes,
// forcefully grab a fresh token
token = await currentUser.getIdToken(true);
}
// Attach the token to the request
config.headers.Authorization = `Bearer ${token}`;
return config;
});
return () => axios.interceptors.request.eject(authRequestInterceptor);
}, []);
Note: You should use the same useEffect() strategy for your response interceptor and you should use return firebase.auth().onAuthStateChanged(...) so that the cleanup function returned by onAuthStateChanged is accessible to its useEffect().