How to update connection metadata in client-side store? - relayjs

I'm attempting to learn Relay by implementing TodoMVC from scratch.
I can query my data like this which is working well:
query {
allTodos(first: 100) {
totalCount
completedCount
edges {
node {
id
text
completed
}
}
}
}
I got the idea to add the totalCount and completedCount metadata to the connection from here: http://graphql.org/learn/pagination/#end-of-list-counts-and-connections
It's similar in this example: https://github.com/graphql/swapi-graphql/blob/master/src/schema/index.js#L78
Now I am writing a mutation to change the completed field of a Todo given its id.
I gather I will need to return the new completedCount in the mutation payload, but I'm not sure how to implement getConfigs() to update this in the client-side store. I don't have an id for the connection, right? Is there is a flaw in my schema design? Thanks!

Assuming your mutation returns a viewer, you'll need to add the viewer to your fatQuery and getConfigs. I think this tutorial might be helpful. Here's the excerpt relevant to your task:
Adding a Todo is more complex. The reason for this is that we need to
update not only the state of a Todo object that we will create, but
also a connection where it is stored - the count of Todos will change,
as well as the listing of Todo nodes in edges.
import Relay from 'react-relay';
export default class AddTodoMutation extends Relay.Mutation {
static fragments = {
viewer: () => Relay.QL`fragment on ReindexViewer {
id
allTodos {
count,
}
}`
};
getMutation() {
return Relay.QL`mutation{ createTodo }`;
}
getVariables() {
return {
text: this.props.text,
complete: false,
};
}
getFatQuery() {
return Relay.QL`
fragment on _TodoPayload {
changedTodoEdge,
viewer {
id,
allTodos {
count
}
}
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentID: this.props.viewer.id,
connectionName: 'allTodos',
edgeName: 'changedTodoEdge',
rangeBehaviors: {
'': 'prepend',
},
}, {
type: 'FIELDS_CHANGE',
fieldIDs: {
viewer: this.props.viewer.id,
},
}];
}
getOptimisticResponse() {
return {
changedTodoEdge: {
node: {
text: this.props.text,
complete: false,
},
},
viewer: {
id: this.props.viewer.id,
allTodos: {
count: this.props.viewer.allTodos.count + 1,
},
},
};
}
}
In order to perform this mutation, we need some data that might not be
available to the component - the id of viewer object and count of
allTodos connection. Therefore we need to specify fragments for the
mutation same way as we specify them for containers.
Our configs are more complex this time too - we need to add our new
Todo to a connection, so we use RANGE_ADD mutation config. Relay
expects an edge to be passed in payload, not just a Todo, Reindex
provides changedTodoEdge for this. Lastly we need to fetch updated
connection count from the server and for this viewer field is
available for every payload.
In our optimistic update we increment the count of allTodos, so that
we change our “total” display without any delay.

Related

Neo4j GraphQL how to check if node exists before connecting to it

B"H
I've seen this question and I want to do the same thing with the GraphQL driver for Neo4j. I think the answer lies somewhere in combining AND and OR operators found here, of some sort, but not sure how.
To illustrate the question, say I have this type:
type Comment {
id: ID #id
timeAdded: DateTime! #timestamp(
operations: [CREATE]
)
writer: User! #relationship(
type: "WRITTEN_BY",
direction: IN
)
content: String
}
type User {
id: ID #id
name: String
commentsWritten: [Comment!]! #relationship(
type: "WRITTEN_BY",
direction: OUT
)
}
type Mutation {
addComment(authorID: String)
}
and this resolver to add a new comment (where "Comment" is a reference to the OGM model of the Comment type):
addComment: async (
src,
args,
ctx
) => {
var authorID = args/*edit*/.authorID //let's say this is passed
//from request headers / JWT token or something
var adCom = await Comment.create({
input: [
{
content: args.content,
writer: {
connect: {
where: {
node: {
users: {
id: authorID
}
}
}
}
}
}
]
})
return adCom;
}
So it attempts to connect to that user with that ID. but if there is no user with that ID, I don't want to create anything [not only do I not want to connect it].
I could run another query to find a User with that ID, and test if it went through, but was wondering if it's possible to do everything in one call

Trying to Script a Search Result into a Netsuite Entity Field

Having two issues with this. One is that I keep getting an error when trying to upload my script. The other is that one version that I did get to upload, didn't load any value into the field (ie. field blank after script ran)
The error I keep getting on upload is "Fail to evaluate script: All SuiteScript API Modules are unavailable while executing your define callback." And although I've made drastic changes to the script, it still won't allow me to upload.
/**
*#NApiVersion 2.x
*#NScriptType ScheduledScript
*/
define(['N/search', "N/record"],
function(search, record) {
function loadAndRunSearch(scriptContext) {
var mySearch = search.load({
id: 'customsearch1088'
});
mySearch.run().each(function (result) {
var countt = result.getValue({
name: 'number'
});
var entity = result.getValue({
name: 'internalid'
});
var objRecord = record.load({
type: record.Type.CUSTOMER,
id: entity,
isDynamic: true,
});
var vfield = objRecord.getField({
fieldId: 'custentity_orders_12m'
});
objRecord.setValue({fieldId: 'custentity_orders_12m', value: countt});
objRecord.save();
});
}
return {
execute: loadAndRunSearch
};
});
That's the script stripped down to the bare bones (FYI still doesn't upload), and the script that uploaded was basically a more complicated version of the same script, except it didn't set the field value. Can anyone see where I've gone wrong?
You haven't returned the entry function.
/**
*#NApiVersion 2.x
*#NScriptType ScheduledScript
*/
define(['N/search', 'N/record'],
function(search, record) {
function loadAndRunSearch(scriptContext) {
var mySearch = search.load({
id: 'customsearch1088'
});
mySearch.run().each(function (result) {
var countt = result.getValue({
name: 'number'
});
var entity = result.getValue({
name: 'internalid'
});
record.submitField({
type: record.Type.CUSTOMER,
id: entity,
values: {
'custentity_orders_12m' :countt
}
});
});
}
return {
execute : loadAndRunSearch
}
});

Client-side mutation with RANGE_ADD type doesn't include edge inside request payload

I'm trying to create new object using client-side mutation described below:
import Relay from 'react-relay'
export default class CreateThemeMutation extends Relay.Mutation {
static fragments = {
admin: () => Relay.QL`fragment on Admin { id }`,
};
getMutation() {
return Relay.QL`mutation { createTheme }`
}
getFatQuery() {
return Relay.QL`
fragment on CreateThemePayload {
admin { themes }
themeEdge
}
`
}
getVariables() {
return {
name: this.props.name,
}
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'admin',
parentID: this.props.admin.id,
connectionName: 'themes',
edgeName: 'themeEdge',
rangeBehaviors: {
'': 'append',
},
}]
}
}
Root query field admin is quite similar to viewer so this shouldn't be a problem. The problem is I haven't found themeEdge (which I believe should present) within the request payload (admin { themes } is there though):
query: "mutation CreateThemeMutation($input_0:CreateThemeInput!){createTheme(input:$input_0){clientMutationId,...F3}} fragment F0 on Admin{id} fragment F1 on Admin{id,...F0} fragment F2 on Admin{_themes2gcwoM:themes(first:20,query:""){count,pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},edges{node{id,name,createdAt},cursor}},id,...F1} fragment F3 on CreateThemePayload{admin{id,...F0,id,...F2}}"
variables: {input_0: {name: "test", clientMutationId: "0"}}
As a result outputFields.themeEdge.resolve inside the server-side mutation never get called and I see this message:
Warning: writeRelayUpdatePayload(): Expected response payload to include the newly created edge `themeEdge` and its `node` field. Did you forget to update the `RANGE_ADD` mutation config?
I've seen similar issue on github. However REQUIRED_CHILDREN isn't my case because the application has requested themes connection already. Am I missing something obvious? Should I paste more info? Thanks.
react-relay version: 0.6.1
I ran into the same issue and eventually solved it by making sure that my equivalent of themeEdge actually existed as an edge in my schema. If you grep your schema for themeEdge, does an object exist?
For reference, here's my edge definition tailored for you:
{
"name":"themeEdge",
"description":null,
"args":[],
"type":{
"kind":"NON_NULL",
"name":null,
"ofType":{
"kind":"OBJECT",
"name":"ThemeEdge",
"ofType":null
}
},
"isDeprecated":false,
"deprecationReason":null
}
and
{
"kind":"OBJECT",
"name":"ThemeEdge",
"description":"An edge in a connection.",
"fields":[{
"name":"node",
"description":"The item at the end of the edge.",
"args":[],
"type":{
"kind":"NON_NULL",
"name":null,
"ofType":{
"kind":"OBJECT",
"name":"Theme",
"ofType":null
}
},
"isDeprecated":false,
"deprecationReason":null
}
Also note that your rangeBehaviors must exactly match the query you use to retrieve your parent object. You can specify multiple queries as follows, which also shows the syntax for when your query contains multiple variables:
{
type: 'RANGE_ADD',
parentName: 'admin',
parentID: this.props.admin.id,
connectionName: 'themes',
edgeName: 'themeEdge',
rangeBehaviors: {
'': 'append',
'first(1).id($adminId)': 'append',
},
}

search functionality using relay

How to implement a search functionality with relay?
So, the workflow is
user navigate to search form.
there should not be any query (as in relay container) when initializing the view.
user fills the field values, and press the action/search button.
a relay query is sent to the server
results are received from the server.
page displays it and relay reconciles the filtered results with local cache.
I have not seen an example of ad hoc query but only part of a relay container (which it resolves before component initialization). So, how to model it. should it be like a mutation?
If I understand correctly you'd like to not send any query at all for the component until the user enters some search text, at which point the query should sent. This can be accomplished with the example posted by #Xuorig, with one addition: use GraphQL's #include directive to skip the fragment until a variable is set. Here's the extended example:
export default Relay.createContainer(Search, {
initialVariables: {
count: 3,
query: null,
hasQuery: false, // `#include(if: ...)` takes a boolean
},
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
# add `#include` to skip the fragment unless $query/$hasQuery are set
items(first: $count, query: $query) #include(if: $hasQuery) {
edges {
node {
...
}
}
}
}
`,
},
});
This query will be skipped initially since the include condition is falsy. Then, the component can call setVariables({query: someQueryText, hasQuery: true}) when text input is changed, at which point the #include condition will become true and the query will be sent to the server.
This is the way I've implemented simple search in my project:
export default Relay.createContainer(Search, {
initialVariables: {
count: 3,
title: null,
category: null,
},
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
items(first: $count, title: $title, category: $category) {
edges {
node {
...
}
}
}
}
`,
},
});
Your search form simply has to update the initialVariables using this.props.relay.setVariables and relay will query the new data.

