Apollo breaks when a client is stated in a mutation - graphql

I am using this recent feature of adding multiple clients and it is working well so far, but only in this case, the following code breaks when I state the client explicitly in the options of my mutation. I have followed this exact pattern with other components and haven't had any issue.
import { gql } from 'react-apollo';
const networkInterfaceAccounts = createNetworkInterface({
uri: ACCOUNTS_CLIENT_URL,
});
networkInterfaceAccounts.use([authMiddleware]);
const apolloClientAccounts = new ApolloClient({
networkInterface: networkInterfaceAccounts,
reduxRootSelector: state => state.apolloAccounts,
});
export const signupRequestMutation = gql`
mutation signupRequest($username: String!, $fname: String!, $lname: String!) {
signupRequest(username: $username, firstName: $fname, lastName: $lname) {
_id
}
}
`;
export const signupRequestOptions = {
options: () => ({
client: apolloClientAccounts,
}),
props: ({ mutate }) => ({
signupRequest: ({ username, fname, lname }) => {
return mutate({
variables: {
username,
fname,
lname,
},
});
},
}),
};
And the react component looks like this:
export default compose(
graphql(signupRequestMutation, signupRequestOptions),
withRouter,
reduxForm({
form: 'signup',
validate,
}),
)(SignupForm);
Intended outcome:
As with other components, I expect that the mutation works whether I pass client as an option or not.
options: () => ({
client: apolloClientAccounts,
}),
Actual outcome:
I am getting this error:
Uncaught Error: The operation 'signupRequest' wrapping 'withRouter(ReduxForm)' is expecting a variable: 'username' but it was not found in the props passed to 'Apollo(withRouter(ReduxForm))'
After that, I can submit the form.
Version
apollo-client#1.9.1
react-apollo#1.4.15

Related

RTK type Error when using injectedEndpoints with openApi

