I'm using Apollo to call a rest endpoint that takes variables from query string:
/api/GetUserContainers?showActive=true&showSold=true
I'm having trouble figuring out how to pass variables to the query, so it can then call the correct url. From looking at apollo-link-rest docs and a few issues I think I'm supposed to use pathBuilder but this is not documented and I haven't been able to get it working.
So far I've defined my query like this:
getUserContainersQuery: gql`
query RESTgetUserContainers($showActive: Boolean, $showSold: Boolean, $pathBuilder: any) {
containerHistory #rest(type: "ContainerHistoryResponse", pathBuilder: $pathBuilder) {
active #type(name: "UserContainer") {
...ContainerFragment
}
sold #type(name: "UserContainer") {
...ContainerFragment
}
}
}
${ContainerFragment}
`
and calling it in my component like this, which does not work:
import queryString from 'query-string'
// ...
const { data } = useQuery(getUserContainersQuery, {
variables: {
showActive: true,
showSold: false,
pathBuilder: () => `/api/GetUserContainers?${queryString.stringify(params)}`,
},
fetchPolicy: 'cache-and-network',
})
The only way I got this to work was by passing the fully constructed path to the query from the component:
// query definition
getUserContainersQuery: gql`
query RESTgetUserContainers($pathString: String) {
containerHistory #rest(type: "ContainerHistoryResponse", path: $pathString) { // <-- pass path here, instead of pathBuilder
// same response as above
}
}
`
// component
const params = {
showActive: true,
showSold: false,
}
const { data } = useQuery(getUserContainersQuery, {
variables: {
pathString: `/api/GetUserContainers?${queryString.stringify(params)}`,
},
fetchPolicy: 'cache-and-network',
})
These seems to me like a really hacky solution which I'd like to avoid.
What is the recommended way to handle this query string problem?
You shouldn't need to use the pathBuilder for simple query string params. You can pass your params directly as variables to useQuery then pass then directly into teh path using the {args.somearg} syntax. The issue I see is you've not defined the variables your using for you query containerHistory bu only in the query alias RESTgetUserQueries. If updated is should look like this:
// query definition
getUserContainersQuery: gql`
query RESTgetUserContainers($showActive: Boolean, $showSold: Boolean) {
// pass the variables to the query
containerHistory(showActive:$showActive, showSold:$showSold) #rest(type: "ContainerHistoryResponse", path:"/api/GetUserContainers?showActive={args.showActive}&showSold={args.showTrue}") {
//.. some expected reponse
}
}
`
// component
const params = {
showActive: true,
showSold: false,
}
const { data } = useQuery(getUserContainersQuery, {
variables: {
showActive,
showSold
},
fetchPolicy: 'cache-and-network',
})
Related
(Edited)
Refetching with the generated query type seems to work different from the refetch function in Apollo Client useQuery. I don't understand how to phrase it - can anyone provide an example?
I'm realizing the problem is probably either my refetch is not properly phrased, or maybe the store is only hitting the cached query. I've been going over my code for days and I can't figure out what it could be. I've tried await blocks too.
The refetch worked with svelte-apollo, but i'm trying to eliminate that dependency. I've also tried Apollo Client's useQuery, but the whole point of graphql-codegen with typescript-svelte-apollo is to use the generated typescript wrapper for the query.
When I assign the generated query to a reactive constant in my Svelte front-end code,
$: observations = getObservations({ variables: { filter } });
the query does not refetch when i update the query variables, as I would expect.
This is how my svelte template is using the query. The filter object changes based on a form user input. I've tried this with an await block too.
<script lang="ts">
import { getObservations } from '$lib/generated';
$: observations = getObservations({ variables: { filter } });
function handleFilter(event) {
filter = event.detail;
}
</script>
{#if $observations.loading}
Loading...
{:else if $observations.error}
{$observations.error}
{:else if $observations.data}
{#each $observations.data['observations']['edges'] as edge}
<Item node={edge['node']} />
{/each}
{/if}
Since this plugin allows to use the query directly, without Apollo's useQuery, i'm not sure how to phrase a refetch.
If i do $observations.refetch(); inside handleFilter(e), i get an error
Property 'refetch' does not exist on type 'Readable<ApolloQueryResult<GetObservationsQuery> & { query: ObservableQuery<GetObservationsQuery, Exact<{ filter?: FilterObservationsInput; }>>; }>'.ts(2339)
There's nothing fancy in my config. Am I doing something wrong here?
schema: src/graphql/schema.graphql
documents:
- src/graphql/queries.graphql
- src/graphql/mutations.graphql
generates:
src/lib/generated.ts:
plugins:
- typescript
- typescript-operations
- graphql-codegen-svelte-apollo
config:
clientPath: src/lib/shared/client
# asyncQuery: true
scalars:
ISO8601Date: Date
ISO8601DateTime: Date
Here's the client:
export default new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
observations: relayStylePagination(),
},
},
},
})
});
The generated query:
export const getObservations = (
options: Omit<
WatchQueryOptions<GetObservationsQueryVariables>,
"query"
>
): Readable<
ApolloQueryResult<GetObservationsQuery> & {
query: ObservableQuery<
GetObservationsQuery,
GetObservationsQueryVariables
>;
}
> => {
const q = client.watchQuery({
query: GetObservationsDoc,
...options,
});
var result = readable<
ApolloQueryResult<GetObservationsQuery> & {
query: ObservableQuery<
GetObservationsQuery,
GetObservationsQueryVariables
>;
}
>(
{ data: {} as any, loading: true, error: undefined, networkStatus: 1, query: q },
(set) => {
q.subscribe((v: any) => {
set({ ...v, query: q });
});
}
);
return result;
}
Here's the query document that it's built from:
query getObservations($filter: FilterObservationsInput) {
observations(filter: $filter) {
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
cursor
node {
id
createdAt
updatedAt
when
where
imgSrcThumb
imgSrcSm
imgSrcMed
thumbImage {
width
height
}
name {
formatName
author
}
user {
name
login
}
rssLog {
detail
}
}
}
}
}
I have an array of countries received from Apollo backend without an ID field.
export const QUERY_GET_DELIVERY_COUNTRIES = gql`
query getDeliveryCountries {
deliveryCountries {
order
name
daysToDelivery
zoneId
iso
customsInfo
}
}
`
Schema of these objects:
{
customsInfo: null
daysToDelivery: 6
iso: "UA"
name: "Ukraine"
order: 70
zoneId: 8
__typename: "DeliveryCountry"
}
In nested components I read these objects from client.readQuery.
What I want is to insert it to localStorage, read it initially and write this data to Apollo Client Cache.
What I've already tried to do:
useEffect(() => {
const deliveryCountries = JSON.parse(localStorage.getItem('deliveryCountries') || '[]')
if(!deliveryCountries || !deliveryCountries.length) {
getCountriesLazy()
} else {
deliveryCountries.map((c: DeliveryCountry) => {
client.writeQuery({
query: QUERY_GET_DELIVERY_COUNTRIES,
data: {
deliveryCountries: {
__typename: "DeliveryCountry",
order: c.order,
name: c.name,
daysToDelivery: c.daysToDelivery,
zoneId: c.zoneId,
iso: c.iso,
customsInfo: c.customsInfo
}
}
})
})
}
}, [])
But after execution the code above I have only one object in countries cache. How to write all objects without having an explicit ID, how can I do it? Or maybe I'm doing something wrong?
Lol. I just had to put the array into necessary field without iterating. writeQuery replaces all the data and not add any "to the end".
client.writeQuery({
query: QUERY_GET_DELIVERY_COUNTRIES,
data: {
deliveryCountries: deliveryCountries
}
})
I've got a very simple Nuxt app with Strapi GraphQL backend that I'm trying to use and learn more about GraphQL in the process.
One of my last features is to implement a search feature where a user enters a search query, and Strapi/GraphQL performs that search based on attributes such as image name and tag names that are associated with that image. I've been reading the Strapi documentation and there's a segment about performing a search.
So in my schema.graphql, I've added this line:
type Query {
...other generated queries
searchImages(searchQuery: String): [Image
}
Then in the /api/image/config/schema.graphql.js file, I've added this:
module.exports = {
query: `
searchImages(searchQuery: String): [Image]
`,
resolver: {
Query: {
searchImages: {
resolverOf: 'Image.find',
async resolver(_, { searchQuery }) {
if (searchQuery) {
const params = {
name_contains: searchQuery,
// tags_contains: searchQuery,
// location_contains: searchQuery,
}
const searchResults = await strapi.services.image.search(params);
console.log('searchResults: ', searchResults);
return searchResults;
}
}
}
},
},
};
At this point I'm just trying to return results in the GraphQL playground, however when I run something simple in the Playground like:
query($searchQuery: String!) {
searchImages(searchQuery:$searchQuery) {
id
name
}
}
I get the error: "TypeError: Cannot read property 'split' of undefined".
Any ideas what might be going on here?
UPDATE:
For now, I'm using deep filtering instead of the search like so:
query($searchQuery: String) {
images(
where: {
tags: { title_contains: $searchQuery }
name_contains: $searchQuery
}
) {
id
name
slug
src {
url
formats
}
}
}
This is not ideal because it's not an OR/WHERE operator, meaning it's not searching by tag title or image name. It seems to only hit the first where. Ideally I would like to use Strapi's search service.
I actually ran into this problem not to recently and took a different solution.
the where condition can be combined with using either _and or _or. as seen below.
_or
articles(where: {
_or: [
{ content_contains: $dataContains },
{ description_contains: $dataContains }
]})
_and
(where: {
_and: [
{slug_contains: $categoriesContains}
]})
Additionally, these operators can be combined given that where in this instance is an object.
For your solution I would presume you want an or condition in your where filter predicate like below
images(where: {
_or: [
{ title_contains: $searchQuery },
{ name_contains: $searchQuery }
]})
Lastly, you can perform a query that filters by a predicate by creating an event schema and adding the #search directive as seen here
What i'm trying to do: skip the query if the parameter in the query doesn't change.
I understand that useQuery is smart enough to not run if the input parameter doesn't change. However, if I have useQuery in componentA, and i switch from it to componentB and back to componentA, useQuery will run again even if the input parameter is the same.
So, I thought of using the skip option in useQuery. However its behaviour is not as expected. If I define a constant that is a boolean outside and set it to the 'skip' option, the query isn't skipped even if my constant was 'true'.
const skipQuery = props.match.params.itineraryId == placeState.itineraryId;
console.log(skipQuery) // prints true
const { data } = useQuery(GET_ITINERARY, {
skip: skipQuery,
onCompleted(data){
fetchItineraryFromGoogle(data);
},
variables: {itineraryId}
})
However this works:
const { data } = useQuery(GET_ITINERARY, {
skip: true,
onCompleted(data){
fetchItineraryFromGoogle(data);
},
variables: {itineraryId}
})
so does this:
const { data } = useQuery(GET_ITINERARY, {
skip: 1==1,
onCompleted(data){
fetchItineraryFromGoogle(data);
},
variables: {itineraryId}
})
so why doesn't my boolean constant work?
I'm trying to group my mutations into second level types. The schema is parsed correctly, but resolvers aren't firing in Apollo. Is this even possible? Here's the query I want:
mutation {
pets: {
echo (txt:"test")
}
}
Here's how I'm trying to do it
Schema:
type PetsMutations {
echo(txt: String): String
}
type Mutation {
"Mutations related to pets"
pets: PetsMutations
}
schema {
mutation: Mutation
}
Resolvers:
...
return {
Mutation: {
pets : {
echo(root, args, context) {
return args.txt;
}
},
}
Assuming you're using apollo-server or graphql-tools, you cannot nest resolvers in your resolver map like that. Each property in the resolver map should correspond to a type in your schema, and itself be a map of field names to resolver functions. Try something like this:
{
Mutation: {
// must return an object, if you return null the other resolvers won't fire
pets: () => ({}),
},
PetsMutations: {
echo: (obj, args, ctx) => args.txt,
},
}
Side note, your query isn't valid. Since the echo field is a scalar, you can't have a subselection of fields for it. You need to remove the empty brackets.