Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Q: How would I update the Apollo store after creation? #180

Closed
WilliamHolmes opened this issue May 6, 2016 · 23 comments
Closed

Q: How would I update the Apollo store after creation? #180

WilliamHolmes opened this issue May 6, 2016 · 23 comments
Assignees
Milestone

Comments

@WilliamHolmes
Copy link

I'm receiving real-time data from another channel (WS).

I was hoping to update the apollo store much like queries and mutations, but I'm not sure how to go about this.

Is there an option or approach which creates a mutation with some data but not to actually send that mutation to the server?

UseCase:
Apollo Store > Query > Get All Comments > store gets updated > React UI updates
New Comments arrive via a web socket

My Issue
I would like to update my React Component(s) (via ApolloClient & ReactApollo).

I guess this pattern is not specific to apollo-client, and more general to any graphQL client using store (such as redux)

@stubailo
Copy link
Contributor

stubailo commented May 6, 2016

Ooh, this is something I've been thinking about but haven't documented, as part of #172 (writing up reactivity designs).

I really want to make this work for you, so that we can expand it into a first-class feature of Apollo Client. Can you give me a bit more info? Specifically:

  1. What is the shape of the data that comes in over the websocket
  2. What do the queries that you're trying to update look like

Because Apollo Client has the ability to normalize data by ID, I think we can come up with something awesome. The ID normalization is implemented and tested, just not turned on in the public API -- I suspect making that feature available will help a lot.

@WilliamHolmes
Copy link
Author

WilliamHolmes commented May 6, 2016

The data is a structured JSON response, the structure itself does not match the schema, but of course the data keys/values do.
ie (Someone creates a New toDo).

Incoming Data
{"type":"todo_created","ToDo":{"id":"urn:lsid:572d058c9932c8bb19416853","title":"NewToDo","published":1462568332749,"createdBy":"e8e73f40-ba74-102b-8a1d-c574ebd76cf3"}}

Query
{ node (id: "urn:lsid:572d058c9932c8bb19416853") { ... on ToDo { id title published createdBy } } }