I define config file for openApi to create automatically endpoints with types:
const config: ConfigFile = {
schemaFile: 'https://example.com/static/docs/swagger.json',
apiFile: './api/index.ts',
apiImport: 'api',
outputFile: './api/sampleApi.ts',
exportName: 'sampleApi',
hooks: true,
};
export default config;
I used :
"#rtk-query/codegen-openapi": "^1.0.0-alpha.1"
"#reduxjs/toolkit": "^1.7.2",
Then I define an index.tsx that has
export const api = createApi({
baseQuery: axiosBaseQuery({ baseUrl: '' }),
endpoints: () => ({}),
});
and So I generate successfully my sampleApi.tsx file with all of endpoints and types.
like here:
const injectedRtkApi = api.injectEndpoints({
endpoints: (build) => ({
postUsersCollections: build.mutation<
PostUsersCollectionsApiResponse,
PostUsersCollectionsApiArg
>({
query: (queryArg) => ({
url: `/users/collections`,
method: 'POST',
body: queryArg.postCollectionBody,
}),
}),
getUsersCollections: build.query<
GetUsersCollectionsApiResponse,
GetUsersCollectionsApiArg
>({
query: (queryArg) => ({
url: `/users/collections`,
params: { name: queryArg.name },
}),
}),
overrideExisting: false,
});
export const {
usePostUsersCollectionsMutation,
useGetUsersCollectionsQuery
} = injectedRtkApi;
when in a component I use hook function useGetUsersCollectionsQuery as bellow I got an error that TypeError: Cannot read properties of undefined (reading 'subscriptions'). There is no lint typescript error related to typescript in my project.
const { data: collectionData = [] } = useGetUsersCollectionsQuery({});
It's Interesting that this hook called and I see API call in network tab but immediately I got this error. I remove this line and error is disappeared.
And Also for mutation hook I send data within it but I got 400 error. as Below:
const [postCollection, { data: newCollect }] =
usePostUsersCollectionsMutation();
...
const handleCreateItem = async () => {
const response: any = await postCollection({
postCollectionBody: { name: 'sample' },
}); }
Please help me! I really thanks you for taking time.
Finally I resolved it!
I should define reducerPath as this:
export const api = createApi({
reducerPath: 'api', <=== add this and define `api` in reducers
baseQuery: axiosBaseQuery({ baseUrl: '' }),
endpoints: () => ({}),
});

Error when building typedefs TypeError: Cannot read property 'some' of undefined

I am getting the following error when building Typedefs in Apollo Server:
return typeDef.definitions.some(definition => definition.kind === language_1.Kind.DIRECTIVE_DEFINITION &&
^
TypeError: Cannot read property 'some' of undefined
I tried to follow some solutions from here https://github.com/apollographql/apollo-server/issues/2961 but still, I am getting the error.
This is how I am creating the schema:
fs.readdirSync(__dirname)
.filter(dir => { console.log('dir', dir); return dir.indexOf('.') < 0 })
.forEach((dir) => {
const tmp = require(path.join(__dirname, dir)).default;
resolvers = merge(resolvers, tmp.resolvers);
typeDefs.push(tmp.types);
});
const schema = new ApolloServer({
typeDefs,
resolvers,
playground: {
endpoint: '/graphql',
settings: {
'editor.theme': 'light'
}
}
});
type.js
const Book = gql`
type Book {
title: String!
author: String!
}
`;
export const types = () => [Book];
export const typeResolvers = {
};
mutation.js
const Mutation = gql`
extend type Mutation {
addBook(book: BookInput): Book
}
`;
export const mutationTypes = () => [Mutation];
export const mutationResolvers = {
Mutation: {
addBook: async (_, args, ctx) => {
return []
}
}
};
index.js
export default {
types: () => [types, queryTypes, inputTypes, mutationTypes],
resolvers: Object.assign(queryResolvers, mutationResolvers, typeResolvers),
};
Any suggestions? What could I be missing?
I just had the same issue for the past 2 hours. I realized the file were i was instantiating my apollo server was being executed before the typedefs was created.
Simple way to test for this is to make a console.log(types, queryTypes, inputTypes, mutationTypes) right before the execution of const schema = new ApolloServer({ ....
One of them is undefined. Thanks.
After spending some time making changes, I finally got a working solution.
I had to make sure that typeDefs was an array of GraphQL Documents and not a type of [Function: types]. To do that, I removed unnecessary function syntax.
For example:
I replaced this export const types = () => [Book]; with this export const types = Book;
I replaced this types: () => [types, queryTypes, inputTypes, mutationTypes] with types: [types, queryTypes, inputTypes, mutationTypes]
... and pretty much every where I had () =>
Finally, before instantiating ApolloServer, instead of pushing tmp.types to the array of types, I used concat to use all defined graphql types in the I had defined the current file 'plus' the graphql types imported in every directory
typeDefs = typeDefs.concat(tmp.types);

GraphQLError: Syntax Error: Cannot parse the unexpected character "."

I am currently using Apollo-server as my GraphQL server of choice and anytime I try to run tests I get the error indicated in the title. However, my server implementation works as expected.
import 'cross-fetch/polyfill';
import ApolloClient, { gql } from 'apollo-boost';
const client = new ApolloClient({
uri: 'http://localhost:4000/',
});
const USER = {
invalidPassWord : {
name: 'Gbolahan Olagunju',
password: 'dafe',
email: 'gbols#example.com'
},
validCredentials: {
name: 'Gbolahan Olagunju',
password: 'dafeMania',
email: 'gbols#example.com'
},
usedEmail: {
name: 'Gbolahan Olagunju',
password: 'dafeMania',
email: 'gbols#example.com'
}
};
describe('Tests the createUser Mutation', () => {
test('should not signup a user with a password less than 8 characters', () => {
const createUser = gql`
mutation {
createUser(data: USER.invalidPassWord){
token
user {
name
password
email
id
}
}
}
`
client.mutate({
mutation: createUser
}).then(res => console.log(res));
})
})
There is a problem with the document used in your test. USER.invalidPassWord is not valid GraphQL syntax. Presumably you meant to use a template literal there to reference the USER variable defined earlier.
Doing the GrapQL tutorial at
https://www.howtographql.com/react-apollo/1-getting-started/
gives the same error.
I have razed the "same" issue at their site, though is the problem another in my case.
https://github.com/howtographql/howtographql/issues/1047

Relay commitUpdate callback with follow-up mutation and missing fragment

I have two GraphQL/Relay mutations that work fine separately. The first one creates an item. The second one runs a procedure for connecting two items.
GraphQL
createOrganization(
input: CreateOrganizationInput!
): CreateOrganizationPayload
createOrganizationMember(
input: CreateOrganizationMemberInput!
): CreateOrganizationMemberPayload
input CreateOrganizationInput {
clientMutationId: String
organization: OrganizationInput!
}
input CreateOrganizationMemberInput {
clientMutationId: String
organizationMember: OrganizationMemberInput!
}
# Represents a user’s membership in an organization.
input OrganizationMemberInput {
# The organization which the user is a part of.
organizationId: Uuid!
# The user who is a member of the given organization.
memberId: Uuid!
}
type CreateOrganizationPayload {
clientMutationId: String
# The `Organization` that was created by this mutation.
organization: Organization
# An edge for our `Organization`. May be used by Relay 1.
organizationEdge(
orderBy: OrganizationsOrderBy = PRIMARY_KEY_ASC
): OrganizationsEdge
# Our root query field type. Allows us to run any query from our mutation payload.
query: Query
}
I would like to be able to run the createOrganization mutation and then connect the user to the organization with the createOrganizationMember mutation. The second mutation takes two arguments, one of which is the newly created edge.
I tried passing the edge into the mutation, but it expects the mutation to be able to getFragment. How can I get the fragment for the payload edge so it can be passed into a mutation?
React-Relay
Relay.Store.commitUpdate(
new CreateOrganizationMutation({
organizationData: data,
user,
query,
}), {
onSuccess: response => {
Relay.Store.commitUpdate(
new CreateOrganizationMemberMutation({
organization: response.createOrganization.organizationEdge.node,
user,
})
);
},
}
);
fragments: {
user: () => Relay.QL`
fragment on User {
${CreateOrganizationMutation.getFragment('user')},
${CreateOrganizationMemberMutation.getFragment('user')},
}
`,
I solved this problem without changing any GraphQL:
I created a new Relay container, route, and queries object. It is configured as a
child route for the container where the first of two mutation occurs. The id for
the new edge is passed as a parameter via the route pathname. A router state
variable is also passed.
Routes
import {Route} from 'react-router';
function prepareProfileParams (params, {location}) {
return {
...params,
userId: localStorage.getItem('user_uuid'),
};
}
// ProfileContainer has the component CreateOrganizationForm, which calls
// the createOrganization mutation
<Route
path={'profile'}
component={ProfileContainer}
queries={ProfileQueries}
prepareParams={prepareProfileParams}
onEnter={loginBouncer}
renderLoading={renderLoading}
>
<Route path={'join-organization'}>
<Route
path={':organizationId'}
component={JoinOrganizationContainer}
queries={JoinOrganizationQueries}
renderLoading={renderLoading}
/>
</Route>
</Route>
CreateOrganizationForm.js
Relay.Store.commitUpdate(
new CreateOrganizationMutation({
organizationData: data,
user,
query,
}), {
onSuccess: response => {
const organizationId = response.createOrganization.organizationEdge.node.rowId;
router.push({
pathname: `/profile/join-organization/${organizationId}`,
state: {
isAdmin: true,
},
});
},
}
);
The new Relay container JoinOrganizationContainer will hook into a lifecycle
method to call the second mutation that we needed. The second mutation has an
onSuccess callback which does router.push to the page for the new object we
created with the first mutation.
JoinOrganizationContainer.js
import React from 'react';
import Relay from 'react-relay';
import CreateOrganizationMemberMutation from './mutations/CreateOrganizationMemberMutation';
class JoinOrganizationContainer extends React.Component {
static propTypes = {
user: React.PropTypes.object,
organization: React.PropTypes.object,
};
static contextTypes = {
router: React.PropTypes.object,
location: React.PropTypes.object,
};
componentWillMount () {
const {user, organization} = this.props;
const {router, location} = this.context;
Relay.Store.commitUpdate(
new CreateOrganizationMemberMutation({
user,
organization,
isAdmin: location.state.isAdmin,
}), {
onSuccess: response => {
router.replace(`/organization/${organization.id}`);
},
}
);
}
render () {
console.log('Joining organization...');
return null;
}
}
export default Relay.createContainer(JoinOrganizationContainer, {
initialVariables: {
userId: null,
organizationId: null,
},
fragments: {
user: () => Relay.QL`
fragment on User {
${CreateOrganizationMemberMutation.getFragment('user')},
}
`,
organization: () => Relay.QL`
fragment on Organization {
id,
${CreateOrganizationMemberMutation.getFragment('organization')},
}
`,
},
});
JoinOrganizationQueries.js
import Relay from 'react-relay';
export default {
user: () => Relay.QL`
query { userByRowId(rowId: $userId) }
`,
organization: () => Relay.QL`
query { organizationByRowId(rowId: $organizationId) }
`,
};
One unexpected benefit of doing things this way is that there is now a shareable url that can be used as an invite link for joining an organization in this app. If the user is logged in and goes to the link: <host>/profile/join-organization/<organizationRowId>, the mutation will run that joins the person as a member. In this use case, router.state.isAdmin is false, so the new membership will be disabled as an admin.

How do you dynamically control react apollo-client query initiation?

A react component wrapped with an apollo-client query will automatically initiate a call to the server for data.
I would like to fire off a request for data only on a specific user input.
You can pass the skip option in the query options - but this means the refetch() function is not provided as a prop to the component; and it appears that the value of skip is not assessed dynamically on prop update.
My use is case is a map component. I only want data for markers to be loaded when the user presses a button, but not on initial component mount or location change.
A code sample below:
// GraphQL wrapping
Explore = graphql(RoutesWithinQuery, {
options: ({ displayedMapRegion }) => ({
variables: {
scope: 'WITHIN',
targetRegion: mapRegionToGeoRegionInputType(displayedMapRegion)
},
skip: ({ targetResource, searchIsAllowedForMapArea }) => {
const skip = Boolean(!searchIsAllowedForMapArea || targetResource != 'ROUTE');
return skip;
},
}),
props: ({ ownProps, data: { loading, viewer, refetch }}) => ({
routes: viewer && viewer.routes ? viewer.routes : [],
refetch,
loading
})
})(Explore);
To include an HoC based on a condition affected by a props change, you could use branch from recompose.
branch(
test: (props: Object) => boolean,
left: HigherOrderComponent,
right: ?HigherOrderComponent
): HigherOrderComponent
check: https://github.com/acdlite/recompose/blob/master/docs/API.md#branch
For this specific example, would look something like:
const enhance = compose(
branch(
// evaluate condition
({ targetResource, searchIsAllowedForMapArea }) =>
Boolean(!searchIsAllowedForMapArea || targetResource != 'ROUTE'),
// HoC if condition is true
graphql(RoutesWithinQuery, {
options: ({ displayedMapRegion }) => ({
variables: {
scope: 'WITHIN',
targetRegion: mapRegionToGeoRegionInputType(displayedMapRegion)
},
}),
props: ({ ownProps, data: { loading, viewer, refetch } }) => ({
routes: viewer && viewer.routes ? viewer.routes : [],
refetch,
loading
})
})
)
);
Explore = enhance(Explore);
I have a similar use case, I wanted to load the data only when the user clicked.
I've not tried the withQuery suggestion given by pencilcheck above. But I've seen the same suggestion elsewhere. I will try it, but in the meantime this is how I got it working based off a discussion on github:
./loadQuery.js
Note: I'm using the skip directive:
const LOAD = `
query Load($ids:[String], $skip: Boolean = false) {
things(ids: $ids) #skip(if: $skip) {
title
}
`
LoadMoreButtonWithQuery.js
Here I use the withState higher-order function to add in a flag and a flag setter to control skip:
import { graphql, compose } from 'react-apollo';
import { withState } from 'recompose';
import LoadMoreButton from './LoadMoreButton';
import LOAD from './loadQuery';
export default compose(
withState('isSkipRequest', 'setSkipRequest', true),
graphql(
gql(LOAD),
{
name: 'handleLoad',
options: ({ids, isSkipRequest}) => ({
variables: {
ids,
skip: isSkipRequest
},
})
}
),
)(Button);
./LoadMoreButton.js
Here I have to manually "flip" the flag added using withState:
export default props => (
<Button onClick={
() => {
props.setSkipRequest(false); // setter added by withState
props.handleLoad.refetch();
}
}>+</Button>
);
Frankly I'm a little unhappy with this, as it is introduces a new set of wiring (composed in by "withState"). Its also not battle tested - I just got it working and I came to StackOverflow to check for better solutions.

Resources