Apollo Client 3: query returns results in Chrome's Network tab, but undefined in my app - react-apollo

I am fetching some data using #apollo/client v3. In Chrome's network tab (http results), I can see it returns data and errors (I am not worried about the error, I know why it is right now.):
{
data: {workItems: [,…]},…},
errors: [{message: "Error trying to resolve position."
}
However in my app, data returns undefined.
Here's my client config:
export const graphqlClient = new ApolloClient({
cache: new InMemoryCache(),
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach(error =>
console.log(
`[GraphQL error]: ${JSON.stringify(error, null, 2)}`
)
)
}
if (networkError) {
console.log(`[Network error]: ${networkError}`)
}
}),
apolloLink
])
})
And my query:
gql`
query WorkItems($ppm: String) {
workItems(where: { ppm: $ppm }) {
...WorkItemKanban
}
}
${workItemFragment.workItemKanban}
`
const useWorkItemListDataGraphql = (args: {
query: DocumentNode
variables: { parent: string } | { ppm: string }
}) => {
const { variables, query } = args
const { data, error, loading, refetch } = useQuery<
{ workItems: WorkItem[] },
{ parent: string } | { ppm: string }
>(query, {
pollInterval: 180000,
variables
})
// data returns undefined, but error shows the same error as in Chrome's network tab
return { ...data, error, loading, refetch }
}
I am not sure where to start to identify what goes wrong. Loading the data works when there is no error, but I reckon this is not normal behavior, it should always load the same as I can see in Chrome's tab.

As explained in Apollo documentation - Error policies
By default, the error policy treats any GraphQL Errors as network errors and ends the request chain
hence by default, Apollo client returns undefined if there is an error.
Adding errorPolicy: 'all' in the query's options, or in the client default options solves the problem.
Example:
const { loading, error, data } = useQuery(MY_QUERY, { errorPolicy: 'all' });

Related

Using Nextjs (getServerSideprops) with elasticsearch from lambda node causes an error

I use nextjs with elastic search cloud, also use amplify and lambda created from amplify rest api.
This is my function:
export async function getServerSideProps(context) {
let esItems;
try {
const { query } = context.query;
const apiName = 'APINAME';
const path = '/searchlist';
const myInit = {
response: true,
queryStringParameters: { query: query }
};
const response = await API.get(apiName, path, myInit);
esItems = response;
} catch (err) {
console.log(err)
}
return {
props: {
allProducts: esItems ? esItems.data.items : [],
}
};
}
I return 50 products from elastic and get this error:
502 ERROR
The request could not be satisfied.
The Lambda function returned invalid JSON: The JSON output is not parsable. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
This is lambda function:
app.get('/searchlist', async (req, res) => {
const { query } = req.query;
const client = new Client({
node: "https://elastic-cloud....",
auth: {
username: process.env.USERNAME,
password: process.env.PASSWORD
}
});
const searchQuery = {
query: {
multi_match: {
query: query,
type: "phrase",
fields: [
'manufacturerTypeDescription^8',
'manufacturerName^6',
'ean^4',
'_id^2',
]
}
}
}
const typeSearch = {
index: "product",
size: 50,
body: searchQuery
}
const r = await client.search(typeSearch);
const hits = r.body.hits;
const items = hits.hits.map((hit) => ({
_id: hit._id,
...hit._source,
}))
res.json({
success: 'get call succeed!',
items
});
});

Redux saga failed in production

