Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GraphQL at Product Hunt

GraphQL at Product Hunt

Radoslav Stankov

October 15, 2017
Tweet

More Decks by Radoslav Stankov

Other Decks in Technology

Transcript

  1. GraphQL
    at
    Product Hunt
    Radoslav Stankov 20/10/2017

    View Slide

  2. Radoslav Stankov
    @rstankov

    http://rstankov.com

    http://github.com/rstankov

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. html
    html
    json

    View Slide

  8. json
    json

    View Slide

  9. View Slide

  10. View Slide


  11. http://graphql.org/


    View Slide

  12. View Slide

  13. View Slide

  14. View Slide


  15. topic {
    id
    name
    description
    isFollowed
    image
    }

    View Slide


  16. query {
    topic(id: 1) {
    id
    name
    description
    isFollowed
    image
    }
    }

    View Slide


  17. query {
    topic(id: 1) {
    id
    name
    description
    isFollowed
    image
    }
    }
    {
    "data": {
    "topic": {
    "id": 1,
    "name": "Developer Tools",
    "description": "Writing cod
    "isFollowed": true,
    "image": "assets.producthun
    }
    }
    }
    POST /graphql

    View Slide


  18. query {
    topic(id: 1) {
    id
    name
    description
    isFollowed
    image
    }
    }

    View Slide


  19. query {
    topic(id: 1) {
    id
    name
    description
    isFollowed
    image
    }
    }


    {topic.name}

    {topic.description}



    View Slide

  20. query {
    topic(id: 1) {
    id
    ...TopicItem
    }
    }
    fragment TopicItem on Topic {
    id
    name
    description
    ...TopicFollowButton
    ...TopicImage
    }
    fragment TopicFollowButton on Topic {
    id
    name
    isFollowed
    }
    fragment TopicImage on Topic {
    image
    }


    {topic.name}

    {topic.description}



    View Slide

  21. query {
    topic(id: 1) {
    id
    ...TopicItem
    }
    }
    fragment TopicItem on Topic {
    id
    name
    description
    ...TopicFollowButton
    ...TopicImage
    }
    fragment TopicFollowButton on Topic {
    id
    name
    isFollowed
    }
    fragment TopicImage on Topic {
    image
    }


    {topic.name}

    {topic.description}



    View Slide

  22. query {
    topic(id: 1) {
    id
    ...TopicItem
    }
    }
    fragment TopicItem on Topic {
    id
    name
    description
    ...TopicFollowButton
    ...TopicImage
    }
    fragment TopicFollowButton on Topic {
    id
    name
    isFollowed
    }
    fragment TopicImage on Topic {
    image
    }


    {topic.name}

    {topic.description}



    View Slide

  23. query {
    topic(id: 1) {
    id
    ...TopicItem
    }
    }
    fragment TopicItem on Topic {
    id
    name
    description
    ...TopicFollowButton
    ...TopicImage
    }
    fragment TopicFollowButton on Topic {
    id
    name
    isFollowed
    }
    fragment TopicImage on Topic {
    image
    }


    {topic.name}

    {topic.description}



    View Slide

  24. query {
    topic(id: 1) {
    id
    ...TopicItem
    }
    }
    fragment TopicItem on Topic {
    id
    name
    description
    ...TopicFollowButton
    ...TopicImage
    }
    fragment TopicFollowButton on Topic {
    id
    name
    isFollowed
    }
    fragment TopicImage on Topic {
    image
    }


    {topic.name}

    {topic.description}



    View Slide

  25. query {
    topic(id: 1) {
    id
    ...TopicItem
    }
    }
    fragment TopicItem on Topic {
    id
    name
    description
    ...TopicFollowButton
    ...TopicImage
    }
    fragment TopicFollowButton on Topic {
    id
    name
    isFollowed
    }
    fragment TopicImage on Topic {
    image
    }
    POST /graphql

    View Slide

  26. query {
    topic(id: 1) {
    id
    ...TopicItem
    }
    }
    fragment TopicItem on Topic {
    id
    name
    description
    ...TopicFollowButton
    ...TopicImage
    }
    fragment TopicFollowButton on Topic {
    id
    name
    isFollowed
    }
    fragment TopicImage on Topic {
    image
    }
    {
    "data": {
    "topic": {
    "id": 1,
    "name": "Developer Tools",
    "description": "Writing cod
    "isFollowed": true,
    "image": "assets.producthun
    }
    }
    }
    POST /graphql

    View Slide


  27. query {
    topic(id: 1) {
    id
    ...TopicItem

    }
    }

    View Slide

  28. View Slide


  29. query {
    allTopics {
    id
    ...TopicItem

    }
    }

    View Slide

  30. POST /graphql

    query {
    allTopics {
    id
    ...TopicItem

    }
    }

    View Slide

  31. {
    "data": {
    "allTopics": [{
    "id": 1,
    "name": "Developer Tools",
    "description": "Writing code is har
    "isFollowed": true,
    "image": "assets.producthunt.com/uu
    }, {

    "id": 2,
    "name": "Books",
    "description": "There’s just nothin
    "isFollowed": false,
    "image": "assets.producthunt.com/uu
    }, {

    "id": 3,
    "name": "iPhone",
    "description": "The beloved "phone"
    "isFollowed": true,
    "image": "assets.producthunt.com/uu
    }]
    }
    }
    POST /graphql

    query {
    allTopics {
    id
    ...TopicItem

    }
    }

    View Slide


  32. http://www.apollodata.com/


    View Slide

  33. /components/TopicFollowButton/Fragment.graphql
    /components/TopicFollowButton/index.js
    /components/TopicFollowButton/styles.css

    /components/TopicImage/Fragment.graphql
    /components/TopicImage/index.js
    /components/TopicImage/styles.css

    /components/TopicItem/Fragment.graphql
    /components/TopicItem/index.js
    /components/TopicItem/styles.css
    /pages/Topics/Query.graphql
    /pages/Topics/index.js

    /pages/Topics/styles.css

    View Slide

  34. /components/TopicFollowButton/Fragment.graphql
    /components/TopicFollowButton/index.js
    /components/TopicFollowButton/styles.css

    /components/TopicImage/Fragment.graphql
    /components/TopicImage/index.js
    /components/TopicImage/styles.css

    /components/TopicItem/Fragment.graphql
    /components/TopicItem/index.js
    /components/TopicItem/styles.css
    /pages/Topics/Query.graphql
    /pages/Topics/index.js

    /pages/Topics/styles.css

    View Slide

  35. /components/TopicFollowButton/Fragment.graphql
    /components/TopicFollowButton/index.js
    /components/TopicFollowButton/styles.css

    /components/TopicImage/Fragment.graphql
    /components/TopicImage/index.js
    /components/TopicImage/styles.css

    /components/TopicItem/Fragment.graphql
    /components/TopicItem/index.js
    /components/TopicItem/styles.css
    /pages/Topics/Query.graphql
    /pages/Topics/index.js

    /pages/Topics/styles.css

    View Slide

  36. fragment TopicImage on Topic {
    image
    }
    /components/TopicImage/Fragment.graphql

    View Slide

  37. fragment TopicFollowButton on Topic {
    id

    name

    isFollowed
    }
    /components/TopicFollowButton/Fragment.graphql

    View Slide

  38. #import "ph/components/TopicFollowButton/Fragment.graphql"
    #import "ph/components/TopicImage/Fragment.graphql"
    fragment TopicItem on Topic {
    id
    name

    slug
    description
    ...TopicFollowButton
    ...TopicImage
    }
    /components/TopicItem/Fragment.graphql

    View Slide

  39. /components/TopicFollowButton/Fragment.graphql
    /components/TopicFollowButton/index.js
    /components/TopicFollowButton/styles.css

    /components/TopicImage/Fragment.graphql
    /components/TopicImage/index.js
    /components/TopicImage/styles.css

    /components/TopicItem/Fragment.graphql
    /components/TopicItem/index.js
    /components/TopicItem/styles.css
    /pages/Topics/Query.graphql
    /pages/Topics/index.js

    /pages/Topics/styles.css

    View Slide

  40. /components/TopicFollowButton/Fragment.graphql
    /components/TopicFollowButton/index.js
    /components/TopicFollowButton/styles.css

    /components/TopicImage/Fragment.graphql
    /components/TopicImage/index.js
    /components/TopicImage/styles.css

    /components/TopicItem/Fragment.graphql
    /components/TopicItem/index.js
    /components/TopicItem/styles.css
    /pages/Topics/Query.graphql
    /pages/Topics/index.js

    /pages/Topics/styles.css

    View Slide

  41. #import "ph/components/TopicItem/Fragment.graphql"

    query TopicsPage {
    allTopics {
    id

    ...TopicItem

    }
    }
    /pages/Topics/Query.graphql

    View Slide

  42. TopicPage TopicItem
    TopicImage
    TopicFollowButton

    View Slide

  43. import TopicItem from 'components/TopicItem';

    import QUERY from './Query.graphql';
    import { graphql } from 'react-apollo';
    export default function Content(props) {
    /* ... */
    }
    export default graphql(QUERY)(Content);
    /pages/Topics/index.js

    View Slide

  44. import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { graphql } from 'react-apollo';
    export function Content({ data: { allTopics, loading } }) {
    if (loading) {
    return Loading...;
    }
    return (

    Topics

    {allTopics.map(topic => )}


    );
    }
    export default graphql(QUERY)(Content);
    /pages/Topics/index.js

    View Slide

  45. import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { graphql } from 'react-apollo';
    export function Content({ data: { allTopics, loading } }) {
    if (loading) {
    return Loading...;
    }
    return (

    Topics

    {allTopics.map(topic => )}


    );
    }
    export default graphql(QUERY)(Content);
    /pages/Topics/index.js

    View Slide

  46. /pages/Topics/index.js
    import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    export function Content({ data: { allTopics } }) {
    return (

    Topics

    {allTopics.map(topic => )}


    );
    }
    export default graphql(QUERY)(withLoading(Content));

    View Slide

  47. /pages/Topics/index.js
    import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    export function Content({ data: { allTopics } }) {
    return (

    Topics

    {allTopics.map(topic => )}


    );
    }
    export default compose(
    graphql(QUERY),
    withLoading
    )(Content);

    View Slide

  48. /pages/Topics/index.js
    import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    export function Content({ data: { allTopics } }) {
    return (

    Topics

    {allTopics.map(topic => )}


    );
    }
    export default compose(
    graphql(QUERY),
    withLoading
    )(Content);

    View Slide

  49. https://github.com/facebook/flow

    View Slide


  50. https://github.com/facebook/flow

    export type Topic = {
    id: number,
    image_uuid?: ?string,
    name: string,
    description?: string,
    followers_count: number,
    posts_count: number,
    slug: string,
    };

    View Slide

  51. function isFollowing(user: CurrentUser, topic: Topic): boolean {
    return user.followedTopicsIds.indexOf(topic.id) !== -1;
    }
    isFollowing(null, topic);
    isFollowing(user, null);
    isFollowing(user, somethingElse);
    const variable: number = isFollowing(user, topic);
    const variable: boolean = isFollowing(user, topic); ! "

    https://github.com/facebook/flow


    View Slide

  52. rake graphql:schema:json

    apollo-codegen generate "**/*.graphql" --schema graphql/schema.json --target flow --output graphql/schema.js

    View Slide

  53. /pages/Topics/index.js
    /* @flow */


    import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import type { TopicsPageQuery } from 'graphql/schema.js';


    type Props: {

    data: TopicsPageQuery,

    };
    export function Content({ data: { allTopics } }: Props) {
    return (

    Topics

    {allTopics.map(topic => )}


    );
    }
    export default compose(
    graphql(QUERY),
    withLoading
    )(Content);

    View Slide

  54. /pages/Topics/index.js
    /* @flow */


    import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import type { TopicsPageQuery } from 'graphql/schema.js';


    type Props: {

    data: TopicsPageQuery,

    };
    export function Content({ data: { allTopics } }: Props) {
    return (

    Topics

    {allTopics.map(topic => )}


    );
    }
    export default compose(
    graphql(QUERY),
    withLoading
    )(Content);

    View Slide

  55. /pages/Topics/index.js
    /* @flow */
    import type { TopicItemFragment } from 'decorators/withLoading';


    type Props: {

    topic: { ...TopicItemFragment },

    };
    export function TopicItem({ topic }: Props) {
    /* ... */
    }

    View Slide

  56. View Slide

  57. https://facebook.github.io/relay/

    View Slide

  58. # Node interface
    $ Connections
    % Mutations
    GraphQL Relay Specification
    https://facebook.github.io/relay/docs/graphql-relay-specification.html

    View Slide


  59. query {
    allTopics {
    id
    }
    }
    {
    "data": {
    "allTopics": [{
    "id": 1
    }]
    }
    }
    https://facebook.github.io/relay/docs/graphql-object-identification.html
    Node Interface

    View Slide


  60. query {
    allTopics {
    id
    }
    }
    {
    "data": {
    "allTopics": [{
    "id": base64("Topic:1")
    }]
    }
    }
    https://facebook.github.io/relay/docs/graphql-object-identification.html
    Node Interface

    View Slide


  61. query {
    allTopics {
    id
    }
    }
    {
    "data": {
    "allTopics": [{
    "id": "VG9waWM6MQ=="
    }]
    }
    }
    https://facebook.github.io/relay/docs/graphql-object-identification.html
    Node Interface

    View Slide


  62. query {
    node(id: "VG9waWM6MQ==") {
    id
    }
    }
    {
    "data": {
    "node": {
    "id": "VG9waWM6MQ=="
    }
    }
    }
    https://facebook.github.io/relay/docs/graphql-object-identification.html
    Node Interface

    View Slide


  63. query {
    node(id: "VG9waWM6MQ==") {
    id

    ... on Topic {

    name

    }
    }
    }
    {
    "data": {
    "node": {
    "id": "VG9waWM6MQ==",

    "name": "Games"
    }
    }
    }
    https://facebook.github.io/relay/docs/graphql-object-identification.html
    Node Interface

    View Slide


  64. query {
    node(id: "VG9waWM6MQ==") {
    id

    ... on Topic {

    name

    }

    ... on User {

    fullName

    }
    }
    }
    {
    "data": {
    "node": {
    "id": "VG9waWM6MQ==",

    "name": "Games"
    }
    }
    }
    https://facebook.github.io/relay/docs/graphql-object-identification.html
    Node Interface

    View Slide

  65. query TopicsPage {
    allTopics {

    id

    ...TopicItem

    }
    }
    https://facebook.github.io/relay/docs/graphql-connections.html
    Connections

    View Slide

  66. query TopicsPage($cursor: String) {
    allTopics(first: 10, after: $cursor) {

    edges {

    node {

    id

    ...TopicItem

    }

    }

    pageInfo {

    hasNextPage

    }
    }
    }
    https://facebook.github.io/relay/docs/graphql-connections.html
    Connections

    View Slide

  67. query TopicsPage($cursor: String) {
    allTopics(last: 10, before: $cursor) {

    edges {

    node {

    id

    ...TopicItem

    }

    }

    pageInfo {

    hasPreviousPage

    }
    }
    }
    https://facebook.github.io/relay/docs/graphql-connections.html
    Connections

    View Slide

  68. [connectionName](first:, after:) {

    edges {

    cursor

    node {

    [item]

    }

    }

    pageInfo {

    endCursor
    startCursor

    hasPreviousPage

    hasNextPage
    }
    }
    https://facebook.github.io/relay/docs/graphql-connections.html
    Connections

    View Slide

  69. View Slide

  70. View Slide

  71. #import "ph/components/TopicItem/Fragment.graphql"

    query TopicsPage {
    allTopics {
    id

    ...TopicItem

    }
    }
    /pages/Topics/Query.graphql

    View Slide

  72. #import "ph/components/TopicItem/Fragment.graphql"

    query TopicsPage {
    allTopics(first: 10) {

    edges {

    node {

    id

    ...TopicItem

    }

    }

    pageInfo {

    hasNextPage

    }
    }
    }
    /pages/Topics/Query.graphql

    View Slide

  73. /pages/Topics/index.js
    import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    export function Content({ data: { allTopics } }) {
    return (

    Topics

    {allTopics.map(topic => )}


    );
    }
    export default compose(
    graphql(QUERY),
    withLoading
    )(Content);

    View Slide

  74. import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    export function Content({ data }) {
    const allTopics = data.allTopics.edges.map(({ node }) => node);
    return (

    Topics

    {allTopics.map(topic => )}


    );
    }
    export default compose(
    graphql(QUERY),
    withLoading
    )(Content);
    /pages/Topics/index.js

    View Slide

  75. export function extractEdgeNodes(data) {
    return data.edges.map(({ node }) => node);
    }
    /utils/graphql.js

    View Slide

  76. export function extractEdgeNodes(data) {
    if (!data || !data.edges) {
    return [];
    }
    return data.edges.map(({ node }) => node);
    }
    /utils/graphql.js

    View Slide

  77. import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import { extractEdgeNodes } from 'utils/graphql';
    export function Content({ data }) {
    const allTopics = extractEdgeNodes(data.allTopics);
    return (

    Topics

    {allTopics.map(topic => )}


    );
    }
    export default compose(
    graphql(QUERY),
    withLoading
    )(Content);
    /pages/Topics/index.js

    View Slide

  78. import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import { extractEdgeNodes } from 'utils/graphql';
    export function Content({ data }) {
    return (

    Topics

    {extractEdgeNodes(data.allTopics).map(topic => (


    ))}


    );
    }
    export default compose(
    graphql(QUERY),
    withLoading
    )(Content);
    /pages/Topics/index.js

    View Slide

  79. #import "ph/components/TopicItem/Fragment.graphql"

    query TopicsPage {
    allTopics(first: 10) {

    edges {

    node {

    id

    ...TopicItem

    }

    }

    pageInfo {

    hasNextPage

    }
    }
    }
    /pages/Topics/Query.graphql

    View Slide

  80. #import "ph/components/TopicItem/Fragment.graphql"

    query TopicsPage($cursor: String) {
    allTopics(first: 10, after: $cursor) {

    edges {

    node {

    id

    ...TopicItem

    }

    }

    pageInfo {

    endCursor

    hasNextPage

    }
    }
    }
    /pages/Topics/index.js

    View Slide

  81. import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import { extractEdgeNodes } from 'utils/graphql';
    export function Content({ data }) {
    return (

    Topics

    {extractEdgeNodes(data.allTopics).map(topic => (


    ))}


    );
    }
    export default compose(
    graphql(QUERY),
    withLoading
    )(Content);
    /pages/Topics/index.js

    View Slide

  82. import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import { extractEdgeNodes } from 'utils/graphql';
    export function Content({ data, loadMore }) {
    const hasNextPage = data.allTopics.pageInfo.hasNextPage;
    return (

    Topics

    {extractEdgeNodes(data.allTopics).map(topic => (
    ,
    ))}


    );
    }
    export default compose(
    graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => {
    return data.fetchMore({
    variables: {
    /pages/Topics/index.js

    View Slide

  83. import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import { extractEdgeNodes } from 'utils/graphql';
    export function Content({ data, loadMore }) {
    const hasNextPage = data.allTopics.pageInfo.hasNextPage;
    return (

    Topics

    {extractEdgeNodes(data.allTopics).map(topic => (
    ,
    ))}


    );
    }
    export default compose(
    graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => {
    return data.fetchMore({
    variables: {
    /pages/Topics/index.js

    View Slide

  84. import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import { extractEdgeNodes } from 'utils/graphql';
    export function Content({ data, loadMore }) {
    const hasNextPage = data.allTopics.pageInfo.hasNextPage;
    return (

    Topics

    {extractEdgeNodes(data.allTopics).map(topic => (
    ,
    ))}


    );
    }
    export default compose(
    graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => {
    return data.fetchMore({
    variables: {
    /pages/Topics/index.js

    View Slide

  85. ))}


    );
    }
    export default compose(
    graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => {
    return data.fetchMore({
    variables: {
    cursor: data.allTopics.pageInfo.endCursor,
    },
    updateQuery(previousResult, { fetchMoreResult }) {
    const connection = fetchMoreResult.allTopics;
    return {
    allTopics: {
    edges: [...previousResult.allTopics.edges, ...connection.edges]
    pageInfo: connection.pageInfo,
    },
    };
    },
    });
    },
    }),
    }),
    withLoading,
    )(Content);
    /pages/Topics/index.js

    View Slide

  86. ))}


    );
    }
    export default compose(
    graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => {
    return data.fetchMore({
    variables: {
    cursor: data.allTopics.pageInfo.endCursor,
    },
    updateQuery(previousResult, { fetchMoreResult }) {
    const connection = fetchMoreResult.allTopics;
    return {
    allTopics: {
    edges: [...previousResult.allTopics.edges, ...connection.edges]
    pageInfo: connection.pageInfo,
    },
    };
    },
    });
    },
    }),
    }),
    withLoading,
    )(Content);
    /pages/Topics/index.js
    &

    View Slide

  87. View Slide

  88. View Slide

  89. graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => {
    return data.fetchMore({
    variables: {
    cursor: data.allTopics.pageInfo.endCursor,
    },
    updateQuery(previousResult, { fetchMoreResult }) {
    const connection = fetchMoreResult.allTopics;
    return {
    allTopics: {
    edges: [...previousResult.allTopics.edges, ...connection.edges],
    pageInfo: connection.pageInfo,
    },
    };
    },
    });
    },
    }),
    });

    View Slide

  90. graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => {
    return data.fetchMore({
    variables: {
    cursor: data.allTopics.pageInfo.endCursor,
    },
    updateQuery(previousResult, { fetchMoreResult }) {
    const connection = fetchMoreResult.allTopics;
    return {
    allTopics: {
    edges: [...previousResult.allTopics.edges, ...connection.edges],
    pageInfo: connection.pageInfo,
    },
    };
    },
    });
    },
    }),
    });

    View Slide

  91. graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => {
    return data.fetchMore({
    variables: {
    cursor: data[path].pageInfo.endCursor,
    },
    updateQuery(previousResult, { fetchMoreResult }) {
    const connection = fetchMoreResult[path];
    return {
    [path]: {
    edges: [...previousResult[path].edges, ...connection.edges],
    pageInfo: connection.pageInfo,
    },
    };
    },
    });
    },
    }),
    });

    View Slide

  92. graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => {
    const path = 'allTopics';
    return data.fetchMore({
    variables: {
    cursor: data[path].pageInfo.endCursor,
    },
    updateQuery(previousResult, { fetchMoreResult }) {
    const connection = fetchMoreResult[path];
    return {
    [path]: {
    edges: [...previousResult[path].edges, ...connection.edges],
    pageInfo: connection.pageInfo,
    },
    };
    },
    });
    },
    }),
    });

    View Slide

  93. function loadMore(data, path) {
    return data.fetchMore({
    variables: {
    cursor: data[path].pageInfo.endCursor,
    },
    updateQuery(previousResult, { fetchMoreResult }) {
    const connection = fetchMoreResult[path];
    return {
    [path]: {
    edges: [...previousResult[path].edges, ...connection.edges],
    pageInfo: connection.pageInfo,
    },
    };
    },
    });
    }
    graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => loadMore(data, 'allTopics'),
    }),
    });

    View Slide

  94. /pages/Topics/index.js
    import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import { extractEdgeNodes, loadMore } from 'utils/graphql';
    export function Content({ data, loadMore }) {
    return (

    Topics
    {extractEdgeNodes(data.allTopics).map(topic =>
    ,
    )}


    );
    }
    export default compose(
    graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => loadMore(data, 'allTopics'),
    }),
    }),
    withLoading,
    )(Content);

    View Slide

  95. /pages/Topics/index.js
    import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import { extractEdgeNodes, loadMore } from 'utils/graphql';
    export function Content({ data, loadMore }) {
    return (

    Topics
    {extractEdgeNodes(data.allTopics).map(topic =>
    ,
    )}


    );
    }
    export default compose(
    graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => loadMore(data, 'allTopics'),
    }),
    }),
    withLoading,
    )(Content);
    '

    View Slide

  96. /pages/Topics/index.js
    import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import { extractEdgeNodes, loadMore } from 'utils/graphql';
    export function Content({ data, loadMore }) {
    return (

    Topics
    {extractEdgeNodes(data.allTopics).map(topic =>
    ,
    )}


    );
    }
    export default compose(
    graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => loadMore(data, 'allTopics'),
    }),
    }),
    withLoading,
    )(Content);

    View Slide

  97. /pages/Topics/index.js
    import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import { extractEdgeNodes, loadMore } from 'utils/graphql';
    export function Content({ data, loadMore }) {
    return (

    Topics
    {extractEdgeNodes(data.allTopics).map(topic =>
    ,
    )}


    );
    }
    export default compose(
    graphql(QUERY, {
    props: ({ data }) => ({
    loadMore: () => loadMore(data, 'allTopics'),
    }),
    }),
    withLoading,
    )(Content);
    (

    View Slide

  98. /pages/Topics/index.js
    import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import { extractEdgeNodes } from 'utils/graphql';
    export function Content({ data }) {
    return (

    Topics

    {extractEdgeNodes(data.allTopics).map(topic =>
    ,
    )}


    );
    }
    export default compose(
    graphql(QUERY),
    withLoading,
    )(Content);

    View Slide

  99. /pages/Topics/index.js
    import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import { extractEdgeNodes } from 'utils/graphql';
    export function Content({ data }) {
    return (

    Topics

    {extractEdgeNodes(data.allTopics).map(topic =>
    ,
    )}


    );
    }
    export default compose(
    graphql(QUERY),
    withLoading,
    )(Content);

    View Slide

  100. /pages/Topics/index.js
    import TopicItem from 'components/TopicItem';
    import QUERY from './Query.graphql';
    import { compose, graphql } from 'react-apollo';
    import withLoading from 'decorators/withLoading';
    import { extractEdgeNodes } from 'utils/graphql';
    export function Content({ data }) {
    return (

    Topics

    {extractEdgeNodes(data.allTopics).map(topic =>
    ,
    )}


    );
    }
    export default compose(
    graphql(QUERY),
    withLoading,
    )(Content);
    )

    View Slide

  101. View Slide

  102. Mutations

    View Slide

  103. # Node interface
    $ Connections
    % Mutations
    GraphQL Relay Specification
    https://facebook.github.io/relay/docs/graphql-relay-specification.html

    View Slide

  104. View Slide

  105. View Slide


  106. mutation FollowTopic($id: ID!) {
    followTopic(id: $id) {

    id
    isFollowed
    }
    }
    {
    "data": {

    "followTopic": {
    "id": "VG9waWM6MQ==",
    "isFollowed": true
    }
    }
    }
    POST /graphql

    View Slide


  107. mutation UnfollowTopic($id: ID!) {
    unfollowTopic(id: $id) {

    id
    isFollowed
    }
    }
    {
    "data": {

    "unfollowTopic": {
    "id": "VG9waWM6MQ==",
    "isFollowed": false
    }
    }
    }
    POST /graphql

    View Slide


  108. mutation FollowTopic($id: ID!) {
    followTopic(id: $id) {

    id
    isFollowed
    }
    }
    {
    "data": {

    "followTopic": {
    "id": "VG9waWM6MQ==",
    "isFollowed": true
    }
    }
    }
    POST /graphql

    View Slide


  109. mutation FollowTopic($id: ID!, $a: Int, $b: Int, $c: Int, $d: Int) {
    followTopic(id: $id, a: $a, b: $b, c: $c, d: $d) {

    id
    isFollowed
    }
    }
    POST /graphql

    View Slide


  110. mutation FollowTopic($input: FollowInput!) {
    followTopic(input: $input) {

    clientMutationId
    topic {

    id
    isFollowed

    }
    }
    }
    https://facebook.github.io/relay/docs/graphql-mutations.html
    POST /graphql

    View Slide


  111. mutation FollowTopic($input: FollowInput!) {
    followTopic(input: $input) {

    clientMutationId
    topic {

    id
    isFollowed

    }
    }
    }
    {
    "data": {

    "followTopic": {
    "clientMutationId": "0",
    "topic": {

    "id": "VG9waWM6MQ==",
    "isFollowed": true
    }
    }
    }
    }
    https://facebook.github.io/relay/docs/graphql-mutations.html
    POST /graphql

    View Slide


  112. mutation FollowTopic($input: FollowInput!) {
    followTopic(input: $input) {

    clientMutationId
    topic {

    id
    isFollowed

    }
    }
    }
    {
    "data": {

    "followTopic": {
    "clientMutationId": "0",
    "topic": {

    "id": "VG9waWM6MQ==",
    "isFollowed": true
    }
    }
    }
    }
    https://facebook.github.io/relay/docs/graphql-mutations.html
    POST /graphql

    View Slide

  113. View Slide



  114. {topic.name}

    {topic.description}



    View Slide

  115. mutation FollowTopic($input: FollowTopicInput!) {
    followTopic(input: $input) {
    topic {
    id
    isFollowed
    }
    }
    }
    /components/FollowButton/FollowTopic.graphql

    View Slide

  116. mutation UnfollowTopic($input: UnfollowTopicInput!) {
    unfollowTopic(input: $input) {
    topic {
    id
    isFollowed
    }
    }
    }
    /components/FollowButton/UnfollowTopic.graphql

    View Slide

  117. import CREATE_MUTATION from './FollowTopic.graphql';

    import REMOVE_MUTATION from './UnfollowTopic.graphql';


    export function FollowButton({ /* ... */ }) { /* ... */ }

    export default compose(
    graphql(CREATE_MUTATION, {
    props: ({ ownProps: { topic: { id } }, mutate }) => ({
    followTopic() {
    return mutate({
    variables: {
    input: { id },
    },
    });
    },
    }),
    }),
    graphql(REMOVE_MUTATION, { /* ... */ }),
    )(FollowButton)
    /components/FollowButton/index.js

    View Slide

  118. import CREATE_MUTATION from './FollowTopic.graphql';

    import REMOVE_MUTATION from './UnfollowTopic.graphql';


    export function FollowButton({ /* ... */ }) { /* ... */ }

    export default compose(
    graphql(CREATE_MUTATION, {
    props: ({ ownProps: { topic: { id } }, mutate }) => ({
    followTopic() {
    return mutate({
    variables: {
    input: { id },
    },
    optimisticResponse: {
    response: {
    node: {
    __typename: 'Topic',
    id,
    isFollowed: true
    },
    },
    }
    });
    },
    }),
    }),
    graphql(REMOVE_MUTATION, { /* ... */ }),
    )(FollowButton)
    /components/FollowButton/index.js

    View Slide

  119. import CREATE_MUTATION from './FollowTopic.graphql';

    import REMOVE_MUTATION from './UnfollowTopic.graphql';


    export function FollowButton({ topic, followTopic, unfollowTopic }) {
    if (topic.isFollowed) {
    return (

    Following

    );
    }
    return (

    Follow

    );
    }

    export default compose(
    graphql(CREATE_MUTATION, { /* ... */ }),
    graphql(REMOVE_MUTATION, { /* ... */ }),
    )(FollowButton)
    /components/FollowButton/index.js

    View Slide


  120. mutation FollowTopic($input: FollowInput!) {
    followTopic(input: $input) {

    clientMutationId
    topic {

    id
    isFollowed

    }
    }
    }
    {
    "data": {

    "followTopic": {
    "clientMutationId": "0",
    "topic": {

    "id": "VG9waWM6MQ==",
    "isFollowed": true
    }
    }
    }
    }
    https://facebook.github.io/relay/docs/graphql-mutations.html
    POST /graphql

    View Slide


  121. mutation FollowTopic($input: FollowInput!) {
    followTopic(input: $input) {

    clientMutationId
    node {

    id
    isFollowed

    }
    }
    }
    {
    "data": {

    "followTopic": {

    "clientMutationId": "0",
    "node": {
    "id": "VG9waWM6MQ==",
    "isFollowed": true
    }
    }
    }
    }
    POST /graphql

    View Slide


  122. mutation FollowTopic($input: FollowInput!) {
    followTopic(input: $input) {

    clientMutationId
    node {

    id
    isFollowed

    }
    }
    }
    {
    "data": {

    "followTopic": {

    "clientMutationId": "0",
    "node": {
    "id": "VG9waWM6MQ==",
    "isFollowed": true
    }
    }
    }
    }
    POST /graphql

    View Slide

  123. Forms

    View Slide

  124. View Slide

  125. #import "./Fragment.graphql"


    mutation UpdateSetting($input: SettingInput!) {
    updateSettings(input: $input) {
    node {

    ...SettingsForm

    }
    }
    }
    /pages/Settings/Mutation.graphql

    View Slide

  126. function SettingsPage({ data: { settings }, submit }) {
    return (










    );
    }
    export default compose(
    graphql(QUERY)
    graphql(MUTATION, {
    props: ({ ownProps, mutate }) => ({
    submit(input) {
    return mutate({
    variables: { input },
    });
    },
    }),
    }),
    );
    /pages/Settings/index.js

    View Slide

  127. function SettingsPage({ data: { settings }, submit, onSubmit }) {
    return (










    );
    }
    export default compose(
    graphql(QUERY)
    graphql(MUTATION, {
    props: ({ ownProps, mutate }) => ({
    submit(input) {
    return mutate({
    variables: { input },
    });
    },
    onSubmit(node) {
    // handle successful update
    },
    }),
    /pages/Settings/index.js

    View Slide

  128. View Slide


  129. mutation UpdateSetting($input: SettingInput!) {
    updateSettings(input: $input) {
    node {

    ...SettingsForm

    }
    }
    }
    {
    "data": {

    "updateSettings": {
    "node": {

    "...": "...",
    }
    }
    }
    }
    POST /graphql

    View Slide


  130. mutation FollowTopic($input: FollowTopicInput!) {
    updateSettings(input: $input) {
    node {

    id
    isFollowed

    }

    errors {
    field
    messages
    }
    }
    }
    POST /graphql
    {
    "data": {

    "updateSettings": {
    "node": {

    "...": "...",
    }

    "errors": [{

    "name": ["missing"],
    "email": ["invalid"],
    }]
    }
    }
    }

    View Slide


  131. mutation FollowTopic($input: FollowTopicInput!) {
    updateSettings(input: $input) {
    node {

    id
    isFollowed

    }

    errors {
    field
    messages
    }
    }
    }
    POST /graphql
    {
    "data": {

    "updateSettings": {
    "node": {

    "...": "...",
    }

    "errors": [{

    "name": ["missing"],
    "email": ["invalid"],
    }]
    }
    }
    }

    View Slide

  132. GraphQL

    Apollo

    Relay Specification

    * Node

    + Connections

    , Mutations

    - Data Fetching

    . Pagination

    / Mutations

    0 Forms
    Recap

    View Slide

  133. Thanks !

    View Slide