Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough

Apollo Client React Hooks with Drupal GraphQL - useMutation in Client, part 3

Parent Feed: 

In the previous article we’ve shown how to get data with useQuery from a Drupal powered GraphQL API. Fetching data is fun, but even more fun is to be able to send data to an API as part of interactivity features that our user might need to enjoy when using our apps. To do so in a more modern way Apollo now provides us with the useMutation()-hook. Let’s see how to make use of it.

Apollo Client React Hooks with Drupal GraphQL

Published on 2019-09-07 at 18:45

Drupal decoupled React.js GraphQL Apollo

Updating our Drupal GraphQL API to support mutations

First thing needed is to make Drupal aware of the fact that we want it to receive and store data via the GraphQL implementation. In the first part of this article series we already made the preparations so that Drupal knows how to handle GraphQL-queries. Let’s extend these so that players can be created via mutations as well. The first change needs to happen in the graphql/players.graphqls file of our custom module:

schema {
  query: Query
  mutation: Mutation
}

...

type Mutation {
  createPlayer(data: PlayerInput): Player
}

input PlayerInput {
  first_name: String!
  last_name: String!
}

The schema block receives the command mutation, which points to the new type “Mutation”. Inside of it we define the command and the input/data it needs, in order to be successful. We need two more changes, first update the resolvers in our Drupal schema - see changes src/Plugin/GraphQL/Schema/PlayersSchema.php:

addQueryFields($registry, $builder);
    $this->addMutationFields($registry, $builder);
    $this->addPlayerFields($registry, $builder);

    // Re-usable connection type fields.
    $this->addConnectionFields('PlayerConnection', $registry, $builder);

    return $registry;
  }

  ...

  /**
   * @param \Drupal\graphql\GraphQL\ResolverRegistry $registry
   * @param \Drupal\graphql\GraphQL\ResolverBuilder $builder
   */
  protected function addMutationFields(ResolverRegistry $registry, ResolverBuilder $builder) {
    // Create player mutation.
    $registry->addFieldResolver('Mutation', 'createPlayer',
      $builder->produce('create_player')
        ->map('data', $builder->fromArgument('data'))
    );
  }

  ...

}

We create a new addMutationFields()-method where we assign the create_player producer to the mutation and set the data needed. The producer receives this data as an argument it its’ resolve method and handles the logic for manipulating and saving it, see src/Plugin/GraphQL/DataProducer/CreatePlayer.php:

get('current_user')
    );
  }

  /**
   * CreatePlayer constructor.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param array $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   */
  public function __construct(array $configuration, $plugin_id, array $plugin_definition, AccountInterface $current_user) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->currentUser = $current_user;
  }

  /**
   * Creates a player.
   *
   * @param array $data
   *   The title of the job.
   *
   * @return \Drupal\node\NodeInterface
   *   The newly created player.
   *
   * @throws \Exception
   */
  public function resolve(array $data) {
    if ($this->currentUser->hasPermission('create player content')) {
      $first_name = $data['first_name'];
      $last_name = $data['last_name'];

      $values = [
        'type' => 'player',
        'title' => $first_name . ' ' . $last_name,
        'field_first_name' => $first_name,
        'field_last_name' => $last_name,
      ];
      $node = Node::create($values);
      $node->save();

      return $node;
    }

    return NULL;
  }

}

Visit the GraphiQL explorer and make sure the mutation is working as here:

Create player mutation in GraphiQL explorer
Create player mutation in GraphiQL explorer

Make the mutations from the React.js-app with useMutation()

Something important first. As this article does not handle authentication etc. make sure to loosen up Drupals’ permission system appropriately, so that anonymous users also have the permission “Player: Create new content” - something you probably must reconsider in production.

Let’s start by adding our new GraphQL-query to src/gql/common.js:

export const CREATE_PLAYER = gql`
mutation CreatePlayer ($first_name: String!, $last_name: String!) {
  createPlayer (data: {first_name: $first_name, last_name: $last_name}) {
    id
  }
}
`;

With that in place, we can turn our attention to the new component itself - create a src/components/CreatePlayer.js-file:

import React from 'react';
import { useMutation } from '@apollo/react-hooks';
import { CREATE_PLAYER, PLAYERS } from '../gql/common';

const CreatePlayer = () => {
  const [createPlayer, { loading }] = useMutation(CREATE_PLAYER);

  return (
    
      {loading ? 

Saving player...

: ''}
); } export default CreatePlayer;

Right at the beginning of the component we call the useMutation()-hook and pass it the new query. The hook is fired, when the appropriate function is called, as defined by the hook, in this case createPlayer(). This function get passed an object as an argument with the variables for the query, but also an array of queries that need to be re-fetched, so that Apollo gets the most recent data. At the end, we create a new player here and the players data would need to be updated and therefore the Players-component re-rendered.

Last but not least, let’s add this new component to the Overview.js:

import React from 'react';
import Players from './Players';
import CreatePlayer from './CreatePlayer';

const Overview = () => {
  return (
    
  );
}

export default Overview;

Let’s try out or new code:

Create new player
Create new player

The code using useMutation() looks pretty slick and well isolated. One of the main advantages of this hook is that it can be reused multiple times in a single component, e.g. to update and delete an entity, something not possible prior with the HOC-approach. Check out the code for backend and frontend in the respective repositories.

Find me on LinkedIn or Twitter with any questions you have on this topic, looking forward to get in touch with you.

Author: 
Original Post: