I'm using graphql yoga and apollo client in my next app. Graphql yoga suggests this example for apollo client. link It works fine on non-serverless environment but when I deploy on vercel, subscriptions not working. I have tried checking for if it's running on browser but didn't do much. Here's my current apollo client
import {
ApolloLink,
Observable,
ApolloClient,
InMemoryCache,
} from '#apollo/client/core'
import { split } from '#apollo/client/link/core'
import { HttpLink } from '#apollo/client/link/http'
import { print, getOperationAST } from 'graphql'
import { setContext } from '#apollo/client/link/context'
import { getCookie } from 'cookies-next'
const isBrowser = typeof window !== 'undefined'
const token = getCookie('token')
class SSELink extends ApolloLink {
constructor() {
super()
}
request(operation) {
const url = new URL(`${process.env.NEXT_PUBLIC_APP_BASE_URL}/api/graphql`)
url.searchParams.append('query', print(operation.query))
if (operation.operationName) {
url.searchParams.append(
'operationName',
JSON.stringify(operation.operationName)
)
}
if (operation.variables) {
url.searchParams.append('variables', JSON.stringify(operation.variables))
}
if (operation.extensions) {
url.searchParams.append(
'extensions',
JSON.stringify(operation.extensions)
)
}
return new Observable((sink) => {
const eventsource = new EventSource(url.toString(), this.options)
eventsource.onmessage = function (event) {
const data = JSON.parse(event.data)
sink.next(data)
if (eventsource.readyState === 2) {
sink.complete()
}
}
eventsource.onerror = function (error) {
sink.error(error)
}
return () => eventsource.close()
})
}
}
const uri = `${process.env.NEXT_PUBLIC_APP_BASE_URL}/api/graphql`
const sseLink = isBrowser ? new SSELink({ uri }) : null
const httpLink = new HttpLink({ uri })
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
...(isBrowser && token && { authorization: `Bearer ${token}` }),
},
}
})
const link = isBrowser
? split(
({ query, operationName }) => {
const definition = getOperationAST(query, operationName)
return (
definition?.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
sseLink,
httpLink
)
: authLink.concat(httpLink)
export default new ApolloClient({
ssrMode: !isBrowser,
link,
cache: new InMemoryCache(),
})
I'm get the error in tittle when a action is dispatched to redux in a next application and i can't find the solution: the first action is correctly dispatched but others raises the error: TypeError: Cannot perform 'get' on a proxy that has been revoked, redux-toolkit and nextJS, you can see the project in the follow link : https://github.com/cpereiramt/BACKBONE-TEST
Below I share the mainly snippets of code and the configuration in general :
configuring store:
import {
configureStore,
EnhancedStore,
getDefaultMiddleware
} from "#reduxjs/toolkit"
import { MakeStore } from "next-redux-wrapper"
import { Env } from "../constants"
import { rootReducer, RootState } from "./reducers"
import { createWrapper } from 'next-redux-wrapper';
/**
* #see https://redux-toolkit.js.org/usage/usage-with-typescript#correct-typings-for-the-dispatch-type
*/
const middlewares = [...getDefaultMiddleware<RootState>()]
const store = configureStore({
reducer: rootReducer,
middleware: middlewares,
devTools: Env.NODE_ENV === "development",
})
const makeStore: MakeStore = (_?: RootState): EnhancedStore => store
export const wrapper = createWrapper(makeStore);
combineReducers
import { combineReducers } from "redux"
import { contactReducer } from "./contact"
/**
* Combine reducers
* #see https://redux-toolkit.js.org/usage/usage-with-typescript
*/
export const rootReducer = combineReducers({
contacts: contactReducer,
})
export type RootState = ReturnType<typeof rootReducer>
actions
import { createAsyncThunk } from "#reduxjs/toolkit";
import { Contact } from "../../model";
import { FeatureKey } from "../featureKey";
/**
* Fetch all contact action
*/
export const fetchAllContactsAction = createAsyncThunk(
`${FeatureKey.CONTACT}/fetchAll`,
async (arg: { offset: number; limit: number }) => {
const { offset, limit } = arg
const url = `/api/contact?offset=${offset}&limit=${limit}`
const result: Contact[] = await fetch(url, {
method: "get",
}).then((response: Response) => response.json())
return { contacts: result }
}
)
/**
* Fetch contact action
*/
export const fetchContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/fetch`,
async (arg: { id: number }) => {
const url = `/api/contact/${arg}`
const result: Contact = await fetch(url, {
method: "get",
}).then((response: Response) => response.json())
return { contacts: result }
}
)
/**
* Add contact action
*/
export const addContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/add`,
async (arg: { contact: Contact }) => {
const url = `/api/contact`
const result: Contact = await fetch(url, {
method: "post",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(arg),
}).then((response: Response) => response.json())
return { contacts: result }
}
)
/**
* Edit contact action
*/
export const editContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/edit`,
(arg: { contact: Contact }) => {
const { contact } = arg
const url = `/api/contact/${arg.id}`
const result: Contact = fetch(url, {
method: "put",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(contact),
}).then((response: Response) => response.json())
return { contacts: result }
}
)
/**
* Delete contact action
*/
export const deleteContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/delete`,
async (arg: { id: number }) => {
const url = `/api/contact/${arg}`
await fetch(url, {
method: "delete",
})
}
)
reducers
import { ActionReducerMapBuilder, createReducer } from "#reduxjs/toolkit"
import {
addContactAction,
deleteContactAction,
editContactAction,
fetchAllContactsAction,
fetchContactAction
} from "./action"
import { adapter, ContactState, initialState } from "./state"
/**
* CONTACT reducer
*/
export const contactReducer = createReducer(
initialState,
(builder: ActionReducerMapBuilder<ContactState>) =>
builder
.addCase(fetchAllContactsAction.pending, (state) => {
return { ...state, isFetching: true }
})
.addCase(fetchAllContactsAction.fulfilled, (state, action) => {
const { contacts } = action.payload
return adapter.setAll({ ...state, isFetching: false }, contacts)
})
.addCase(fetchAllContactsAction.rejected, (state) => {
return { ...state, isFetching: false }
})
//-------------------------------------------------------------------------------
.addCase(fetchContactAction.pending, (state, action) => {
const { id } = action.meta.arg
return { ...state, isFetching: true, selectedId: id }
})
.addCase(fetchContactAction.fulfilled, (state, action) => {
const { contacts } = action.payload
return adapter.upsertOne({ ...state, isFetching: false }, contacts)
})
.addCase(fetchContactAction.rejected, (state) => {
return { ...state, isFetching: false }
})
//-------------------------------------------------------------------------------
.addCase(addContactAction.pending, (state, action) => {
const { contact } = action.meta.arg
return { ...state, isFetching: true, selectedId: contact?.id }
})
.addCase(addContactAction.fulfilled, (state, action) => {
const { contacts } = action.payload
return adapter.addOne({ ...state, isFetching: false }, contacts)
})
.addCase(addContactAction.rejected, (state) => {
return { ...state, isFetching: false }
})
//-------------------------------------------------------------------------------
.addCase(editContactAction.pending, (state, action) => {
const { contact } = action.meta.arg
return { ...state, isFetching: true, selectedId: contact?.id }
})
.addCase(editContactAction.fulfilled, (state, action) => {
const { contacts } = action.payload
return adapter.updateOne(
{ ...state, isFetching: false },
{
id: contacts.id,
changes: contacts,
}
)
})
.addCase(editContactAction.rejected, (state) => {
return { ...state, isFetching: false }
})
//-------------------------------------------------------------------------------
.addCase(deleteContactAction.pending, (state, action) => {
const { id } = action.meta.arg
return { ...state, isFetching: true, selectedId: id }
})
.addCase(deleteContactAction.fulfilled, (state, action) => {
const { id } = action.meta.arg
return adapter.removeOne({ ...state, isFetching: false }, id)
})
.addCase(deleteContactAction.rejected, (state) => {
return { ...state, isFetching: false }
})
)
selectors
import { createSelector } from "#reduxjs/toolkit"
import { RootState } from "../reducers"
import { adapter, ContactState } from "./state"
const { selectAll, selectEntities } = adapter.getSelectors()
const featureStateSelector = (state: RootState) => state.contacts
const entitiesSelector = createSelector(featureStateSelector, selectEntities)
/**
* isFetching selector
*/
export const isFetchingSelector = createSelector(
featureStateSelector,
(state: ContactState) => state?.isFetching
)
/**
* selectedId selector
*/
export const selectedIdSelector = createSelector(
featureStateSelector,
(state: ContactState) => state?.selectedId
)
/**
* all contact selector
*/
export const allContactSelector = createSelector(featureStateSelector, selectAll)
/**
* contact selector
*/
export const contactSelector = createSelector(
entitiesSelector,
selectedIdSelector,
(entities, id) => (id ? entities[id] || null : null)
)
states
import { createEntityAdapter, EntityState } from "#reduxjs/toolkit"
import { Contact } from "../../model"
export interface ContactState extends EntityState<Contact> {
isFetching: boolean
selectedId: number | null
}
export const adapter = createEntityAdapter<Contact>({
selectId: (contacts: Contact) => contacts.id,
})
export const initialState: ContactState = adapter.getInitialState({
isFetching: false,
selectedId: null,
})
And the _app file
import CssBaseline from "#material-ui/core/CssBaseline";
import { ThemeProvider } from "#material-ui/styles";
import { NextPageContext } from 'next';
import App from "next/app";
import React from "react";
import { MuiTheme } from "../components/MuiTheme";
import { Store } from '../redux/store';
import { wrapper } from "../store/configureStore";
import "../styles/main.css";
interface AppContext extends NextPageContext {
store: Store;
}
class MyApp extends App<AppContext> {
componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector("#jss-server-side")
jssStyles?.parentNode?.removeChild(jssStyles)
}
render() {
const { Component, ...props } = this.props;
return (
<ThemeProvider theme={MuiTheme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...props} />
</ThemeProvider>
)
}
}
export default wrapper.withRedux(MyApp);
And in index file the action fetchAllContacts() work without problems.
import React, { useEffect } from "react";
import ContactTable from "../components/Table/";
import { useContact } from "../hooks";
import { Contact } from '../model/Contact';
import appStyles from "./indexStyles";
type Props = {}
function Index(props: Props) {
const { fetchAllContacts } = useContact();
const [contacts, setContacts] = React.useState<Contact[]>([])
useEffect(() => {
const results = fetchAllContacts();
results.then(data => console.log(data));
results.then(data => setContacts(data.contacts));
}, [])
const classes = appStyles(props)
return (
<div className={classes.indexBackground}>
<div className={classes.indexTabletDiv}>
<ContactTable contacts={contacts} />
</div>
</div>
);
}
export default Index
But when I try to use the action addContact() in another component the error is raised
page add
import { Button, createStyles, InputLabel, makeStyles, TextField, Theme } from "#material-ui/core";
import router from "next/router";
import React from "react";
import { useContact } from "../../hooks";
const useStyles = makeStyles((_: Theme) =>
createStyles({
root: {},
})
)
type Props = { }
const AddContact = (props: Props) => {
const { addContact } = useContact();
const newContact = {
id:'455666gghghttttytyty',
firstName: 'clayton',
lastName: 'pereira',
email: 'cpereiramt#gmail.com',
phone: '5565992188269',
}
const handleCreateContact = () => {
addContact(newContact);
}
const { } = props
return (
<div style={{margin: '10px', display: 'flex', justifyContent: 'space-between', wrap: 'wrap', flexDirection:'column'}}>
<>
<InputLabel>Name </InputLabel><TextField />
<InputLabel>Last Name </InputLabel><TextField />
<InputLabel>Email </InputLabel><TextField />
<div>
<Button variant="outlined" color="primary" onClick={() => handleCreateContact(newContact)} >
Create Contact</Button>
<Button variant="outlined" color="primary" onClick={() => router.push('/')} >
Back</Button>
</div>
</>
</div>
)
}
export default AddContact;
I think I see the issue.
First, the actual error message is Immer telling you that something is trying to modify the Proxy-wrapped state in a reducer, but long after the reducer has actually finished running. Normally that's impossible, because reducers are synchronous. So, there has to be some kind of async behavior going on.
The case reducers themselves seem basically okay, and mostly look like this:
.addCase(fetchAllContactsAction.pending, (state) => {
return { ...state, isFetching: true }
})
I'll point out that Immer lets you write state.isFetching = true instead, so you don't have to do object spreads :) But this code should run fine, and it's synchronous. So, what's the problem?
You didn't actually describe which actions are causing errors, so I'm having to guess. But, I think it's in one of the async thunks, and specifically, here:
export const editContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/edit`,
(arg: { contact: Contact }) => {
const { contact } = arg
const url = `/api/contact/${arg.id}`
// PROBLEM 1
const result: Contact = fetch(url, {
method: "put",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(contact),
}).then((response: Response) => response.json())
// PROBLEM 2
return { contacts: result }
}
Notice the line const result: Contact = fetch(). This doesn't have any await in there So, this is going to end up returning a Promise and saving it as result, and then that Promise ends up being returned as the contacts field.
I think that the object with the promise is being put into Redux state, wrapped by an Immer proxy, and then modified sometime later, and that's what's causing the error. But I'm not 100% sure because I don't know which actions are actually involved.
I've got several pages that I want to call a query inside of getServerSideProps to request the currentUser.
What I have currently is something like this:
import { NextPageContext } from 'next';
import { withAuth } from 'hoc/withAuth';
import { addApolloState, initializeApollo } from 'lib/apolloClient';
import { MeDocument } from 'generated/types';
import nookies from 'nookies';
import Profile from 'components/Profile';
const ProfilePage: React.FC = () => <Profile />;
export const getServerSideProps = async (
context: NextPageContext
): Promise<any> => {
// withAuth(context);
const client = initializeApollo();
const { my_token } = nookies.get(context);
await client.query({
query: MeDocument,
context: {
headers: {
authorization: my_token ? `Bearer ${my_token}` : '',
},
},
});
return addApolloState(client, { props: {} });
};
export default ProfilePage;
This works, and I can verify in my Apollo devtools that the cache is being updated with the User.
When I try to move the Apollo initialization and query in to a separate file, the cache is never updated for some reason.
Inside of a withAuth.tsx file, I had something like this:
import { NextPageContext } from 'next';
import { addApolloState, initializeApollo } from 'lib/apolloClient';
import { MeDocument } from 'generated/types';
import nookies from 'nookies';
export const withAuth = async (context: any,) => {
const client = initializeApollo();
const { gc_token } = nookies.get(context);
await client.query({
query: MeDocument,
context: {
headers: {
authorization: gc_token ? `Bearer ${gc_token}` : '',
},
},
});
return addApolloState(client, { props: {} });
};
With this, all I have to do is call withAuth() in the getServerSideProps. There are no errors, however the cache doesn't update.
How can I extract that code to a separate file correctly?
Thanks to #juliomalves in the comments, I simply forgot to return the withAuth function!
Here's how it looks now:
pages/index.tsx
export const getServerSideProps = async (
context: NextPageContext
): Promise<any> => await withAuth(context);
withAuth.tsx
/* eslint-disable #typescript-eslint/explicit-module-boundary-types */
import { getSession } from 'next-auth/client';
import redirectToLogin from 'helpers/redirectToLogin';
import { addApolloState, initializeApollo } from 'lib/apolloClient';
import { MeDocument } from 'generated/types';
import nookies from 'nookies';
export const withAuth = async (context: any) => {
const session = await getSession(context);
const isUser = !!session?.user;
// no authenticated session
if (!isUser) redirectToLogin();
const client = initializeApollo();
const { token } = nookies.get(context);
await client.query({
query: MeDocument,
context: {
headers: {
authorization: token ? `Bearer ${token}` : '',
},
},
});
return addApolloState(client, { props: {} });
};
I have page with getServerSideProps:
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const apolloClient = initializeApollo()
await apolloClient.query({
query: UserQuery,
variables: { id: Number(ctx.query.id) },
})
return {
props: { initialApolloState: apolloClient.cache.extract() },
}
}
And resolver for User:
resolve: async (_parent, { where }, ctx, info) => {
const select = new PrismaSelect(info).value
console.log(ctx) // {} why is empty when i use getServerSideProps?
const res = await ctx.db.user.findOne({
where: { id: 1 },
...select,
})
return null
},
Same client side query works fine, but for some reason when i use getServerSideProps ctx is empty. How to fix this?
I can pass ctx to const apolloClient = initializeApollo(null, ctx), but it's will not run apolloServer ctx resolver function with db property and ctx.db will be undefined.
Apollo-server:
const apolloServer = new ApolloServer({
schema,
async context(ctx: Ctx): Promise<Ctx> {
ctx.db = prisma
return ctx
},
})
export const apolloServerHandler = apolloServer.createHandler({ path: '/api/graphql' })
Apollo-client:
import { useMemo } from 'react'
import { ApolloClient, InMemoryCache, NormalizedCacheObject } from '#apollo/client'
import { IncomingMessage, ServerResponse } from 'http'
type ResolverContext = {
req?: IncomingMessage
res?: ServerResponse
}
const typePolicies = {
Test: {
keyFields: ['id'],
},
User: {
keyFields: ['id'],
},
}
let apolloClient: ApolloClient<NormalizedCacheObject>
function createIsomorphLink(context: ResolverContext = {}): any {
if (typeof window === 'undefined') {
const { SchemaLink } = require('#apollo/client/link/schema')
const { schema } = require('backend/graphql/schema')
return new SchemaLink({ schema, context })
} else {
const { HttpLink } = require('#apollo/client/link/http')
return new HttpLink({
uri: '/api/graphql',
credentials: 'same-origin',
})
}
}
function createApolloClient(context?: ResolverContext): ApolloClient<NormalizedCacheObject> {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: createIsomorphLink(context),
cache: new InMemoryCache({ typePolicies }),
})
}
export function initializeApollo(
initialState: any = null,
// Pages with Next.js data fetching methods, like `getStaticProps`, can send
// a custom context which will be used by `SchemaLink` to server render pages
context?: ResolverContext
): ApolloClient<NormalizedCacheObject> {
const _apolloClient = apolloClient ?? createApolloClient(context)
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// get hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract()
// Restore the cache using the data passed from getStaticProps/getServerSideProps
// combined with the existing cached data
_apolloClient.cache.restore({ ...existingCache, ...initialState })
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
// eslint-disable-next-line
export function useApollo(initialState: any): ApolloClient<NormalizedCacheObject> {
const store = useMemo(() => initializeApollo(initialState), [initialState])
return store
}
You need to aplly context resolver:
async contextResolver(ctx: Ctx): Promise<Ctx> {
ctx.db = prisma
return ctx
},
const apolloServer = new ApolloServer({
schema,
context: contextResolver,
})
Inside getServerSideProps:
export const getServerSideProps: GetServerSideProps = async (ctx) => {
await contextResolver(ctx)
const apolloClient = initializeApollo(null, ctx)
await apolloClient.query({
query: UserQuery,
variables: { id: Number(ctx.query.id) },
})
return {
props: { initialApolloState: apolloClient.cache.extract() },
}
}
Hello I am trying to make an API Post request using Firebase cloud function,Here are the code.
My effort is to get details from cloud and make an http request to my project's API. But i am getting an error of can not find module!!i have already put it.
so how to make an api call??
Here is my index.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import {TenantServiceProxy, CreateTenantInput} from '../../src/app/cloud/cloud-service';
let _tenantService: TenantServiceProxy;
const tenant = new CreateTenantInput();
admin.initializeApp();
export const onOrganizationUpdate =
functions.firestore.document('organizations/{id}').onUpdate(change => {
const after = change.after.data()
const payload = {
data: {
OrganizationId: String(after.OrganizationId),
Name: String(after.Name),
IsDeleted: String(after.IsDeleted)
}
}
console.log("updated", payload);
https.get('https://reqres.in/api/users?page=2', (resp: any) => {
let data = '';
// A chunk of data has been recieved.
resp.on('data', (chunk: any) => {
data += chunk;
});
// The whole response has been received. Print out the result.
resp.on('end', () => {
console.log("-------------------->",JSON.parse(data));
});
}).on("error", (err: any) => {
console.log("Error: " + err.message);
});
return admin.messaging().sendToTopic("OrganizationId", payload)
})
export const onOrganizationCreate =
functions.firestore.document('organizations/{id}').onCreate(change=>{
const onCreationTime =change.data()
const payload={
data:{
organizationId:String(onCreationTime.organizationId),
name:String(onCreationTime.name),
isDeleted:String(onCreationTime.isDeleted)
},
}
console.log("created",payload);
tenant.pkOrganization = payload.data.organizationId;
tenant.name = payload.data.name;
tenant.isDeleted = Boolean(payload.data.isDeleted);
_tenantService.createTenant(tenant).subscribe(()=>{
console.log("created",payload);
});
return admin.messaging().sendToTopic("OrganizationId",payload)
})
Here is the cloud.service.module.TS
//cloud service module
import { AbpHttpInterceptor } from '#abp/abpHttpInterceptor';
import { HTTP_INTERCEPTORS } from '#angular/common/http';
import { NgModule } from '#angular/core';
import * as ApiServiceProxies from '../../app/cloud/cloud-service';
#NgModule({
providers: [
ApiServiceProxies.TenantServiceProxy,
{ provide: HTTP_INTERCEPTORS, useClass: AbpHttpInterceptor, multi: true }
]
})
export class CloudServiceModule { }
Here is My api call service
#Injectable()
export class TenantServiceProxy {
private http: HttpClient;
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(#Inject(HttpClient) http: HttpClient, #Optional() #Inject(API_BASE_URL) baseUrl?: string) {
this.http = http;
this.baseUrl = baseUrl ? baseUrl : '';
}
createTenant(input: CreateTenantInput | null | undefined): Observable<void> {
let url_ = this.baseUrl + '/api/services/app/Tenant/CreateTenant';
url_ = url_.replace(/[?&]$/, '');
const content_ = JSON.stringify(input);
const options_: any = {
body: content_,
observe: 'response',
responseType: 'blob',
headers: new HttpHeaders({
'Content-Type': 'application/json',
})
};
return this.http.request('post', url_, options_).pipe(_observableMergeMap((response_: any) => {
return this.processCreateTenant(response_);
})).pipe(_observableCatch((response_: any) => {
if (response_ instanceof HttpResponseBase) {
try {
return this.processCreateTenant(<any>response_);
} catch (e) {
return <Observable<void>><any>_observableThrow(e);
}
} else {
return <Observable<void>><any>_observableThrow(response_);
}
}));
}
protected processCreateTenant(response: HttpResponseBase): Observable<void> {
const status = response.status;
const responseBlob =
response instanceof HttpResponse ? response.body :
(<any>response).error instanceof Blob ? (<any>response).error : undefined;
const _headers: any = {}; if (response.headers) { for (const key of response.headers.keys()) { _headers[key] = response.headers.get(key); } }
if (status === 200) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
return _observableOf<void>(<any>null);
}));
} else if (status !== 200 && status !== 204) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
return throwException('An unexpected server error occurred.', status, _responseText, _headers);
}));
}
return _observableOf<void>(<any>null);
}
}
I have defined the module in my services.