relay modern run RefetchContainer only manually - relayjs

I still don't have much experience with relay modern and am trying to implement a search.
For this I use a "RefetchContainer":
export default createRefetchContainer(
SearchComponent,
{
base: graphql`
fragment SearchComponent_base on Base
#argumentDefinitions(
input: {type: "FireSearchInput!" defaultValue: {page: 0,queryString:""}}
)
{
search(input: $input){
total
...SearchResult_searchResult
}
}
`
},
graphql`
query SearchComponentRefetchQuery($input: FireSearchInput!) {
...SearchComponent_base #arguments(input: $input)
}
`
)
The problem is only that the query is already execute when you start the app, without an input is made.
Is it possible to suppress the automatic query and execute it only with the command this.props.relay.refetch?

the solution is to simply use a QueryRenderer:
<QueryRenderer
environment={environment}
query={graphql`
query SearchComponent($input: FireSearchInput!) {
search(input: $input){
total
...SearchResult_searchResult
}
}
`}
variables={{
input: {///my input}
}}

Related

Refetch Container with two independent data sets

I have a component that has two 'independent' datasets, in that refetching argument a changes field A and refetching argument b changes field B.
export default createRefetchContainer(
Component,
{
a: graphql`
fragment Comp_a on Query #argumentDefinitions(
a: { type: "String!", defaultValue: "" },
) {
A(a: $a) {
name
}
}
`,
b: graphql`
fragment MainList_repo on Query #argumentDefinitions(
b: { type: "String!", defaultValue: "" }
) {
B(b: $b) {
name
}
}
`
},
{
a: graphql`
query CompRefetchQuery($a: String!) {
...Comp_a #arguments(a: $a)
}
`,
b: graphql`
query CompRefetchQuery($b: String!) {
...Comp_b #arguments(b: $b)
}
`
}
)
There is no such example in relay docs, and I just inferred this from fragment containers. The problem is calling
props.relay.refetch(vars => ({ data }), null)
results in a RelayModernGraphQLTag: Expected an operation, got{}. error (reporting refetch itself as the call site).
Is there a way to create two 'refetch' subsets like this in relay? Or do I need to create two refetch containers and nest them?

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.

Select2 with createSearchChoice uses newly created choice for keyboard entry even given a match, bug or am I missing something?

I'm using Select2 (version 3.4.0) to populate a tag list. The tags are matched against existing ones via ajax call, and I'm using createSearchChoice to allow creating new tags. The code works so far, and looks something like this:
$(mytags).select2({
multiple: true,
placeholder: "Please enter tags",
tokenSeparators: [ "," ],
ajax: {
multiple: true,
url: myurl,
dataType: "json",
data: function(term, page) {
return {
q: term
};
},
results: function(data, page) {
return data;
}
},
createSearchChoice: function(term) {
return {
id: term,
text: term + ' (new)'
};
},
});
All pretty standard, except note the appended (new) in createSearchChoice. I need users to know that this is not a preexisting tag.
It works as expected: if I start typing "new-tag", I get "new-tag (new)" tag suggested at the top of the list, and if I pick it, the tag list contains "new-tag (new)", as expected. If the tag already exists, Select2 detects the match, and no "(new)" choice is created. Pressing return or clicking on the match works as expected.
The problem appears when I type a comma (my single tokenSeparators entry) while there is a match. Select2 closes that token, and adds the tag to the list, but with the "(new)" label appended, i.e. it uses the return value from createSeachChoice even if it does not have to.
Is this a bug in Select2, or am I using it wrong (and what should I do instead)?
I 'm not sure if this is a bug or not -- in any case, there is no open issue referring to this behavior at the GitHub issue tracker at this moment.
You can mostly fix the behavior yourself though. The idea is that the createSearchChoice callback must be able to tell if term refers to a search result or not. But createSearchChoice does not have direct access to the search results, so how can we enable that? Well, by saving the latest batch of search results from inside the results callback.
var lastResults = [];
$(...).select2({
ajax: {
multiple: true,
url: "/echo/json/",
dataType: "json",
type: "POST",
data: function (term, page) {
return {
json: JSON.stringify({results: [{id: "foo", text:"foo"},{id:"bar", text:"bar"}]}),
q: term
};
},
results: function (data, page) {
lastResults = data.results;
return data;
}
},
createSearchChoice: function (term) {
if(lastResults.some(function(r) { return r.text == term })) {
return { id: term, text: term };
}
else {
return { id: term, text: term + " (new)" };
}
}
});
This code uses Array.some so you need something better than IE8 (which is the select2 minimum requirement) to run it, but of course it is possible to emulate the behavior.
See it in action.
There is, however, a fly in the ointment: this code works correctly only if the search results corresponding to the current search term have been already received.
This should be obvious: if you type very fast and create a search term that corresponds to an existing tag but hit comma before the search results that include that tag have arrived, createSearchChoice will be testing for the tag's presence among the previously received search results. If those results do not include the tag, then the tag will be displayed as "new" even though it is not.
Unfortunately I don't believe there is anything you can do to prevent this from happening.
Instead of tweeking the result, I think it is better to work on the server side.
If the server doesn't find a tag make it return a json answer with the new tag
{"more":false,"results":[{"id":"whatever","text":"new-tag (new)"}]}
There is another parameter for the 'createSearchChoice' - 'page', it lists all the choices, you can easily find dupes with it.
createSearchChoice = function (term, page) {
if( page.some(function(item) {
return item.text.toLowerCase() === term.toLowerCase();
}) ){
return { val: term, name: term + '*' };
}
}

Resources