General get ToDo query
{ viewer { ToDo(first: 10) { edges { node { id title createdBy published } } } }

The ID normalization seems like it might work for me.

const fragment = gql`
  fragment node on ToDo {
    id,
    title
    published
    createdBy
  }
`;

const result = {
  id: "urn:lsid:572d058c9932c8bb19416853",
  title: "NewToDo",
  published: 1462568332749,
  createdBy: "e8e73f40-ba74-102b-8a1d-c574ebd76cf3"
};

BTW: Are you using _.deepclone (in that unit test) due to your test case reusing the result obj, if not at its always required, may be better placed within the writeFragmentToStore function for convenience.

@stubailo
Copy link
Contributor

stubailo commented May 6, 2016

Are you using _.deepclone (in that unit test) due to your test case reusing the result obj

Exactly. I ran into an error before where I was accidentally mutating the result object, and that made the test pass even though the code was wrong. So nothing to worry about - just a test artifact.

@stubailo
Copy link
Contributor

stubailo commented May 6, 2016

OK so it looks like you should be able to use the fragment you wrote and use writeFragmentToStore - only thing missing is the ability to inject the idGetter into ApolloClient so that queries and mutations use it as well!

Going to set that up asap.

@stubailo stubailo added this to the 5/10 cycle milestone May 6, 2016
@stubailo stubailo self-assigned this May 6, 2016
@smolinari
Copy link
Contributor

smolinari commented May 7, 2016

Sort of an on-topic tangent, but wouldn't a streaming API into Apollo-Client be outside the GraphQL spec?

Scott

@stubailo
Copy link
Contributor

stubailo commented May 7, 2016

Sure, but the spec applies to what the server does, not how the client uses that data.

@WilliamHolmes
Copy link
Author

The alternative is to make an additional query for data we already have on the client, just to populate the apollo store on the client.

@stubailo
Copy link
Contributor

stubailo commented May 7, 2016

Yep, basically firing a fake query result event!

@smolinari
Copy link
Contributor

Sure, but the spec applies to what the server does, not how the client uses that data.

I guess I was getting my impression from the GraphQL introduction the Facebook team did last year. I seem to remember them saying they explored a streaming capability and deciding to not go that direction for Facebook's purposes (in their GraphQL API). You are right, the spec doesn't even mention how the data is transported or ingested by either the client or the server actually.

👍 to a streaming capability. Could HTTP2 push also be something to use as the transport?

https://en.wikipedia.org/wiki/HTTP/2_Server_Push

Scott

@dahjelle
Copy link
Contributor

I was just running into this as well. In our case, we can subscribe to a CouchDB _changes feed that has data in basically the same shape as the results for our GraphQL queries. If I'm understanding this conversation correctly, if I can make

  1. a GraphQL fragment that corresponds to the data I got from _changes,
  2. a mocked response to that fragment

then i can use the writeFragmentToStore function to update the store, correct?

@lorensr
Copy link
Contributor

lorensr commented May 11, 2016

This might be useful for mixing with Meteor pubsub? Instead of putting data from DDP in minimongo, put it in Redux?

@stubailo
Copy link
Contributor

then i can use the writeFragmentToStore function to update the store, correct?

Exactly!

And yes, this could also be a way to put Meteor pubsub data into the Apollo store if one desired. I think this might call for a new API on ApolloClient to inject external data, just to make the approach standardized.

@dahjelle
Copy link
Contributor

Thanks—I have some code working that updates existing items in the store.

Still have a follow up question, though: if the result of my query is a list of items, and I have a new item (rather than an update to an existing item), how should that be handled? Do I have to refetch the query, or is there a way to let apollo know where the data ought to be added in?

I see the ROOT_QUERY section of the apollo store…do I need to manually add to that?

@stubailo
Copy link
Contributor

Yeah that's definitely one thing we need to figure out - it's not enough to just insert the new object, you also need to update any lists or references to include it:

{
  todoList(id: 4) {
    todos { id, content, completed }
  }
}

This may return:

{
  todoList: {
    todos: [ ... ]
  }
}

Now when we insert a new todo item we need to make sure it's inserted into that todos array.

Here's one option:

const newTodoItem = {
  id: '7',
  content: 'This is a task!',
  completed: false,
};

const query = gql`
  {
    todoList(id: 4) {
      todos { id, content, completed }
    }
  }
`

const result = readQueryFromStore({ query, store });

// Now, we have a query-shaped data blob, let's add the new item
result.todoList.todos.push(newTodoItem);

state = writeQueryToStore({
  result,
  query,
  store,
  dataIdFromObject,
});

This way, we update the store without actually dealing with the store format. What do you think?

@dahjelle
Copy link
Contributor

Where does store come from in this case? I was expecting it to be state.apollo from within a reducer, but that doesn't seem to be what you are referring to…

@stubailo
Copy link
Contributor

Oh yeah that is what it should be. The "store" parameter in those functions is pretty misnamed... All of the code I posted would go in the reducer.

@dahjelle
Copy link
Contributor

Still working through your example, but I think it actually needs state.apollo.data, correct?

I'll give this a shot…and let you know how it goes. Thanks again!

@stubailo
Copy link
Contributor

Yeah, sorry - maybe I should put it into a gist or something so that I can update it more easily...

@dahjelle
Copy link
Contributor

I'm not sure where the best place to put thoughts/notes on reactivity is, but since I've been discussing it here so far, I'll continue. Just wanted to share a few things I've been running in to and thinking here.


An issue that I just ran in to with this (and is perhaps immediately obvious, but took some time for me to realize) is that if you do a writeQueryToStore for

  {
    todoList(id: 4) {
      todos { id, content, completed }
    }
  }

as in the example above, it won't update the results for a query like

  {
    todoList {
      todos { id, content, completed }
    }
  }

and vice-versa.

This, of course, makes sense: at some point, you'd have to have the entire logic of the GraphQL server to satisfy something like

{
  todoList(limitToFiveMostUrgent: true) {
    todos { id, content, completed }
  }
}

That said, it seems like some functions that would allow us to do some of the following would be really helpful:

  • find out what queries are already under ROOT_QUERY for a particular root query type (i.e. is there already a toDoList({"id": "4"}) there that should be updated?
  • the idea of batch-updating a set of queries sounds nice, but I'm not sure that is generalizable
  • there may be a set of common arguments (such as id, ids, startAt) that could be specified to allow apollo to "automatically" update the ROOT_QUERY on its own

Some of this is maybe already done in the mutationResult work—I haven't dug in enough to that yet to really understand all that's happening there.

@RobinGa
Copy link

RobinGa commented Aug 1, 2016

I have a question which is maybe very simple but I can't find the answer anywhere else (I am very new to apollo, thank for all the work achieved so far!)

I have a websocket streaming data into my store and I want to update my components without refetching data. I used this thread to achieve it but my component connected to apollo client by mapQueryToProps won't update:

const state = store.getState().apollo;
const data = readQueryFromStore({
  store: state.data,
  query: myQuery,
});

/*
 mutate the data
 insert some element and delete others
 ...
*/

state.data = writeQueryToStore({
  result: data,
  query: myQuery,
  store: state.data,
  dataIdFromObject,
});

Is there any redux actions I need to dispatch in order to update components connected with mapQueryToProps to this query? Is there something I am missing?

@stubailo
Copy link
Contributor

stubailo commented Aug 4, 2016

You need to do this in a Redux reducer. Redux doesn't have any way of telling when state has changed, so you should fire a custom action and do this inside the reducer.

Also, you can use a new feature we just added, updateQuery: https://github.com/apollostack/apollo-client/blob/master/CHANGELOG.md#v0411

Although I'm not sure react-apollo supports it.

@helfer helfer added this to the New API/Refactor milestone Sep 29, 2016
@helfer
Copy link
Contributor

helfer commented Oct 26, 2016

Should be easy now with #766

@dahjelle
Copy link
Contributor

If anyone is coming to this in the future, I see that writeFragment and writeQuery are the new methods to implement this kind of behavior, amongst other things. See the blog post for more information.

jbaxleyiii pushed a commit that referenced this issue Oct 18, 2017
Clarify what is "Query" key in `customResolvers` example
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 17, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants