I have a very weird issue with Apollo Client.
We are using apollo-client#1.9.3 with react (react-apollo#1.4.16).
In our project, we notice that apollo always wait for 1 to 2 seconds before sending the request.
Below is a screenshot of the situation:
This is how our client config looks like:
const customNetworkInterface = {
query: request =>
fetch('/graphql', {
method: 'POST',
credentials: 'include',
mode: 'cors',
cache: 'default',
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
body: JSON.stringify({
...request,
query: print(request.query),
}),
})
.then(resp => resp.json())
.then(({ data, errors }) => {
if (errors) {
const userErrors = errors
.filter(({ code }) => +code >= 400 && +code <= 401)
.map(({ message }) => message)
.join('\n');
const serverErrors = errors
.filter(
({ code }) => !code || (+code < 400 && +code > 401)
)
.map(({ message }) => message)
.join('\n');
if (serverErrors.length > 0) {
error(serverErrors);
if (isProduction) {
window.triggerAlert(
'danger',
'The server encountered an error. Our technical team has been notified.'
);
} else {
window.triggerAlert('danger', serverErrors);
}
} else if (userErrors.length > 0) {
window.triggerAlert('danger', userErrors);
}
}
return { data, errors };
}),
};
const networkInterface = createNetworkInterface({
uri: '/graphql',
opts: {
credentials: 'same-origin',
},
});
networkInterface.useAfter([
{
applyAfterware({ response }, next) {
response
.clone()
.json()
.then(responseJson => {
if (responseJson.errors) {
error(
responseJson.errors
.map(({ message }) => message)
.join('\n')
);
}
next();
});
},
},
]);
export const client = new ApolloClient({
networkInterface: customNetworkInterface,
queryDeduplication: true,
addTypename: true,
});
Then the query code is with react-apollo:
graphql(RaceResultsQuery, {
props: ({ ownProps, data }) => ({
race_results: _.get(data, 'me.my_race_results', []),
}),
}),
This would need a complete, minimal example to provide an answer for sure (delete as much of your code as possible with the issue still happening).
My guess would be that you have a parent component with a very expensive query and it only renders the component with the delayed query after the expensive query returned.
Related
I have a Cypress test:
describe('Create a session ', () => {
it('creates a session', () => {
cy.request({
method: 'POST',
url: `${Cypress.env('apiURL')}/api/v1/user/login/`,
form: true,
body: {
email: Cypress.env('email'),
password: Cypress.env('password'),
},
}).then((response) => {
expect(response.status).to.eq(200);
cy.task('setKey', response.body.data.key);
});
});
});
This POST returns some session data needed to create a dummy account:
describe('Create a company ', () => {
it('creates a company', () => {
cy.task('getKey')
.then((data: Key) => {
key = data;
})
.then(() => {
createNonce();
cy.request({
method: 'POST',
url: `${Cypress.env('apiURL')}/api/v1/cli/`,
headers: {
'X-Auth-Timestamp': epochTime(),
'X-Auth-Key': key.key,
'X-Auth-Nonce': nonce,
'X-Auth-Signature': createSignature(),
},
body: {
args: ['seeder', 'create', 'abc1'],
},
}).then((response) => {
expect(response.status).to.eq(200);
// TODO: we need some REST endpoints to return a JSON object instead of a string
data = JSON.parse(response.body.substring(response.body.indexOf('{')));
cy.task('setCompany', data);
});
});
});
});
I'm not sure I need these functions to be tests since they don't test anything, but just do a POST request. Is it possible to maybe move the functionality into a cypress task?
You can add the post request in your commands file:
function postRequest() {
cy.request({
method: 'POST',
url: `${Cypress.env('apiURL')}/api/v1/cli/`,
headers: {
'X-Auth-Timestamp': epochTime(),
'X-Auth-Key': key.key,
'X-Auth-Nonce': nonce,
'X-Auth-Signature': createSignature(),
},
body: {
args: ['seeder', 'create', 'abc1'],
},
})
}
Cypress.Commands.add('postRequest', postRequest)
An assuming all the rest of your code is fine, and you want only to abstract the logic; then in your test you can invoke that command:
describe('Create a company ', () => {
it('creates a company', () => {
cy.task('getKey')
.then((data: Key) => {
key = data;
})
.then(() => {
createNonce();
cy.postRequest().then((response) => {
expect(response.status).to.eq(200);
data = JSON.parse(response.body.substring(response.body.indexOf('{')));
cy.task('setCompany', data);
});
});
});
});
You can move these into before() or beforeEach() so they will be separate from your tests.
describe('Create a company ', () => {
before(() => {
cy.task('getKey')
.then((data: Key) => {
key = data;
})
.then(() => {
createNonce();
cy.request({
method: 'POST',
url: `${Cypress.env('apiURL')}/api/v1/cli/`,
headers: {
'X-Auth-Timestamp': epochTime(),
'X-Auth-Key': key.key,
'X-Auth-Nonce': nonce,
'X-Auth-Signature': createSignature(),
},
body: {
args: ['seeder', 'create', 'abc1'],
},
}).then((response) => {
expect(response.status).to.eq(200);
// TODO: we need some REST endpoints to return a JSON object instead of a string
data = JSON.parse(response.body.substring(response.body.indexOf('{')));
cy.task('setCompany', data);
});
});
})
it('creates a company', () => {
//test code
});
});
I would like to use Cypress for API testing. My goal is to extract a part of the API response and pass it to another API request. Here's a sample code:
Cypress.Commands.add('createCustomer', () => {
return cy.request({
method: 'POST',
url: 'api/v1/Customers',
headers: {
'Content-Type': 'application/json'
},
body: {
// sample content
}
}).then((response) => {
return new Promise(resolve => {
expect(response).property('status').to.equal(201)
expect(response.body).property('id').to.not.be.oneOf([null, ""])
const jsonData = response.body;
const memberId = jsonData.id
resolve(memberId)
return memberId
})
})
})
With this code, I am getting [object%20Object] as the result.
Hoping for some feedback.
So you are adding the id generated by the POST to a subsequent GET request?
Try returning the id without using a Promise, I don't think you need one at that point since the response has already arrived.
}).then((response) => {
expect(response).property('status').to.equal(201)
expect(response.body).property('id').to.not.be.oneOf([null, ""])
const jsonData = response.body;
const memberId = jsonData.id;
return memberId;
})
Url for GET
cy.createCustomer().then(id => {
const url = `api/v1/Customers${id}`;
...
or
cy.createCustomer().then($id => {
const id = $id[0]; // Not quite sure of the format, you may need to "unwrap" it
const url = `api/v1/Customers${id}`;
...
If you want to pass response from API Request 1 to API Request 2, you can do something like this:
describe('Example to demonstrate API Chaining in Cypress', function () {
it('Chain two API requests and validate the response', () => {
//Part 1
cy.request({
method: 'GET',
url: 'https://www.metaweather.com/api/location/search/?query=sn',
}).then((response) => {
const location = response.body[0].title
return location
})
//Part 2
.then((location) => {
cy.request({
method: 'GET',
url: 'https://www.metaweather.com/api/location/search/?query=' + location
}).then((response) => {
expect(response.status).to.eq(200)
expect(response.body[0]).to.have.property('title', location)
})
})
})
})
Your code seems to be failing during the initial request, not during the subsequent actions. I am far from a Javascript expert, but you seem to have some unnecessary complexity in there. Try simplifying your command like this and see if you can at least get a successful request to go through:
Cypress.Commands.add('createCustomer', () => {
cy.request({
method: 'POST',
url: 'api/v1/Customers',
headers: {
'Content-Type': 'application/json'
},
body: {
// sample content
}
})
})
And if that works, keep going:
Cypress.Commands.add('createCustomer', () => {
cy.request({
method: 'POST',
url: 'api/v1/Customers',
headers: {
'Content-Type': 'application/json'
},
body: {
// sample content
}
}).then((response) => {
expect(response).property('status').to.equal(201)
expect(response.body).property('id').to.not.be.oneOf([null, ""])
const jsonData = response.body;
const memberId = jsonData.id
return memberId
})
})
My reducer file is below
const slice = createSlice({
name: "hotels",
initialState: {
list: [],
loading: false,
lastFetch: null,
},
reducers: {
hotelsRequested: (hotels) => {
hotels.loading = true;
},
hotelsRequestFailed: (hotels) => {
hotels.loading = false;
},
hotelsReceived: (hotels, action) => {
hotels.list = action.payload;
hotels.loading = false;
hotels.lastFetch = Date.now();
},
hotelEnabled: (hotels, action) => {
const { slug } = action.payload;
const index = hotels.list.findIndex((hotel) => hotel.slug === slug);
hotels.list[index].active = true;
},
},
});
export const {
hotelsReceived,
hotelsRequestFailed,
hotelsRequested,
hotelEnabled,
} = slice.actions;
export default slice.reducer;
//Action creators
export const loadHotels = () => (dispatch, getState) => {
const { lastFetch } = getState().entities.hotels;
const diffInMinutes = moment().diff(lastFetch, "minutes");
if (diffInMinutes < 10) return;
dispatch(
hotelApiCallBegan({
url: hotelUrl,
onStart: hotelsRequested.type,
onSuccess: hotelsReceived.type,
onError: hotelsRequestFailed.type,
})
);
};
export const enableHotel = (slug) =>
hotelApiCallBegan(
{
url: `${hotelUrl}${slug}/partial-update/`,
method: "put",
data: { active: true },
onSuccess: hotelEnabled.type,
},
console.log(slug)
);
My api request middleware function is as follows
export const hotelsApi = ({ dispatch }) => (next) => async (action) => {
if (action.type !== actions.hotelApiCallBegan.type) return next(action);
const {
onStart,
onSuccess,
onError,
url,
method,
data,
redirect,
} = action.payload;
if (onStart) dispatch({ type: onStart });
next(action);
try {
const response = await axiosInstance.request({
baseURL,
url,
method,
data,
redirect,
});
//General
dispatch(actions.hotelApiCallSuccess(response.data));
//Specific
if (onSuccess) dispatch({ type: onSuccess, payload: response.data });
} catch (error) {
//general error
dispatch(actions.hotelApiCallFailed(error.message));
console.log(error.message);
//Specific error
if (onError) dispatch({ type: onError, payload: error.message });
console.log(error.message);
}
};
Could anyone point me in the right direction of how to add an optimistic update reducer to this code. Currently on hitting enable button on the UI there's a lag of maybe second before the UI is updated. Or maybe the question, is do i create another middleware function to handle optimistic updates? If yes how do i go about that? Thanks
I apply all the solutions in the internet about this error but still i have this problem
i don't know where is the problem !!
1- i checked the link.
2- i checked the query.
(i use React-Apollo-GraphQL).
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem("authToken") || "";
return {
headers: {
...headers,
Authorization: token ? `JWT ${token}` : ""
}
};
});
const httpLink = new createHttpLink({
uri: 'http://localhost:8000/graphql/',
fetchOptions: {
credentials: "include"
},
});
const wsLink = () => {
const token = localStorage.getItem("authToken");
return new WebSocketLink({
uri: `ws://localhost:8000/graphql/`,
options: {
reconnect: true,
timeout: 30000,
connectionParams: {
Authorization: `JWT ${token}`,
authToken: token
}
}
});
};
const link = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink(),
authLink.concat(httpLink),
)
const client = new ApolloClient({
link,
cache: new InMemoryCache(),
clientState: {
defaults: {
isLoggedIn: !!localStorage.getItem("authToken")
}
},
})
can you help me please
Thank you.
** Note When i use this code(below) it work successful.
const client = new ApolloClient({
uri: "http://localhost:8000/graphql/",
fetchOptions: {
credentials: "include"
},
request: operation => {
const token = localStorage.getItem("authToken") || "";
operation.setContext({
headers: {
Authorization: `JWT ${token}`
}
});
},
clientState: {
defaults: {
isLoggedIn: !!localStorage.getItem("authToken")
}
},
});
apollo-boost does not support configuring the link or cache options for its client. These are the only supported configuration options. If you're passing in some other parameter, you should be seeing a warning your console about it.
If you need to customize your ApolloClient instance, you need to migrate to using the full client.
Restarting my mac solved the issue !
I'm trying to implement custom rest client build on top of simple fetch.
If 401-403 response received, it must "redirect" app to login page.
According documentation, if 401-403 error received, it will magically calls authClient with the AUTH_ERROR, but it doesn't.
Can someone explain, how to connect it?
I'm trying to call rest client from component: It's simple reimplementation of 'simpleRestClient'
componentDidMount() {
restClient(CREATE, 'api/method', {
CurrentTime: new Date()
})
.then(o =>
{
this.setState({ Msg: Object.values(o.data.ServerTime) });
});
}
restclient implementation:
export const fetchJson = (url, options = {}) => {
const requestHeaders =
options.headers ||
new Headers({
Accept: 'application/json',
});
if (
!requestHeaders.has('Content-Type') &&
!(options && options.body && options.body instanceof FormData)
) {
requestHeaders.set('Content-Type', 'application/json');
}
if (options.user && options.user.authenticated && options.user.token) {
requestHeaders.set('Authorization', options.user.token);
}
return fetch(url, { ...options, headers: requestHeaders })
.then(response =>
response.text().then(text => ({
status: response.status,
statusText: response.statusText,
headers: response.headers,
body: text,
}))
)
.then(({ status, statusText, headers, body }) => {
if (status < 200 || status >= 300) {
return Promise.reject(
new HttpError(
(json && json.message) || statusText,
status,
json
)
);
}
let json;
try {
json = JSON.parse(body);
} catch (e) {
// not json, no big deal
}
return { status, headers, body, json };
});
};
const httpClient = (url, options = {}) => {
if (!options.headers) {
options.headers = new Headers({ Accept: 'application/json' });
}
return fetchJson(url, options);
}
Have you tried rejecting the promise with an Error rather than an HttpError?