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

How to do mutations? #1

Open
cellis opened this issue Jan 24, 2017 · 13 comments
Open

How to do mutations? #1

cellis opened this issue Jan 24, 2017 · 13 comments

Comments

@cellis
Copy link
Contributor

cellis commented Jan 24, 2017

I've already talked with @nodkz about this. Using this as a way to keep track. Essentially I want to know how to make a mutation that updates a hasMany, hasOne relationship.

I will help you with pleasure. But let keep our discussion open via github issues and use my existed repos for examples and solving problems:

  1. https:/nodkz/relay-northwind-app for the client side howtos questions (Relay specific).
  2. https:/nodkz/graphql-compose-examples for the server side GraphQL howtos questions.
  3. https:/nodkz/graphql-compose for library API

Before answering to your question, firstly we should fix knowledge about Relay Mutation API:


Khm... before playing with mutations needs to tune relayStore:
So the 1st main step will be creation of own instance of RelayEnvirement (also called as relayStore). Our relayStore will provide helper methods like mutate (for mutations) and reset (when login/logout for cleaning up store from data) Take it here https://gist.github.com/nodkz/03f28aaa1b66195b517ecf06e92487bf
Also we will add to our relayStore fetch method for manual fetching data from server (sometimes you may need data that is difficult to obtain via component fragments, eg. list of admins, or list of cities for autosuggestion component).

So fork my repo https:/nodkz/relay-northwind-app

  • try to put there somehow relayStore in app/clientStores.js
  • add Reset link for flushing Relay cache (in the footer of MainPage)

In further we will refactor the code, introduce mutations and solve tons of other small questions which will occur.

Best,
Pavel.

nodkz pushed a commit that referenced this issue Jan 24, 2017
* feat(build): Add @nodkz's relay store gist

In order to prepare for relay 2, and to make it easier to write mutations, let's use
Relay.GraphQLMutation to run mutations. In theory this will allow us to not have to use Fat/Tracked
queries.

Addresses #1

* fix(relay2Store): Make all Relay.Environment initialization in one place.

Also notice how tricky I am getting endpoint url in LoadingPage component. In clientStores.js file I keep all global variables that requires my app. It will work on client (in browser) and on server (for rendering). No need to define window.SOME_GLOBAL_VAR.
I future clientStores may keep global events, redux store, browserHistrory, modalWindow caller.
@nodkz
Copy link
Owner

nodkz commented Jan 24, 2017

Before we coming to mutations, we should complete preparation of our relayStore:

  • Let make our relayStore.mutate() method chainable via Promise. This will allows to us using async/await for complex data flow with mutations.
  • And add relayStore.fetch() method for fetching data from the server without hitting relay-router and component fragments. We will make code refactoring and start using relayStore on full power.

Right now I implement this and provide additional instructions.

nodkz added a commit that referenced this issue Jan 24, 2017
@nodkz
Copy link
Owner

nodkz commented Jan 24, 2017

So let practice with relayStore.fetch method. Mutate method has analogous symantics with additional arguments (we will consider it tomorrow, just a little patience).

So lets refactorapp/components/categories/ToggleCategory.js component which use manual data fetching.

  // native Relay API
  toggle() {
    this.setState({
      isOpen: !this.state.isOpen,
    });

    if (!this.state.data) {
      const query = Relay.createQuery(
        Relay.QL`query {
          viewer {
            category(filter: $filter) {
              ${Category.getFragment('category')}
            }
          }
        }`,
        { filter: { categoryID: this.props.id } }
      );
      relayStore.primeCache({ query }, readyState => {
        if (readyState.done) {
          const data = relayStore.readQuery(query)[0];
          this.setState({ data: data.category });
        }
      });
    }
  }

As you can see it is difficult for reading (Relay.createQuery, primeCache, readyState, relayStore.readQuery). Agrrrr.

So let rewrite it with our new relayStore.fetch method

  // our wrapper on relayStore, with simplified API
  toggle() {
    this.setState({ isOpen: !this.state.isOpen });

    if (!this.state.data) {
      relayStore
        .fetch({
          query: Relay.QL`query {
            viewer {
              category(filter: $filter) {
                ${Category.getFragment('category')}
              }
            }
          }`,
          variables: {
            filter: { categoryID: this.props.id },
          },
        })
        .then((res) => {
          // NB: Relay does not return root field, in this case `viewer`
          // so in responce we get object with fields from `viewer` (may be changed in Relay2)
          this.setState({ data: res.category }); 
        });
    }
  }

This is much much better.


@cellis please refactor all other Toggle* components (pull fresh version and create new branch).

When it will be completed let move to the server side. Clone https:/nodkz/graphql-compose-examples and try to add mutations createOneProduct and removeProduct to the schema.

nodkz added a commit that referenced this issue Jan 24, 2017
…ive API with our new `relayStore.fetch` method.

Related to #1
@nodkz
Copy link
Owner

nodkz commented Jan 24, 2017