I've been trying to tackle this problem more than 2 weeks now. Everything works fine in development mode. But not in production mode. The example below are shown using Redux Saga environment (I'm still new in redux saga). But I've tried re-do it using Context API. Unfortunately the problem still persists. (below are the images showing successful process in development mode & unsuccessful process in production mode)
successful in development mode
unsuccessful in production mode
My guess it could be something to do with status code 304 Not Modified. Since the data I tried to fetch not changing, thus it will use cached data in browser. But I don't know how to setup my server so that I can handle this issue. I have read a bunch of online threads. But none were able to resolve my issue.
You may have a look at my code right now. Bear in mind that everything works just fine in development mode. From the images above you can see that I don't have problem logging in. Just fetching & getting data to be displayed in dashboard got error.
client/src/redux/actions/Dashboard.js (Action)
import { SET_ISDASHBOARD, SET_LOADING, SET_ERROR } from '../sagas/types'
// Set Loading
export const setLoading = (status) => ({
type: SET_LOADING,
payload: status
})
// Set Error
export const setError = (error) => ({
type: SET_ERROR,
payload: { error: error.status, message: error.message }
})
// Dashboard
export const isDashboard = () => ({
type: SET_ISDASHBOARD
})
client/src/redux/reducers/Dashboard.jd (Reducer)
import { SET_ERROR, SET_LOADING, SET_ISDASHBOARD, SET_DASHBOARD } from '../sagas/types'
const initialState = {
user: {
id: '',
name: '',
email: ''
},
loading: false,
error: false,
message: ''
}
const reducer = (state = initialState, action) => {
switch(action.type) {
case SET_ISDASHBOARD:
return {
...state,
loading: true
}
case SET_DASHBOARD:
return {
...state,
user: {
...state.user,
id: action.payload.id,
email: action.payload.email,
name: action.payload.name
}
}
case SET_ERROR:
return {
...state,
error: action.payload.status,
message: action.payload.message
}
case SET_LOADING:
return {
...state,
loading: action.payload
}
default:
return state
}
}
export default reducer
client/src/redux/sagas/handlers/dashboard.js (Saga handlers)
import { call, put } from 'redux-saga/effects'
import { requestGetDashboard } from '../requests/dashboard'
import { SET_LOADING, SET_ERROR, SET_DASHBOARD } from '../types'
export function* handleGetDashboard(action) {
try {
const response = yield call(requestGetDashboard)
const result = response.data.data
console.log(response); console.log(result)
// dispatch set dashboard
yield put({ type: SET_DASHBOARD, payload: { id: result.id, email: result.email, name: result.name } })
} catch(error) {
// console.log(error); console.log(error.response)
const result = error.response.data
const payload = {
status: true,
message: result.error
}
// dispatch setError
yield put({ type: SET_ERROR, payload: payload })
}
// loading to false
yield put({ type: SET_LOADING, payload: false })
}
client/src/redux/sagas/requests/dashboard.js (Saga requests)
import axios from 'axios'
/** get dashboard */
export const requestGetDashboard = () => {
return axios.get(
'/api/v1/dashboard',
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('uid')}`
}
}
)
}
client/src/redux/sagas/rootSaga.js (Root Saga)
import {
SET_ISLOGIN, SET_ISLOGOUT, SET_ISAUTH,
SET_ISDASHBOARD,
} from './types'
import { takeLatest } from 'redux-saga/effects'
import { handleClientAuth, handlePostLogin, handlePostLogout } from './handlers/auth'
import { handleGetDashboard } from './handlers/dashboard'
export function* watcherSaga() {
// auth
yield takeLatest(SET_ISLOGIN, handlePostLogin)
yield takeLatest(SET_ISLOGOUT, handlePostLogout)
yield takeLatest(SET_ISAUTH, handleClientAuth)
// dashboard
yield takeLatest(SET_ISDASHBOARD, handleGetDashboard)
}
client/src/redux/sagas/types.js (Types)
/** for AUTH */
export const SET_ISLOGIN = 'SET_ISLOGIN'
export const SET_ISLOGOUT = 'SET_ISLOGOUT'
export const SET_ISAUTH = 'SET_ISAUTH'
export const SET_AUTH = 'SET_AUTH'
export const SET_LOADING = 'SET_LOADING'
export const SET_ERROR = 'SET_ERROR'
/** for DASHBOARD */
export const SET_ISDASHBOARD = 'SET_ISDASHBOARD'
export const SET_DASHBOARD = 'SET_DASHBOARD'
Please point me to any directions that could help get closer insight to this problem.

throw a descriptive error with graphql and apollo

Consider the following class:
// entity/Account.ts
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, Index, CreateDateColumn, UpdateDateColumn } from 'typeorm'
import { Field, Int, ObjectType } from 'type-graphql'
#ObjectType()
#Entity()
export class Account extends BaseEntity {
#Field(() => Int)
#PrimaryGeneratedColumn()
id: number
#Field()
#Column({ length: 50, unique: true })
#Index({ unique: true })
accountIdentifier: string
#Field({ nullable: true })
#Column({ length: 100 })
name?: string
}
With it's corresponding resolver:
// AccountResolver.ts
#Resolver()
export class AccountResolver {
#Mutation(() => Account)
async addAccount(#Arg('options', () => AccountInput) options: AccountInput) {
try {
// if (!options.accountIdentifier) {
// throw new Error(`Failed adding account: the accountIdentifier is missing`)
// }
return await Account.create(options).save()
} catch (error) {
if (error.message.includes('Cannot insert duplicate key')) {
throw new Error(
`Failed adding account: the account already exists. ${error}`
)
} else {
throw new Error(`Failed adding account: ${error}`)
}
}
}
}
Jest test file
// AccountResolver.test.ts
describe('the addAccount Mutation', () => {
it('should throw an error when the accountIdentifier is missing', async () => {
await expect(
client.mutate({
mutation: gql`
mutation {
addAccount(
options: {
name: "James Bond"
userName: "James.Bond#contoso.com"
}
) {
accountIdentifier
}
}
`,
})
).rejects.toThrowError('the accountIdentifier is missing')
})
The field accountIdentifier is mandatory and should throw a descriptive error message when it's missing in the request. However, the error thrown is:
"Network error: Response not successful: Received status code 400"
What is the correct way to modify the error message? I looked at type-graphql with the class-validators and made sure that validate: true is set but it doesn't give a descriptive error.
EDIT
After checking the graphql playground, it does show the correct error message by default. The only question remaining is how write the jest test so it can read this message:
{
"error": {
"errors": [
{
"message": "Field AccountInput.accountIdentifier of required type String! was not provided.",
Thank you for any help you could give me.
The ApolloError returned by your client wraps both the errors returned in the response and any network errors encountered while executing the request. The former is accessible under the graphQLErrors property, the latter under the networkError property. Instea dof using toThrowError, you should use toMatchObject instead:
const expectedError = {
graphQLErrors: [{ message: 'the accountIdentifier is missing' }]
}
await expect(client.mutate(...)).rejects.toMatchObject(expectedError)
However, I would suggest avoiding using Apollo Client for testing. Instead, you can execute operations directly against your schema.
import { buildSchema } from 'type-graphql'
import { graphql } from 'graphql'
const schema = await buildSchema({
resolvers: [...],
})
const query = '{ someField }'
const context = {}
const variables = {}
const { data, errors } = await graphql(schema, query, {}, context, variables)

Apollo client QUERIES not sending headers to server but mutations are fine

I hooked up a front end to a graphql server. Most if not all the mutations are protected while all the queries are not protected. I have an auth system in place where if you log in, you get an access/refresh token which all mutations are required to use. And they do which is great, backend receives the headers and everything!
HOWEVER. There is one query that needs at least the access token to distinguish the current user! BUT the backend does not receive the two headers! I thought that the middlewareLink I created would be for all queries/mutations but I'm wrong and couldn't find any additional resources to help me out.
So here's my setup
apollo-client.js
import { InMemoryCache } from "apollo-cache-inmemory"
import { persistCache } from "apollo-cache-persist"
import { ApolloLink } from "apollo-link"
import { HttpLink } from "apollo-link-http"
import { onError } from "apollo-link-error"
import { setContext } from "apollo-link-context"
if (process.browser) {
try {
persistCache({
cache,
storage: window.localStorage
})
} catch (error) {
console.error("Error restoring Apollo cache", error)
}
}
const httpLink = new HttpLink({
uri: process.env.GRAPHQL_URL || "http://localhost:4000/graphql"
})
const authMiddlewareLink = setContext(() => ({
headers: {
authorization: localStorage.getItem("apollo-token") || null,
"x-refresh-token": localStorage.getItem("refresh-token") || null
}
}))
const afterwareLink = new ApolloLink((operation, forward) =>
forward(operation).map(response => {
const context = operation.getContext()
const {
response: { headers }
} = context
if (headers) {
const token = headers.get("authorization")
const refreshToken = headers.get("x-refresh-token")
if (token) {
localStorage.setItem("apollo-token", token)
}
if (refreshToken) {
localStorage.setItem("refresh-token", refreshToken)
}
}
return response
})
)
const errorLink = onError(({ graphQLErrors, networkError }) => {
...
// really long error link code
...
})
let links = [errorLink, afterwareLink, httpLink]
if (process.browser) {
links = [errorLink, afterwareLink, authMiddlewareLink, httpLink]
}
const link = ApolloLink.from(links)
export default function() {
return {
cache,
defaultHttpLink: false,
link
}
}
Is there a way to target ALL mutations/queries with custom headers not just mutations? Or apply some headers to an individual query since I could probably put that as an app middleware?
edit: Haven't solved the SSR portion of this yet.. will re-edit with the answer once I have.

gatsbyjs query data from graphcms with status condition throw error object undefiend

Hello i have a gatsbyjs site that i tried to pull data of model 'job' from graphcms. if i pull alljob. the query works fine but if i try to put condition to pull only the job with the status field pubished. it didnt pull any data and throw an error:
TypeError: Cannot read property 'allJob' of undefined
Here's my gatsby-node.js:
const path = require(`path`);
const makeRequest = (graphql, request) => new Promise((resolve, reject) => {
resolve(
graphql(request).then(result => {
if (result.errors) {
reject(result.errors)
}
return result;
})
)
});
exports.createPages = ({ boundActionCreators, graphql }) => {
const { createPage } = boundActionCreators;
const getJobs = makeRequest(graphql, `
{
allJob(where: {status: PUBLISHED}) {
edges{
node{
id
}
}
}
}
`).then(result => { result.data.allJob.edges.forEach(({ node }) => {
createPage({
path: `/job/${node.id}`,
component: path.resolve(`src/templates/jobTemplate.js`),
context: {
id: node.id,
}
})
console.log(node.id)
})
}
)
return getJobs;
};
Gatsby doesn't understand allJob(where: {status: PUBLISHED}) as it's the wrong syntax.
You would want to use filter instead. I can't give you an example as I don't know how the structure is but can advise you to run gatsby develop and go to GraphiQL (http://localhost:8000/___graphql) and use it's autocomplete feature (Ctrl + Space) to get the right filter.
More information: https://www.gatsbyjs.org/docs/graphql-reference/#filter

Resources