relay: Relay modern Mutation changes in relay store but doesn't updates the UI

I don’t know if it’s an issue, I’m new to Relay… After doing the mutation in relay modern,

 const mutation = graphql`
       mutation addPostMutation (
       $input: addPostData!
      ) {
           addPost(data: $input) {
           node {
              id
              content
       }
      }
     }
   `;

    commitMutation(
          environment,
          {
              mutation,
             variables,
              onCompleted: (res) => {
               console.log(environment)
               console.log(res)
        },
         onError: err => console.error(err)
      }
     )

when I console.log environment, I can see the newly added post in the relay store, but it’s not updated in the UI. However, after refreshing the app, the changes are made. This is my schema.graphql

schema { query: Root mutation: Mutation }

type Mutation { addPost(data: addPostData!): addPostMutation }

input addPostData { userID: String, post: String, tags: [String] }

type addPostMutation { node: postNode }

interface Node { id: ID! }

type postEdges { node: postNode }

type postNode implements Node { id: ID! content: String }

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 6
  • Comments: 51 (11 by maintainers)

Most upvoted comments

Just giving this a bump as we are having the same problem.

There is no clarity on how to update your UI on a Mutation. The only source of information we have is from GitHub.

We do not use an edge/node schema in the same way that the Facebook Todo example does, thus making it incredibly difficult to find any indication of the way to update our store and UI with a successful mutation.

Most of all, it isn’t clear why the UI doesn’t refresh with the store changes using :

const updater = (store) => {
        let rootField = store.getRootField(`createFundingSource`)
        store.getRoot().copyFieldsFrom(rootField)
}

Would be good to have some clearer documentation on the mutations and updaters.

Thanks @cipater! That, plus reading the RelayConnectionHandler source, gave me the last bits I needed. It works now! 😄

Posting my solution here for others. This is my updater function:

import {ConnectionHandler} from 'relay-runtime';

...

updater: (store) => {
  const payload = store.getRootField('createReport');
  const newReport = payload.getLinkedRecord('report');
  const storeRoot = store.getRoot();
  const connection = ConnectionHandler.getConnection(
    storeRoot,
    'ReportList_reports',
  );
  const newEdge = ConnectionHandler.createEdge(
    store,
    connection,
    newReport,
    'ReportEdge',
  );
  ConnectionHandler.insertEdgeBefore(connection, newEdge);
},

The fragment in my ReportList component:

export default createFragmentContainer(
  ReportList,
  graphql`
    fragment ReportList_query on Query {
      reports(
        first: 2147483647 # max GraphQLInt
      ) @connection(key: "ReportList_reports") {
        edges {
          node {
            id
            mongoId
            name
          }
        }
      }
    }
  `,
);

yeah right. What other approach can we use other than manually updating the store?

The response is normalized in the store, but it is not appended in getPostsQuery Basically, you want something like this

const updater = proxyStore => {
  const newEdgeNode = proxyStore.getRootField('addPost'),
  // since 'AllPosts' is under Root
  const prevPosts = proxyStore.getRoot().getLinkedField('AllPosts');
  const prevEdgeNodes = prevPosts && prevPosts.getValue('edges');
  if (prevEdgeNodes) {
    prevEdgeNodes.push(newEdgeNode); // You might want to append or prepend
    prevPosts.setLinkedRecords(prevEdgeNodes, 'edges');
  }
}

we have this helpers to update connections

export function connectionUpdater({ store, parentId, connectionName, edge, before, filters }) {
  if (edge) {
    if (!parentId) {
      // eslint-disable-next-line
      console.log('maybe you forgot to pass a parentId: ');
      return;
    }

    const parentProxy = store.get(parentId);

    const connection = ConnectionHandler.getConnection(parentProxy, connectionName, filters);

    if (!connection) {
      // eslint-disable-next-line
      console.log('maybe this connection is not in relay store yet:', connectionName);
      return;
    }

    if (before) {
      ConnectionHandler.insertEdgeBefore(connection, edge);
    } else {
      ConnectionHandler.insertEdgeAfter(connection, edge);
    }
  }
}

Usage:

const newEdge = store.getRootField('createChat').getLinkedRecord('chatEdge');

connectionUpdater({
        store,
        parentId: viewer.id,
        connectionName: 'LinkList_allChats',
        edge: newEdge,
        before: true,
      });

Returning an edge from mutation makes things easier, but you could also create the edge on client

Hi, I am running into the same issue here (relay 1.4.0). I’m using the viewer pattern, but I am NOT using connections and pagination as I don’t require that at the moment.

The mutation I am performing gets correctly updated into the relay store, and is correctly inserting records through my updater function, but Relay never automatically updates the UI.

My work around at the moment is to run a completed callback which runs setState on the component to manually update the UI. I am happy with that for now. But it would be nice to know how to get Relay to update the UI after a mutation is successfully performed?

For anyone interested here is how my updater and onCompleted function looks like. is there anything I am missing here?

