Treats

Treats

  • Getting Started

›Tutorial

Getting Started

  • Installation

Tutorial

  • 01. Creating Your First Page
  • 02. Using Redux
  • 03. Using GraphQL
  • 04. Fetch Data for SSR
  • 05. Adding Addons

Main Concepts

  • Overview
  • Routing
  • Localization
  • Code-splitting
  • Redux
  • GraphQL Client
  • Middlewares
  • Helpers
  • Server-side Events
  • Server-side Template
  • Server-side Rendering
  • Custom Server App
  • Custom Client Initialization
  • Custom React App
  • Runtime Config
  • Build Config
  • Environment Variables
  • Code Generator
  • Scripts
  • Addons
  • Typescript
  • Workbox

API Reference

  • Overview
  • Filesystem Hooks
  • Components
  • Server
  • Client
  • Router
  • Intl
  • Locale Data
  • Helmet
  • Redux
  • Graphql

Authoring Addons

  • Overview
  • Helpers
  • Middlewares
  • Generators
  • Wrapping Up

Addons

  • Treats Addons List

Contributing

  • How To Contribute

FAQ

  • FAQs

03. Fetch and Submit Data with GraphQL

So far, our todo apps can insert custom name in profile page and can insert todo list. But this todo app is less helpful since we cannot track the progress of our todo items nor we can edit or delete it. Let's upgrade our todo apps! While upgrading it, we will also get closer into Treats GraphQL. Before starting this tutorial, you may want to read more about GraphQL. GraphQL is also not mandatory in Treats. If you want to disable it you can change it in treats.config.js as instructed here.

GraphQL Configurations

Treats GraphQL configurations are under _graphql folder. GraphQL hooks contains 3 files: link-state.js, uri.js, and config.js. In this tutorial you need to deploy some GraphQL service. We recommend to use Graphcool to deploy your own GraphQL service.

  1. Deploy the following todo datamodel. For a quick start, you can use Graphcool console to create our schema.
type Todo {
  id: ID! @isUnique
  status: String
  todoAction: String
}

You can get your graphql endpoint easily from the "endpoints" button in Graphcool console. The format will be like this: https://api.graph.cool/simple/v1/<key>

  1. After getting your endpoint, create _graphql folder and uri.js under the folder.
/* src/_graphql/uri.js */

const uri="https://api.graph.cool/simple/v1/<your_key>";

export default uri;
  1. Implement config.js under _graphql folder. This config are based on Apollo configuration.
/* src/_graphql/config.js */

import uri from "./uri";

