I guess this is a simple issue, but I am stuck here for a while, so any advice may be helpful!
I have a react app and I am calling a GraphQL api (with apollo). Inside an arrow function component I have:
const [executeQuery, { data }] = useLazyQuery(GET_ALL_TASKS);
const findId = (step) => {
executeQuery({
variables: {
"query": {
"state": "CREATED",
"taskDefinitionId": "something"
}
}
})
}
The query is successful and in the browser inspect panel I get this as the graphql response:
{
"data" : {
"tasks" : [ {
"id" : "2251",
"name" : "some_name",
"__typename" : "Task"
} ]
}
}
In my code I want to use the retrieved id. How can I isolate the id from the response? When I am trying to access the data I get an undefined error.
Thank you!
Not sure why you are wrapping your executeQuery in a function.
The data will be part of the response so you can get it like this:
const {data, loading} = executeQuery({
variables: {
"query": {
"state": "CREATED",
"taskDefinitionId": "something"
}
}
})
// may also need to check for the error state
if(loading){
console.log("Loading...")
}else{
/// the ids seem to be an array of objects
const ids = data.tasks.map((task)=> task.id)
console.log(ids)
}
For anyone who may have the same problem, I realized it is a caching error happening in apollo client. I couldn't figure out the solution. However, I temporarily solved it by downgrading the apollo client dependency to version 3.2.5
Related
I am trying to fetch data from a Supabase table called "profiles" with Next.js 13 and the app directory. I am trying to take advantage of the new next.js fetching methods, my code looks as follows:
export const revalidate = 0;
export default async function getData() {
const { data: profiles } = await supabase
.from("profiles")
.select("*")
.eq("is_host", true);
console.log(profiles);
return { profiles };
if (!profiles) {
return <p>No hosts found</p>
}
The problem is that this code seems to be wrapping the array returned from Supabase in an object.
The data returned looks like this:
{data:
[
{
"id":"feef56d9-cb61-4c4d-88c6-8a8d7c9493d9",
"updated_at":null,
"username":"username",
"full_name":"Full Name",
"avatar_url":"URL",
"website":null,
"is_host":true,
"bio":null,
"languages":6
}
]
}
When I use useState and useEffect instead, the data is returned as expected, and I can map through it.
Does anybody have an idea why, and how I can prevent that?
Thanks in advance.
I worked it out, through a subsequent error, which I as able to solve thanks to the question I asked here and the helpful hints I got from there.
return { profiles };
Returns the array inside an object.
By removing the {} I was able to fetch the array inside of it.
I currently have a AppSync schema where I created a separate query within the AppSync console in order retain certain parameter (assetId) and get a list of the results in my DynamoDB table. I have tested the query already within the AppSync console and it works fine, I am now just having troubles using Amplify in my React App in order to call the query. I get the following error when running my App:
DisplayCard.js:34 Uncaught (in promise) {data: null, errors: Array(1)}
"Validation error of type FieldUndefined: Field 'getAssetIdRating' in type 'Query' is undefined # 'getAssetIdRating'"
I have tried following the documentation as per the Amplify site (https://aws-amplify.github.io/docs/js/api) but am still receiving this error.
For reference here is the query when I run it in the AppSync console: (returns the desired result)
query getAssetIdRating {
getRatingsAssetId(assetId:"949142fb-91d2-41bd-8c04-1d42ed8166c9") {
items {
id
assetId
rating
}
}
}
The resolver that I am using for this query is the following: (I have created a separate Index)
{
"version" : "2017-02-28",
"operation" : "Query",
"index": "assetId-index",
"query" : {
## Provide a query expression. **
"expression": "assetId = :assetId",
"expressionValues" : {
":assetId" : $util.dynamodb.toDynamoDBJson($ctx.args.assetId)
}
}
}
And now moving onto my React code, this is the current query that I am using within react, under src/graphql/queries.
export const getAssetIdRating = /* GraphQL */ `
query getRatingAssetId($assetId: ID) {
getAssetIdRating(assetId: $assetId) {
items {
id
assetId
rating
}
}
}
`;
And when I call it then in my React application:
componentDidMount = async () => {
this.getRatings();
}
getRatings = async () => {
let { assetIdRatings } = this.state;
const result = await API.graphql(graphqlOperation(queries.getAssetIdRating, {assetId: '949142fb-91d2-41bd-8c04-1d42ed8166c9'}));
console.log(result);
}
Note that when I call the listRatings query it works fine, just does not work with this query. And as a side note, I added this query later in manually through the AppSync console, I don't presume that should play an issue?
Either way, any help would be greatly appreciated! And I can upload anymore necessary code if required! Thanks for the help!
sorry you can ignore the question, it was a simple typing error :P You miss it when its late at night!
You can see the typing error on my Query:
getRatingsAssetId(assetId:"949142fb-91d2-41bd-8c04-1d42ed8166c9") {
getAssetIdRating(assetId: $assetId) {
Thanks though for the help!
I am trying to update my chache after succesfully executing a mutation. Here is my query and mutation:
export const Dojo_QUERY = gql`
query Dojo($id: Int!){
dojo(id: $id){
id,
name,
logoUrl,
location {
id,
city,
country
},
members{
id
},
disziplines{
id,
name
}
}
}`;
export const addDiszipline_MUTATION = gql`
mutation createDisziplin($input:DisziplineInput!,$dojoId:Int!){
createDisziplin(input:$input,dojoId:$dojoId){
disziplin{
name,
id
}
}
}`;
and my mutation call:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { disziplines } = cache.readQuery({ query: Constants.Dojo_QUERY,variables: {id}});
console.log(disziplines)
cache.writeQuery({
...some update logic (craches in line above)
});
}
}
);
when i execute this mutation i get the error
Invariant Violation: "Can't find field dojo({"id":1}) on object {
"dojo({\"id\":\"1\"})": {
"type": "id",
"generated": false,
"id": "DojoType:1",
"typename": "DojoType"
}
}."
In my client cache i can see
data{data{DojoType {...WITH ALL DATA INSIDE APPART FROM THE NEW DISZIPLINE}}
and
data{data{DisziplineType {THE NEW OBJECT}}
There seems to be a lot of confusion around the client cache around the web. Somehow none of the posed solutions helped, or made any sense to me. Any help would be greatly appreciated.
EDIT 1:
Maybe this can help?
ROOT_QUERY: {…}
"dojo({\"id\":\"1\"})": {…}
generated: false
id: "DojoType:1"
type: "id"
typename: "DojoType"
<prototype>: Object { … }
<prototype>: Object { … }
Edit 2
I have taken Herku advice and started using fragment. however it still seems to not quite work.
My udated code:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { dojo } = cache.readFragment(
{ fragment: Constants.Diszilines_FRAGMENT,
id:"DojoType:"+id.toString()});
console.log(dojo)
}
}
);
with
export const Diszilines_FRAGMENT=gql`
fragment currentDojo on Dojo{
id,
name,
disziplines{
id,
name
}
}
`;
however the result from console.log(dojo) is still undefined.Any advice?
So I think your actual error is that you have to supply the ID as as a string: variables: {id: id.toString()}. You can see that these two lines are different:
dojo({\"id\":1})
dojo({\"id\":\"1\"})
But I would highly suggest to use readFragment instead of readQuery and update the dojo with the ID supplied. This should update the query as well and all other occurrences of the dojo in all your queries. You can find documentation on readFragment here.
Another trick is as well to simply return the whole dojo in the response of the mutation. I would say people should be less afraid of that and not do to much cache updates because cache updates are implicit behaviour of your API that is nowhere in your type system. That the new disziplin can be found in the disziplins field is now encoded in your frontend. Imagine you want to add another step here where new disziplins have to be approved first before they end up in there. If the mutation returns the whole dojo a simple backend change would do the job and your clients don't have to be aware of that behaviour.
On one of my pages in my app I'm doing two api calls with graphql apollo client. One is document, the other one menu. I need menu data in one of my components so I want to use readQuery in order to not to fetch it again. What I'm doing is:
const client = useApolloClient();
try {
const testData = client.readQuery({
query: gql`
query ($result: String) {
menu(result: $result) {
text
}
}
`,
variables: {
result: „testresult”
},
});
console.log(testData);
} catch(e) {
console.log(e);
}
What graphQL is doing is looking for document root query so the error looks like this:
Invariant Violation: Can't find field menu({"lang":"en-us"}) on object
{
"document({\"lanf\":\"en-us\",\"id\":\"mySite\"})": {
"type": "id",
"generated": false,
"id": "XO5tyxAAALGzcYGG",
"typename": "Document"
}
}.
I believe that it is because menu data is not there yet.
How can I wait until it will be there?
You are right. You are getting an error because the data is not in cache yet:
The query method, on the other hand, may send a request to your server if the appropriate data is not in your cache whereas readQuery will throw an error if the data is not in your cache. readQuery will always read from the cache.
https://www.apollographql.com/docs/react/advanced/caching/#readquery
To do what you want use the a normal query with a cache-only fetchPolicy.
https://www.apollographql.com/docs/react/api/react-apollo/#optionsfetchpolicy
Edit to the makers of AoR : Your framework suffers from horrid documentation. You should really focus on that, people would really adopt it then.
I cant for the life of me decipher how admin-on-rest does the 'rest' part. If there is a better framework with better documentation, Im open to that.
Im very new to react, so thats probably part of it.
What I can discern is that
1) The [Admin] tag takes a prop 'restClient', and this is a function that sets your base path to your JSON source, then returns a function with a specific signature (takes 3 arguments, returns a promise).
2) Then a [Resource] tag adds to the path with name="posts" and makes a list, which (heres where it turns to magic) basically does a wget to your database then iterates over the results.
What I want to do : hook up couchDB to admin-on-rest. I already have a few test docs made on localhost. The couchDB url looks like :
http://127.0.0.1:5984/myproject/_design/getclients/_view/getclient/
and this works in postman, giving me a json object like this :
{
"total_rows": 4,
"offset": 0,
"rows": [
{
"id": "afc3bb9218d1a5c1e81ab3cc9f004467",
"key": {
"status": "active",
"rating": 9.1,
"bio": {
"fname": "Sam",
"mname": "TestMName",
"lname": "TestLName",
"address": "712347 B Street",
"state": "CA",
"city": "Los Santos",
"zip": "90211",
"phone": "123-456-7890",
"email": "sam#samsemail.com",
"username": "TestSam",
"password": "abc123"
}
},
"value": null
},
At this point Im so confused I dont know where to look.
Heres my code now :
//App.js
import React from 'react';
import { jsonServerRestClient, Admin, Resource } from 'admin-on-rest';
import { PostList } from './Posts.js';
const App = () => (
<Admin restClient={jsonServerRestClient('http://127.0.0.1:5984/myproject/')}>
<Resource name="_design/getclients/_view/getclient" list={PostList} />
</Admin>
);
export default App;
And
//Posts.js
export const PostList = (props) => (
<List {...props}>
<Datagrid>
<TextField source="status" />
<TextField source="rating" />
</Datagrid>
</List>
);
The page loads but a little pink box pops up at the bottom saying :
The X-Total-Count header is missing in the HTTP Response. The jsonServer REST client expects responses
The RestClient is a bit of a murky beast. Not perfectly documented for sure.
But it is in the end quite straightforward if you know how the whole thing works together.
1) Admin-On-Rest has defined some REST types (below). These are usually shot off by Redux actions (in their meta tag). The system scans for these rest types and if it sees them, then it calls the RestClient
GET_LIST
GET_ONE
CREATE
UPDATE
DELETE
GET_MANY
GET_MANY_REFERENCE
The REST client is called with these types and some other params. It is the job of the rest client to interpret the type and then use the params to make a request to your API. For this AOR uses the new Fetch API that is built into browsers.
You can access it by calling. You should also go into AOR source code and check out how it works.
import { fetchUtils } from 'admin-on-rest';
2) The X total count is a header field that AOR needs for all responses to the GET_LIST type.
You can set this quite simply in your API. I use loopback and I set the X-Total-Count manually in a remote hook (don't worry about it if you don't know it)
It seems your api is still using the JSON server. JSON server is a dummy API. So your app is not connected to your couchDB right now.
https://github.com/typicode/json-server
If you are not using an api server like express or loopback, then you can also configure your restClient do all request and response handling. You have to construct the URL. Read the below link so you can follow my example code further down.
https://marmelab.com/admin-on-rest/RestClients.html#decorating-your-rest-client-example-of-file-upload
so something like this.
if (type === 'GET_LIST' && resource === 'posts') {
const url = http://127.0.0.1:5984/myproject/_design/getclients/_view/getclient/
options.method = 'GET';
return fetchUtils.fetchJson(url, options)
.then((response) => {
const {headers, json} = response;
//admin on rest needs the {data} key
return {data: json,
total: parseInt(headers.get('x-total-count').split('/').pop(), 10)}
})
You can also write a function like this to handle the request and response.
function handleRequestAndResponse(url, options={}) {
return fetchUtils.fetchJson(url, options)
.then((response) => {
const {headers, json} = response;
//admin on rest needs the {data} key
const data = {data: json}
if (headers.get('x-total-count')) {
data.total = parseInt(headers.get('x-total-count').split('/').pop(), 10)
} else {
data.total = json.length // this is why the X-Total-Count is needed by Aor
}
}
}
// handle get_list responses
return {data: json,
total: } else {
return data
}
})
}
The above code has been formatted in the window and so might not work straight out of the box. But I hope you get the idea.