Also I am using found-relay for my routing. I haven’t heard this mention by anyone. But wondering if this is an element tat could be affecting this issue as well?


Example graphQL mutation input:

mutation CreateTimerMutation($input: CreateTimerInput!){
  createTimer(input: $input) {
    createdTimer{
      id, 
      name, 
      parentTimerId
    }
  }
}

Example graphQL mutation payload:

{
  "data": {
    "createTimer": {
      "createdTimer": {
        "id": "534bec16-42c6-4e09-806c-10a2ac5680f8",
        "name": "New timer 3",
        "parentTimerId": null
      }
    }
  }
}

Updater and onCompleted mutation configs:

    updater(store) {
      const payloadProxy = store.getRootField('createTimer');
      const createdTimerProxy = payloadProxy.getLinkedRecord('createdTimer');

      const viewerProxy = store.getRoot().getLinkedRecord('viewer');
      const timersProxy = viewerProxy.getLinkedRecords('timers');

      if(timersProxy){
        timersProxy.push(createdTimerProxy)
        viewerProxy.setLinkedRecords(timersProxy, 'timers')
      }
    },
    onCompleted(response, errors) {
      //this runs setState on the component with the update data from viewer
      //I'd rather Relay update the UI automatically
      completed() 
    }

Schema: here’s the related schema with relevant sections included:

interface Node {
  id: ID!
}

schema {
  query: Query
  mutation: Mutation
}

type Query { 
  viewer: User
}

type Mutation {
  createTimer(input: CreateTimerInput!): CreateTimerMutationPayload
}

type User implements Node {
  id: ID!
  timers: [Timer!]!
}

type Timer implements Node {
  id: ID!
  parentTimerId: ID
  name: String
...
}

input CreateTimerInput {
  parentTimerId: ID
  name: String
...
  clientMutationId: String
}

type CreateTimerMutationPayload {
  createdTimer: Timer
  clientMutationId: String
}

My query on the component

export const AdminTimersQuery = 
graphql`
    query adminTimers_Query {
      viewer {
        id
        ...adminTimers_viewer
      }
    }
`;

and the related fragment…

export default withRouter(createFragmentContainer(
  AdminTimers, 
  graphql`
    fragment adminTimers_viewer on User {
      timers {
        id,
        name,
        parentTimerId
      }
    }
  `));

hmm, I don’t think @saudpunjwani101 is asking about pagination container. What I mean by “refetch” is that you could refetch the connections in the mutation payload. However, this involves changes in your current schema. I’m not sure how to modify your schema, but we usually have

type Query {
  # ...
  viewer: Viewer
}

type Viewer {
  # ...
  reports(after: String, first: Int, before: String, last: Int): ReportConnection
}

type Mutation {
  createReport(input: CreateReportInput!): CreateReportPayload
}

type CreateReportPayload {
  report: Report 
  clientMutationId: String
  viewer: Viewer
}

Then the query fragment will be

  graphql`
    fragment ReportList_viewer on Viewer {
      reports(
        first: 20 # I don't think a max int makes sense 
      ) {
        edges {
          node {
            id
            mongoId
            name
          }
        }
      }
    }
  `,

And the mutation will be

  mutation CreateReportMutation(
    $input: CreateReportInput!
  ) {
    createReport(input: $input) {
      viewer {
       ...ReportList_viewer # You could use fragment spread here
      }
    }
  }

Then there is no need to have your updater function. The one downside of this is that it will be cumbersome to provide an optimistic response.

In your case, if you really want to fetch many edges in the query, then it makes sense to use @connection and give a customized updater function because it will be too costly to fetch everything again in the mutation. In that case, you could just fetch the newly created edge and use helper functions in RelayConnectionHandler to update the client side (and this is exactly what you are doing now).

I got struck with this problem where it didn’t update the UI in React-native App but work in Web. Thank you @erikfrisk for saving my time not to move to Apollo.

I had a similar problem: I needed to update specific query when mutation was committed. I had this query in my header component

Person(where: $where) {
    id
    title
    firstName
    middleName
    lastName
    gender
  }

where = { id: { eq: '7' } }

So in relay store there was record Person{"where":{"id":{"eq":"7"}}} and it was null.

In my mutations config in updater function i wrote this:

(store) => 
    const record = store.get(store.getRootField('login').getLinkedRecord('user').getValue('id'));
    store.getRoot().setLinkedRecords([record], 'Person', { where: { id: { eq: '7' } } })

my mutations response looks like this: { login: { user: { id: …} } }

I know it’s very specific case but now it’s possible to work with it further. Basically the idea is to get whatever comes from the server find the needed record in the relay store and just reset it.

Or if you have MeQuery query all you need to do to update it in relay store is store.getRoot().setLinkedRecord(record, 'MeQuery');

@saudpunjwani101 A common practice is to have a structure such as:

RefetchContainer // to refetch the connection in-full when filter arguments change
  PaginationContainer // to fetch more items with the same filter arguments
    Array<FragmentContainer> // to render each edge