const customConfig = {
    queryDeduplication: true,
    link: [
        {
            type: "error",
            callback: ({ graphQLErrors, networkError }) => {
                if (graphQLErrors)
                graphQLErrors.map(({ message, locations, path }) =>
                    console.error(
                        `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
                    ));

                if (networkError) console.error(`[Network error]: ${networkError}`);
            }
        },
        {
            type: "batch-http",
            uri: uri
        }
    ]
};

export default customConfig;
  1. Of course, we need to declare the query and some mutation for our todo page let's create todo folder under _graphql and create index.js, query.graphql, and mutation.graphql.
/* src/_graphql/index.js */

import * as todoQuery from "./query.graphql";
import * as todoMutation from "./mutation.graphql";

export { todoQuery };
export { todoMutation };
# src/_graphql/query.graphql
query GetAllTodoes {
  allTodoes {
    id,
    todoAction,
    status
  }
}
# src/_graphql/mutation.graphql
mutation CreateTodo($todoAction: String!) {
  createTodo(
    todoAction: $todoAction
    status: "not yet"
  ) {
    id,
    todoAction,
    status
  }
}

mutation DeleteTodo($id: ID!) {
  deleteTodo(
    id: $id
  ) {
    id,
    todoAction,
    status
  }
}

mutation UpdateTodo($id: ID!, $todoAction: String) {
  updateTodo(
    id: $id,
    todoAction: $todoAction
  ) {
    id,
    todoAction
  }
}

mutation UpdateTodoStatus($id: ID!, $status: String) {
  updateTodo(
    id: $id,
    status: $status
  ) {
    id,
    status
  }
}
  1. That's the GraphQL config we need for now. Read more in the GraphQL Client section.

Refactoring Todo List

Our current todo list still not connected to our GraphQL configuration we just created. Now it's time to refactor!

  1. Back to our todo-list first. Since our item now have state (done or pending) we can not show the item using literal string. We need to create new component for our todo items. Let's create one under component. You may use the component boilerplate generator.
/* src/component/todo-item/todo-item.js */

import React, { Component } from "react";

import style from "./todo-item.css";

class TodoItem extends Component {
    render () {
      const { id, todoAction, status } = this.props;

      return (  
        <div >
            <form
                onSubmit={e => {
                    e.preventDefault();
                }}
            >
                <input
                    ref={node => {
                        todo = node;
                    }}
                    defaultValue={todoAction}
                    readOnly={status === "done"}
                />
                <span>
                    <button
                        className={style.button__orange} 
                        type="submit"
                    >
                        Update Todo
                    </button>
                    <button
                        className={style.button__green}
                    >
                        Is it done?
                    </button>
                </span>
                <button 
                    className={style.button__red}
                >
                    Delete this todo
                </button>
            </form>
        </div>
      )
    }
};
  
export default TodoItem;
/* src/component/todo-item/todo-item.css */

.button__green {
    cursor: pointer;
    background-color: #42b549;
    color: #fff;
    font-weight: 700;
    margin: 10px;
}

.button__red {
    cursor: pointer;
    background-color: #d50000;
    color: #fff;
    font-weight: 700;
    margin: 10px;
}

.button__orange {
    cursor: pointer;
    background-color: #f13700;
    color: #fff;
    font-weight: 700;
    margin: 10px;
}
  1. As we can see, the item now contains buttons for update todo item, update status, and delete todo. To make our buttons do something, we need to connect it with mutation we defined earlier in mutation.graphql. To connect our components, we can use <Mutation> component provided by Treats's GraphQL (Apollo) client.

Let's also create alias for _graphql and component, just like we've done on Redux.

// treats.config.js

const path = require("path");

const config = {
    ....
    alias: {
        "@page": path.resolve(__dirname, "./src/page"),
        "@redux": path.resolve(__dirname, "./src/redux"),
        "@graphql": path.resolve(__dirname, "./src/_graphql")
        "@component": path.resolve(__dirname, "./src/component")
    }
};

module.exports = config;
/* src/component/todo-item/todo-item.js */

import React, { Component } from "react";

import { Mutation } from "@treats/graphql";

import { todoMutation, todoQuery } from "@graphql/todo";

import style from "./todo-item.css";

class TodoItem extends Component {
    render () {
        const { id, todoAction, status } = this.props;

        return (
            <Mutation 
                mutation={todoMutation.DeleteTodo} 
                variables={{ id }} 
                refetchQueries={[{ query: todoQuery.GetAllTodoes }]}    
            >
                {(deleteTodo, { loading: deleteLoading, error: deleteError }) => (
                    <Mutation 
                        mutation={todoMutation.UpdateTodo} 
                        variables={{ id, todoAction }} 
                        refetchQueries={[{ query: todoQuery.GetAllTodoes }]}    
                    >
                        {(updateTodo, { loading: updateLoading, error: updateError }) => (
                            <Mutation 
                                mutation={todoMutation.UpdateTodoStatus} 
                                variables={{ id, status: "done" }} 
                                refetchQueries={[{ query: todoQuery.GetAllTodoes }]}    
                            >
                                {(updateTodoStatus, { loading, error }) => {
                                    let todo;

                                    return (
                                        <div >
                                            <form
                                                onSubmit={e => {
                                                    e.preventDefault();
                                                    updateTodo({ variables: { todoAction: todo.value, id } });
                                                }}
                                            >
                                                <input
                                                    ref={node => {
                                                        todo = node;
                                                    }}
                                                    defaultValue={todoAction}
                                                    readOnly={status === "done"}
                                                />
                                                {status !== "done" && (
                                                    <span>
                                                        <button
                                                            className={style.button__orange} 
                                                            type="submit"
                                                        >
                                                            Update Todo
                                                        </button>
                                                        <button
                                                            className={style.button__green} 
                                                            onClick={updateTodoStatus}
                                                        >
                                                            Is it done?
                                                        </button>
                                                    </span>
                                                )}
                                                <button 
                                                    className={style.button__red}
                                                    onClick={deleteTodo}
                                                >
                                                    Delete this todo
                                                </button>
                                            </form>
                                            {loading || (deleteLoading || updateLoading) && <p>Loading...</p>}
                                            {error || (deleteError || updateError) && <p>Error :( Please try again</p>}
                                        </div>
                                    );
                                }}
                            </Mutation>            
                        )}
                    </Mutation>
                )}
            </Mutation>
        );
    }
};
  
export default TodoItem;

Don't worry if it's long. If you read it carefully, we add every mutation into a single <Mutation> component. That's why there are 3 nested Mutation out there. Also we add some network error handler like {loading || (deleteLoading || uploadLoading) ...} and {error || (deleteError || updateError) ...}. That makes our items contain 3 mutations updateTodo, updateStatus, and deleteTodo.

  1. To make these mutation works, of course we also need to make the list stored in our GraphQL server. If it's stored in the server, then we need to fetch it when rendering our todo-list component. We're going to use <Query> components to get our data from GraphQL server in todo-list.js.
/* src/component/todo-list/todo-list.js */

import React from "react";

import { Query } from "@treats/graphql";

import { todoQuery } from "@graphql/todo";

import TodoItem from "../todo-item";

const TodoList = () => (
    <Query 
        query={todoQuery.GetAllTodoes}
        ssr={false}
        fetchPolicy="cache-and-network"
    >
        {
            ({ loading, error, data }) => {
                if (loading) return <p>Loading....</p>
                if (error) return <p style={{"color": "red"}}>ERROR</p>
                if (data.allTodoes) {
                    return (
                        <div>
                            {data.allTodoes.map(item => (
                                <TodoItem key={item.id} {...item} />
                            ))}
                        </div>
                    );
                }
                return (
                    <div>
                        No Data
                    </div>
                )
            }
        }
    </Query>
);
  
export default TodoList;
  1. Yay! Now we can get data from GraphQL server. Wait a minute... But where the server get the data from?. We must create add CreateTodo mutation into our form in todo.js.
/* src/page/todo/todo.js */

import React from "react";
import { FormattedMessage, injectIntl } from "@treats/intl";
import Link from "@treats/component/link";
import { connect } from "@treats/redux";
import { Mutation } from "@treats/graphql";

import { todoMutation, todoQuery } from "@graphql/todo";

import TodoList from "@component/todo-list";

import style from "./todo.css";

const Todo = ({ intl, name }) => (
    <div>
        ...
        <Mutation
            mutation={todoMutation.CreateTodo}
            refetchQueries={[{ query: todoQuery.GetAllTodoes }]} 
        >
            {(addTodo, { loading, error }) => {
                let input;

                if (loading) {
                    return (<p>Loading...</p>)
                }

                if (error) {
                    return (<p>Error :( Please try again</p>)
                }

                return (
                    <div className={style.todo_form}>
                        <form onSubmit={e => {
                            e.preventDefault();
                            addTodo({ variables: { todoAction: input.value } });
                            input.value = "";
                        }}>
                            <input ref={
                                node => {
                                    input = node;
                                }
                            } />
                            <button>{intl.formatMessage({id: "submit"})}</button>
                        </form>
                    </div>
                )
            }}
        </Mutation>
        <TodoList />
        ...
    </div>
)

const mapStateToProps = state => ({
    name: state.profile.name
})

export default connect(mapStateToProps)(injectIntl(Todo));
  1. All's done! Let's check our todo in GraphQL now! Pretty cool, isn't it? With some touch of css, now you can make this todo apps into more beatiful page.
← 02. Connecting Components with Redux04. Fetching Data for Server-Side-Render →
Tokopedia Open Source
Copyright © 2019 Tokopedia OSS