When you look at the documentation for relay modern([0]) it explains how you'd add a newly created item via a mutation. There is a link to the todomvc-modern([1]) example in the react-examples repo[2] which has a mutation to add a new todo item([3]) but it's much more complicated and different than the example on the documentation([0]).
Looking at the docs it seems like Relay should be able to handle adding new items to existing connections at least in simple cases. I'm aware of other issues with duplicating IDs not properly updating existing connections, but I am not dealing with that problem yet. The example([3]) I've been able to find using react modern is more complicated than can be easily understood just from the docs.
0) https://facebook.github.io/relay/docs/mutations.html#range-add
1) https://github.com/relayjs/relay-examples/tree/master/todo-modern
2) https://github.com/relayjs/relay-examples
3) https://github.com/relayjs/relay-examples/blob/master/todo-modern/js/mutations/AddTodoMutation.js
As a convenience, please post links to all of your references here so people don't have to go track them down.
I believe you are talking about these?
https://facebook.github.io/relay/docs/mutations.html
https://github.com/relayjs/relay-examples/tree/master/todo-modern/js/mutations
Not entirely sure what you mean by this.
Adding new items created via mutations doesn't seem like something that relay couldn't handle by itself without custom updaters and optimistic updaters.
The documentation while helpful, certainly leaves a lot to the imagination. It has been somewhat painstaking to hunt down bits and pieces to fill in the gaps for myself. Hopefully as more engineers continue to adopt Relay and host their codebase to public repos we can get some pull requests for updating the documentation that reflect best practices and a bit more _real world_ implementation in contrast to these sparse snippets.
Here is my code for adding a comment to a post, like one would do in WordPress: https://github.com/staylor/wp-relay-app/blob/master/src/mutations/AddComment.js#L55
The ConnectionHandler API does everything you need, but the documentation is non-existent. Some of the docs for Modern have been updated to use Flux configs for Mutations, which were a Relay Classic paradigm, and are equally as terse of a concept to grasp.
So I think I get it now. I was under the impression using the configs for mutations was the preferred way to do things.
I've followed what you did to create update my mutations client and server side.
Specifically I've noticed how the mutations get a set of information to know where to place the edge on (in my case the viewer's user id). Essentially we're grabbing the result of the mutation on the root. We grab the linked record of the edge we created. I also passed back my viewer in my query but for some reason I couldn't get access to that field. Only the edge.
So I passed in the correct viewer, found the viewer item in the store, got the connection on the viewer I needed and added the edge to the viewer's connection I was using.
I'm understanding it better, but I'm still a little in the dark as to why some of these things work and others do not. I need to read the code later to get a better understanding, but I do think this makes it clear to me that the docs need to updated somehow to either explain some of this or at least provide examples that do not conflict.
Will close this as it looks like this issue is mostly resolved. Relay's mutation documentation needs to be improved, but we're tracking that separately. Please reopen if there is something here that still needs a response!
For others that are searching for this, the code snippit reverenced above is a dead-link. The link is here:
https://github.com/staylor/graphql-wordpress/blob/5a3bf9c5c3a2250a55c1519acda0172821fdcd5c/packages/relay-wordpress/src/mutations/AddComment.js
@woodm1979, and anyone else coming to this issue, the documentation has been improved significantly since I made this issue.
It's much easier and better to use configs rather than writing your own updaters. I personally write my mutations to take the list of configs from the caller and update the store.
e.g. simple example of what I wanted from a personal project
import { commitMutation, graphql } from 'react-relay';
import { defaultErrorHandler } from './utils';
const mutation = graphql`
mutation createPersonMutation($input: CreatePersonInput!, $archived: Boolean!) {
createPerson(input: $input) {
person {
__typename
cursor
node {
id
name
created
}
}
viewer {
id
numberOfPeople(archived: $archived)
}
}
}
`;
const createPerson = (environment, variables, configs = [], optimisticResponse, onCompleted = null, onError = null) => {
let config = {
optimisticResponse,
mutation,
variables,
configs,
};
// Completed and error handlers
if (onCompleted) {
config.onCompleted = onCompleted;
}
if (onError) {
config.onError = onError;
} else {
config.onError = defaultErrorHandler;
}
commitMutation(environment, config);
};
export default createPerson;
Update: I've added an example of the call with the configs I used in another project adapted for the above mutation.
Note the use of filters, the type of the config, and the parentID == the id of the thing the connection is on.
createPerson(
this.props.relay.environment,
{
// Variables
input: {
nickName: this.state.nickName,
},
archived: false,
},
[
{
type: 'RANGE_ADD',
parentID: this.props.viewerId,
connectionInfo: [{
key: 'PeopleList_people',
rangeBehavior: 'prepend',
filters: { archived: false },
}],
edgeName: 'person',
},
],
() => {
this.setState({ nickName: '' });
}
);
Oh interesting! What do you pass in for "configs" and what is the __typename in your mutation? I'm still confused as hell as to how I set the parentId if I'm adding to a list from my base query.
If you could shed light on that, I'd HUGELY appreciate it.
@woodm1979 I updated the post with an example I hope will help.
The idea with configs is that you may have many places you want to add, delete, range add, or range remove from. Usually isn't the case, but it's possible.
Each config is meant to update a specific node that you specify using the parentID and you are specific about which connection you're updating via the connection info. The edge name lets the config know where to pull the edge it needs to add to that connection. The filter information is important because each combination of filters (with/without/etc.) is a "new" connection which can be updated so it's important to provide that if you filter the connection at all.
@0xCMP Thank you so much! I have only one more question, which, I THINK, gets to the heart of my issue:
You're clearly getting the viewerId from props, but I don't really know what a viewer is. I see them EVERYWHERE in relay examples, and perhaps that why I'm having such a hard time finding my "parentID".
Here's what I've been doing:
Essentially, I have a list of departments off the base query type.
query {
departments {
edges {
node {
name
id
}
}
}
}
But I can't come up with an id for either the query or the departments list above. Is that why I see "viewer" in so many examples? To put something with an ID in-between the query and the "base types"?
Also, and I've been looking all night... :-(
What is connectionInfo -> key supposed to be?
I've been running through the debugger and it gets mapped to __${it's value}_connection which doesn't end up in any store any where.
@woodm1979 when you setup the connection you're trying to update it's the key field. Usually it's some form of NameOfModule_fieldName.
e.g.
...
people(after: $afterCursor, first: $count) @connection(key: "PeopleList_people") {
totalCount
...
There is some history and recent news on viewer that I'm not familiar with, but basically it is meant to be "viewer = current user." So for instance { viewer { id name } } should give me the id and name of the current viewer. At one point you had to make viewer the root type you used with relay, but while that may have changed it's easier to still go along with it for now.
I personally use graphene and graphene-django so I create a Viewer type that I resolve everything inside of limited to what that current user can see and then resolve the viewer on my schema with the user from the context (given to me automatically by graphene and django working together)
The component which does the mutation is a simple form so I simply pass the viewer's id to it which I got from the fragment I made on the pagination container.
aaaaaah, this is all happening to a pagination container. (Or can you put the @connection annotation on a normal fragment container as well? Refetch Container?)
I've been using a normal fragment container, and perhaps that's what's been causing me all these problems.
@woodm1979 Yes configs are typically for the pagination container. The relay compiler will complain if you put it on any field other than something that supports being a connection. You should probably only use it on the pagination container (which technically _is_ a refetch container too).
If you don't need it to show up in a list right away (think submitting a new comment, tweet, message, etc.) passing no configs is okay as long as that connection will be refreshed for the user somehow. It's why I made my mutation function simply take the configs as an array which defaults to an empty one. Now I can use it anywhere and update all the connections that matter. I use found-relay so typically any connections on other pages get reloaded on navigation anyways so that means that for me I don't update the connection for anything not on that same page.
Anything that comes back from the mutation's query will be automatically added to your store which you can see in the relay debugger in the 'store update' tab. For instance if you just updated a field (i.e.mutation { createPerson { person { id isActive }}}) all the fields you pull back will be updated for the id's returned.
Most helpful comment
@0xCMP Thank you so much! I have only one more question, which, I THINK, gets to the heart of my issue:
You're clearly getting the viewerId from props, but I don't really know what a viewer is. I see them EVERYWHERE in relay examples, and perhaps that why I'm having such a hard time finding my "parentID".
Here's what I've been doing:
Essentially, I have a list of departments off the base query type.
But I can't come up with an id for either the query or the departments list above. Is that why I see "viewer" in so many examples? To put something with an ID in-between the query and the "base types"?