Game of Thrones Quiz Game with React and GraphQL

Create the Questions Form

We’re going to make use of some new packages for creating questions. Open a terminal in your project folder and run the following command:

npm install react-modal react-tagsinput react-toastify 8base/file-input

Let’s go through the purpose of each package:

  • react-modal - this package is useful for creating modals, and our question form will be displayed on a modal so this comes in handy.
  • react-tagsinput - each question requires options and with this package we can display an input field where multiple string values can be entered.
  • react-toastify - a great toast library for displaying a message after the question is created successfully.
  • 8base/file-input- a library from 8base for managing file uploads in GraphQL.

Question form

Let’s create the question form, this will be a simple form wrapped with a GraphQL mutation. Let’s go ahead and create a folder called question-form in the components directory. Within the question-form folder, create a file index.js.

The first thing we’ll do is define the mutation for the form. Open the file and copy the snippet into the file:

import gql from "graphql-tag";

const QUESTION_CREATE_MUTATION = gql`mutation QuestionCreate($data: QuestionCreateInput!) {
    questionCreate(data: $data) {
      id
    }
  }`;

Next, we’ll create the QuestionForm component, the component will be wrapped by the graphql higher order component. Update the file with the snippet below:

import React, { useState } from 'react';
import TagsInput from 'react-tagsinput';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';
import { FileInput } from '@8base/file-input';
import { toast } from 'react-toastify';
import 'react-tagsinput/react-tagsinput.css';
import './form.css';

const QuestionForm = ({ closeModal, questionCreate }) => {
  const [questionForm, setQuestion] = useState({
    options: [],
    question: '',
    image: {},
    answer: '',
  });

  const inputProps = {
    placeholder: 'Add an option and press enter',
    className: 'question-input',
  };
  const handleSubmit = async (e) => {
    // TODO -- 2
  };
  const handleInputChange = (event) => {
    // TODO -- 3
  };
  const handleTagsChange = (options) => {
    // TODO -- 4
  };
  const handleImageChange = (value) => {
    // TODO -- 5
  };
  return (
    // TODO -- 1
  );
};

const QUESTION_CREATE_MUTATION = gql`mutation QuestionCreate($data: QuestionCreateInput!) {
    questionCreate(data: $data) {
      id
    }
  }`;
export default graphql(QUESTION_CREATE_MUTATION, {
  name: 'questionCreate',
})(QuestionForm);

Let’s go through what’s going on in the snippet:

  1. The QuestionForm component, that takes two props closeModal and questionCreate. The closeModal function is passed from the modal component, it closes the modal when called. While the questionCreate prop is passed from the graphql HOC for mutation, the function will be passed the data required for creating a question.
  2. The inputProps object will be passed to an input element for defining it’s placeholder and className properties.
  3. And finally and a whole lot of TODOs. We’ll run through filling each gap as we progress.

First, let’s add the return value of the component, replace the TODO -- 1 comment with the snippet below:

    <form action="" id="question-form" onSubmit={handleSubmit}>
      <input
        type="text"
        name="question"
        placeholder="Enter your GOT related question...."
        onChange={handleInputChange}
        value={questionForm.question}
        className="question-input"
      />
      <TagsInput
        value={questionForm.options}
        onChange={handleTagsChange}
        maxTags={4}
        inputProps={inputProps}
      />
      <input
        type="text"
        placeholder="Add the answer to the question..."
        value={questionForm.answer}
        onChange={handleInputChange}
        className="question-input"
        name="answer"
      />
      <FileInput
        onChange={handleImageChange}
        value={questionForm.image}
        maxFiles={1}
        name="image"
      >
        {({ pick, value }) => (
          <div className="image-area">
            <button type="button" onClick={pick} className="image-upload">
              Choose File
            </button>
            <p style={{ whiteSpace: 'nowrap' }}>
              {value
                ? Array.isArray(value)
                  ? `${value.length} files selected`
                  : value.filename
                : 'No files selected'}
            </p>
          </div>
        )}
      </FileInput>
      <div className="submit-area">
        <button className="submit-button" type="submit">
          Create Question
        </button>
      </div>
    </form>

The FileInput library manages file upload, it uses FileStack to handle uploads returning the fileId and fileName. 8base uses this to identify your uploads.

Next, let’s handle form submission, replace the TODO -- 2 comment with the snippet below:

const handleSubmit = async (e) => {
    e.preventDefault();
    await questionCreate({ variables: { data: questionForm } });
    closeModal();
    toast('Your question has been created successfully');
};

Here’s what is going on:

  1. We pass an object as an argument to the questionCreate function. The object contains the data for creating a question.
  2. When the request for creating a question is complete, we close the modal by calling the closeModal function and displaying a toast.

To handle input change events, replace the TODO -- 3 comment with the snippet below:

const handleInputChange = (event) => {
  event.preventDefault();
  const {
    target: { name, value },
  } = event;
  setQuestion({
    ...questionForm,
    [name]: value,
  });
};

In the snippet above, we get the name and value from the event target, using these values we update the questionForm state value.

Finally, let’s replace comments TODO -- 4 and TODO -- 5 with the snippets below:

// TODO -- 4
const handleTagsChange = (options) => {
  setQuestion({
    ...questionForm,
    options,
  });
};

The tags input returns an array of strings entered by the user. The returned values are added to state. To enter a new value after typing, you need to press enter before the change event is triggered. Next, replace TODO -- 5 with the snippet below:

const handleImageChange = (value) => {
  setQuestion({
    ...questionForm,
    image: { create: value },
  });
};

After uploading the image, the File-Input component returns an object containing the following fields:

{
  fileId: '',
  filename: '',
  public: true
}

The fileId represents the unique identifier for the uploaded image, the filename and public fields are also required for file upload on 8base.

The stylesheet for this component can be found here. Create a CSS file named form.css in the src/components/question-form directory and copy the contents of the file on GitHub into it.

Next, let’s create the question modal component and render the QuestionForm component within it.

Like this article? Follow @codebeast on Twitter