In a GraphQL/Relay mutation that creates a model, is there a way to get back the model ID?

We're using Relay and GraphQL in a new project.
We've got a Relay mutation that creates a new model in the DB:
export default class AddCampaignMutation extends Relay.Mutation {
getMutation() {
return Relay.QL`mutation { addCampaign }`;
}
getVariables() {
return {
type: this.props.campaignType
};
}
getFatQuery() {
return Relay.QL`
fragment on AddCampaignPayload {
campaignEdge
viewer
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'viewer',
parentID: this.props.viewer.id,
connectionName: 'campaigns',
edgeName: 'campaignEdge',
rangeBehaviors: {
'': 'append',
},
}];
}
static fragments = {
viewer: () => Relay.QL`
fragment on User {
id
}
`,
};
}
However, since none of the components are currently querying for the range specified in RANGE_ADD (viewer { campaigns }), Relay intelligently excludes that query from the AddCampaignPayload.
This results in a console warning:
Warning: writeRelayUpdatePayload(): Expected response payload to include the newly created edge `campaignEdge` and its `node` field. Did you forget to update the `RANGE_ADD` mutation config?
I really want to get back the ID of the newly created model, so that I can navigate the client to it. For example, getting back the new campaignEdge, I want to send the client to /campaigns/${campaignEdge.node.id}.
Is there any way to tell Relay to fetch that edge? Have I configured this mutation correctly?
You can use REQUIRED_CHILDREN in this context. For more details, see https://github.com/facebook/relay/issues/237 and https://github.com/facebook/relay/issues/236.
REQUIRED_CHILDREN lets you specify an extra data dependency for exactly this pattern.

Resources