@cellis also I want you to pay attention to fragment inclusion Category.getFragment('category') to the query. This is a replacement for fatQuery in mutations. All data required explicitly (no more complex operation on mobile devices for determining additional fields for query). So when you provide fragment in such way, data automatically updates in the Relay Store, and store touch all components that subscribed on changed data for rerendering.

And keep in mind that mutations are the regular queries. The difference only in naming (pointing that this request will change data on server) and in serial execution if several mutations in the request (several queries executes in parallel). Say it again, cause it is important, mutations in GraphQL almost similar to the queries. When we complete with queries, it will be very easy to understand and construct complex mutations with deep data fetching in the payload.

@cellis
Copy link
Contributor Author

cellis commented Jan 24, 2017

@nodkz thanks for this. Will work on this refactor tomorrow after work.

@cellis
Copy link
Contributor Author

cellis commented Jan 25, 2017

@nodkz I'm looking into refactoring the Toggles and it dawned upon me, why use the toggle() method at all? I will refactor anyways, but it seems like the act of toggling should instead be showing() (perhaps by setState({ toggled: true })) yet another sub container in relay with it's own route/"root" query. Anyways I will make it work like this, just wondering what the reasoning was. I think it could save time, it's just not documented on the relay website.

@nodkz
Copy link
Owner

nodkz commented Jan 25, 2017

Called toggle cause on pressing a button it should show/hide sub-component and change button text.

toggle() {
    this.setState({ isOpen: !this.state.isOpen });
    // ...
}

render() {
  const  { isOpen } = this.state;

  return (
     // ....
     <Button onClick={this.toggle}>
       {isOpen ? 'close' : 'open'}
     </Button>
   );
}

We may move out fetching data to another method for clearence:

toggle() {
  const isOpen = !this.state.isOpen;
  this.setState({ isOpen });
  if (isOpen) this.fetch();  
}

fetch() {
  relayStore
        .fetch({
          query: Relay.QL`query {
            viewer {
              category(filter: $filter) {
                ${Category.getFragment('category')}
              }
            }
          }`,
          variables: {
            filter: { categoryID: this.props.id },
          },
        })
        .then((res) => {
          this.setState({ data: res.category });
        });
}

Also this code makes one fix, it always will refresh component state from RelayStore on opening. Right now it fetches data only once, and when we introduce mutations and run them we will see stale data from component store, not from relayStore.

BTW relayStore.fetch() method should make http-request only first time for populating its cache. After that (when we hide data and show it again) Relay will run a new query but fulfill it from its cache without network request.

FYI you can use relayStore.fetch({ ..., force: true }) for force retrieving data from a server without clearing the cache.

@nodkz
Copy link
Owner

nodkz commented Jan 25, 2017

Anyway, current toggle implementation it is some kind of hack. Cause we keep data for child in the component state. Maybe in future when will be introduced react-router@v4 and this hack will be somehow removed. Cause RRv4 allows deep nested routes.

But for now, for demo purposes of component approach power is the good solution.

@cellis
Copy link
Contributor Author

cellis commented Jan 25, 2017

@nodkz RRv4 doesn't work with isomorphic rendering ( relay-tools/react-router-relay#193 ). So maybe we have to keep doing it this way :/

@nodkz
Copy link
Owner

nodkz commented Jan 25, 2017

I do not use react-router-relay. With its queries aggregation, it brings too many restrictions for my app. Most important:

  • can not use in sub-routes variables with the same name (eg. filter in parent component and filter in child component)

It was discussed here relay-tools/react-router-relay#213
But with my solution, you may get a waterfall of queries. But for my app it is not a problem.

@cellis
Copy link
Contributor Author

cellis commented Jan 25, 2017

Ok, refactor if Toggle* complete. Will move to other repo later tonight to createProduct mutation.

@nodkz
Copy link
Owner

nodkz commented Jan 31, 2017

Some tricks with auth and context discussed here graphql-compose/graphql-compose-examples#2

nodkz pushed a commit to graphql-compose/graphql-compose-examples that referenced this issue Feb 10, 2017
* feat(backend): Add support for backend mutation in https:/nodkz/relay-northwind-app/pull

This change simply exposes the preexisting mutation in graphql-compose-mongoose

Addresses nodkz/relay-northwind-app#1. Doesn't use the heroku db as that
would be insecure. @nodkz's call here. Perhaps next step is auth ;)

* Add heroku security

* Add heroku security file

Closes #2
@st0ffern
Copy link

@cellis if you want to use react-router-relay you can use my isomorphic router middleware for koa@2
https:/stoffern/koa-universal-relay-router

@cellis
Copy link
Contributor Author

cellis commented Feb 21, 2017

@Stoffern I have been using it in my personal project. But right now react-router-relay is hardcoded to relay 0.9.3 which means it doesn't support the new Relay GraphQLMutation stuff.

nodkz pushed a commit that referenced this issue Sep 27, 2017
…2 Store

Relay 2 will use GraphQLMutation, so we should start getting on this train

Addresses #1
nodkz pushed a commit that referenced this issue Sep 27, 2017
Use the new relay store to create a mutation and send it to local backend. WIP

Addresses #1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants