I'm trying to write a unit test from React with Apollo.
I found an example from https://dev-blog.apollodata.com/seamless-integration-for-graphql-and-react-6ffc0ad3fead
When trying that out I’m getting an error:
Error:
Uncaught (in react-apollo) Error: Network error: No more mocked responses for the query: query people {
allPeople(first: 1) {
people {
name
__typename
}
__typename
}
}
Test:
it('executes a query', (done) => {
const query = gql` query people { allPeople(first: 1) { people { name } } }`;
const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
const client = new ApolloClient({ networkInterface });
const withGraphQL = graphql(query);
class Container extends Component {
componentWillReceiveProps(props) {
expect(props.data.loading).to.be.false;
expect(props.data.allPeople).to.deep.equal(data.allPeople);
done();
}
render() {
return null;
}
};
const ContainerWithData = withGraphQL(Container);
mount(<ApolloProvider client={client}><ContainerWithData /></ApolloProvider>);
});
I know this is an old question, but maybe somebody gets here like I did :) These __typename fields were most likely your problem, the query ended up looking differently than the query you were passing to you mock interface. The error basically meant that it couldn't find any matching mocks for that query.
Anyway, here is a working example of this test, updated to work in Apollo 2.
it('executes a query', (done) => {
const query = gql` query people { allPeople(first: 1) { people { name } } }`;
const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
const withGraphQL = graphql(query);
class Container extends React.Component {
componentWillReceiveProps(props) {
expect(props.data.loading).toBeFalsy();
expect(props.data.error).toBeUndefined();
expect(props.data.allPeople.people[0].name).toEqual(data.allPeople.people[0].name);
done();
}
render() {
return null;
}
};
const ContainerWithData = withGraphQL(Container);
mount(<MockedProvider removeTypename mocks={[ { request: { query }, result: { data } } ]}><ContainerWithData /></MockedProvider>);
});
Related
I tried so hard to update Apollo cache after running Mutation, but i couldn't be able to remove 1 second delay after the mutation.
I followed 'ac3-state-management-examples' for solve this problem, but still couldn't find any problem.
This is my client-side code.
export const DELETE_ITEM_IN_CART = gql`
mutation DeleteItemInCart($cartItemId: String!) {
DeleteItemInCart(cartItemId: $cartItemId)
}
`;
export function useDeleteItemInCart() {
console.log(`DELETION START! ${Date()}`);
const [mutate, { data, error }] = useMutation<
DeleteItemInCartType.DeleteItemInCart,
DeleteItemInCartType.DeleteItemInCartVariables
>(DELETE_ITEM_IN_CART, {
update(cache, { data }) {
const deletedCartItemId = data?.DeleteItemInCart;
const existingCartItems = cache.readQuery<myCart>({
query: MY_CART,
});
if (existingCartItems && deletedCartItem && existingCartItems.myCart) {
cache.writeQuery({
query: MY_CART,
data: {
myCart: {
cartItem: existingCartItems.myCart.cartItem.filter(
t => t.id !== deletedCartItemId,
),
},
},
});
console.log(`DELETION OVER! ${Date()}`);
}
},
});
return { mutate, data, error };
}
And here's my server-side mutation
export const DeleteItemInCart = mutationField('DeleteItemInCart', {
args: {cartItemId: nonNull('String')},
type: nonNull('String'),
description: 'Delete an item in my cart',
resolve: (_, {cartItemId}, ctx) => {
const {prisma} = ctx;
try {
prisma.cartItem.delete({
where: {
id: cartItemId,
},
});
return cartItemId;
} catch (error) {
return cartItemId;
}
},
});
This is an example of Apollo-remote-state-mananagement
export const DELETE_TODO = gql`
mutation DeleteTodo ($id: Int!) {
deleteTodo (id: $id) {
success
todo {
id
text
completed
}
error {
... on TodoNotFoundError {
message
}
}
}
}
`
export function useDeleteTodo () {
const [mutate, { data, error }] = useMutation<
DeleteTodoTypes.DeleteTodo,
DeleteTodoTypes.DeleteTodoVariables
>(
DELETE_TODO,
{
update (cache, { data }) {
const deletedTodoId = data?.deleteTodo.todo?.id;
const allTodos = cache.readQuery<GetAllTodos>({
query: GET_ALL_TODOS
});
cache.writeQuery({
query: GET_ALL_TODOS,
data: {
todos: {
edges: allTodos?.todos.edges.filter((t) => t?.node.id !== deletedTodoId)
},
},
});
}
}
)
return { mutate, data, error };
}
Any advice?
1 second delay is inevitable using apollo cache?
I took a short video of my issue. i dont think it's inevitable...
In the mutation below I'm updating the avatarId for a person and I would like the new imageUrl to propagate everywhere it's needed, namely the <UserAvatar /> component which can be in a lot of components on the same page.
I know this new URL before ever hitting the UpdatePersonMutation so I'm not sure what I'm doing wrong.
Do I need to involve the store and / or use optimisticUpdater, and / or use subscriptions to get a new avatar image to show up everywhere instantly?
import { commitMutation } from 'react-relay';
import graphql from 'babel-plugin-relay/macro';
import modernEnvironment from '../Environment';
const mutation = graphql`
mutation UpdatePersonMutation($input: UpdatePersonInput!) {
updatePerson(input: $input) {
imageByAvatarId {
id
imageUrl // I know this before I ever reach this mutation
}
person {
id
avatarId
}
}
}
`;
const commit = (payload, callback) => {
const input = {
id: payload.id,
personPatch: {
avatarId: payload.avatarId,
},
};
return commitMutation(modernEnvironment, {
mutation,
variables: {
input,
},
optimisticResponse: {
updatePerson: {
imageByAvatarId: {
id: payload.imageId,
imageUrl: payload.imageUrl,
},
person: {
id: payload.id,
avatarId: payload.avatarId,
},
},
},
onCompleted: response => {
callback(response);
},
onError: error => {
console.log(error);
},
});
};
export default commit;
I have the following GRAPHQL subscription:
Schema.graphql
type Subscription {
booking: SubscriptionData
}
type SubscriptionData {
booking: Booking!
action: String
}
And this is the resolver subsrciption file
Resolver/Subscription.js
const Subscription = {
booking: {
subscribe(parent, args, { pubsub }, info) {
return pubsub.asyncIterator("booking");
}
}
};
export default Subscription;
Then I have the following code on the Mutation in question
pubsub.publish("booking", { booking: { booking }, action: "test" });
I have the follow subscription file in front end (React)
const getAllBookings = gql`
query {
bookings {
time
durationMin
payed
selected
activity {
name
}
}
}
`;
const getAllBookingsInitial = {
query: gql`
query {
bookings {
time
durationMin
payed
selected
activity {
name
}
}
}
`
};
class AllBookings extends Component {
state = { allBookings: [] }
componentWillMount() {
console.log('componentWillMount inside AllBookings.js')
client.query(getAllBookingsInitial).then(res => this.setState({ allBookings: res.data.bookings })).catch(err => console.log("an error occurred: ", err));
}
componentDidMount() {
console.log(this.props.getAllBookingsQuery)
this.createBookingsSubscription = this.props.getAllBookingsQuery.subscribeToMore(
{
document: gql`
subscription {
booking {
booking {
time
durationMin
payed
selected
activity {
name
}
}
action
}
}
`,
updateQuery: async (prevState, { subscriptionData }) => {
console.log('subscriptionData', subscriptionData)
const newBooking = subscriptionData.data.booking.booking;
const newState = [...this.state.allBookings, newBooking]
this.setState((prevState) => ({ allBookings: [...prevState.allBookings, newBooking] }))
this.props.setAllBookings(newState);
}
},
err => console.error(err)
);
}
render() {
return null;
}
}
export default graphql(getAllBookings, { name: "getAllBookingsQuery" })(
AllBookings
);
And I get the following response:
data: {
booking: {booking: {...} action: null}}
I get that I am probably setting up the subscription wrong somehow but I don't see the issue.
Based on your schema, the desired data returned should look like this:
{
"booking": {
"booking": {
...
},
"action": "test"
}
}
The first booking is the field on Subscription, while the second booking is the field on SubscriptionData. The object you pass to publish should have this same shape (i.e. it should always include the root-level subscription field).
pubsub.publish('booking', {
booking: {
booking,
action: 'test',
},
})
I'm trying to use react-admin with AWS Amplify library and AWS AppSync SDK.
I can't wrap my head around how to use use ra-data-graphql/ra-data-graphql-simple with AWS AppSync API for querying/mutating data. Trying to do a very basic test with master/examples/demo from https://github.com/marmelab/react-admin/.
Any guidance will be appreciated.
Currently I'm trying to use dataProvider similar to below:
src/dataProvider/appsync.js:
import gql from 'graphql-tag';
import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync';
import buildGraphQLProvider, { buildQuery } from 'ra-data-graphql-simple';
import { __schema as schema } from './schema';
const client = new AWSAppSyncClient({
url: "https://xxxx.appsync-api.us-east-1.amazonaws.com/graphql",
region: "us-east-1,
auth: {
type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
jwtToken: async () => (await Auth.currentSession()).getIdToken().getJwtToken(),
}
const myBuildQuery = introspection => (fetchType, resource, params) => {
const builtQuery = buildQuery(introspection)(fetchType, resource, params);
if (resource === 'listings' && fetchType === 'GET_LIST') {
return {
...builtQuery,
query: gql`
query getListings {
data: getListings {
items {
listingId
}
}
}`,
};
}
return builtQuery;
}
export default buildGraphQLProvider({ client: client, introspection: { schema }, buildQuery: myBuildQuery })
src/dataProvider/index.js:
export default type => {
switch (type) {
case 'graphql':
return import('./graphql').then(factory => factory.default());
case 'appsync':
return import('./appsync');
default:
return import('./rest').then(provider => provider.default);
}
};
src/App.js:
...
import dataProviderFactory from './dataProvider';
...
class App extends Component {
state = { dataProvider: null };
async componentDidMount() {
const dataProvider = await dataProviderFactory(
process.env.REACT_APP_DATA_PROVIDER
);
this.setState({ dataProvider });
}
...
src/dashboard/Dashboard.js:
...
fetchData() {
this.fetchListings();
}
async fetchListings() {
const { dataProvider } = this.props;
const { data: reviews } = await dataProvider(GET_LIST, 'listings');
console.log(listings)
}
...
Currently no data is returned from the API and the exception is thrown on await dataProvider(GET_LIST, 'listings'); saying call: [object Model] is not a function, however I see that buildGraphQLProvider promise was resolved succesfully to a function.
Can anyone suggest what I am doing wrong and what is the right way to approach the task?
I have a functioning web socket created with Apollo's WebSocketLink interface. I managed to subscribe to an event using subscribeToMore and a message is pushed by the server (can see it in the network tab). Unfortunately updateQuery function is never triggered. I wonder whether it's the message structure that is incorrect (therefore a wrong server implementation) or is it something wrong in my client code.
For reference I added the message sent from server:
and here the graphql config for my component:
import { graphql } from "react-apollo/index";
import Insights from 'components/insights/Insights';
import gql from "graphql-tag";
import { withRouter } from "react-router-dom";
import get from 'lodash/get';
const query = gql`
query CampaignInsights($campaignId: ID) {
campaigns (id: $campaignId) {
edges {
node {
insights {
campaignPlanningInsight {
campaign
plannedTotals {
totalOptimizationRules
totalOfferGroups
totalOffers
}
liveTotals {
totalOptimizationRules
totalOfferGroups
totalOffers
}
}
}
}
}
}
}
`;
const insightsSubscription = gql`
subscription onInsightsUpdated($campaignId: ID) {
campaignPlanningInsightUpdated(id: $campaignId) {
id
plannedTotals {
totalOptimizationRules
totalOfferGroups
totalOffers
}
liveTotals {
totalOptimizationRules
totalOfferGroups
totalOffers
}
}
}
`;
const InsightsWithData = graphql(query, {
options: (props) => {
return {
variables: {
campaignId: props.match.params.campaignId
}
}
},
props: ({ data: { campaigns, subscribeToMore }, ownProps: { match }
}) => {
return {
insights: get(campaigns,
'edges[0].node.insights[0].campaignPlanningInsight', null),
subscribeToInsightsUpdate: () => {
return subscribeToMore({
document: insightsSubscription,
variables: {
campaignId: match.params.campaignId
},
updateQuery: (prev, { subscriptionData }) => {
debugger; // never gets here
if (!subscriptionData.data) {
return prev;
}
}
})
}
}
}
})(Insights);
export default withRouter(InsightsWithData);
I believe the issue might be the id of the graphql-ws websocket protocol.
That id needs to match the one sent by the frontend in the GQL_START message. Otherwise, the component won't re-render on a new message.
For more details, look into the subscription-transport-ws protocol