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

GraphQL with Apollo

GraphQL with Apollo

Radoslav Stankov

September 29, 2017
Tweet

More Decks by Radoslav Stankov

Other Decks in Technology

Transcript

  1. GraphQL with Apollo
    Radoslav Stankov 30/09/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. View Slide


  16. topic {
    id
    name
    description
    isFollowed
    image
    }

    View Slide


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

    View Slide


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

    View Slide


  19. 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


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

    View Slide


  21. query {
    topic(id: 1) {
    id
    name
    description
    isFollowed
    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
    }


    {topic.name}

    {topic.description}



    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
    }


    {topic.name}

    {topic.description}



    View Slide

  27. 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

  28. 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


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

    }
    }

    View Slide

  30. View Slide


  31. query {
    allTopics {
    id
    ...TopicItem

    }
    }

    View Slide

  32. POST /graphql

    query {
    allTopics {
    id
    ...TopicItem

    }
    }

    View Slide

  33. {
    "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


  34. http://www.apollodata.com/


    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. /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

  37. /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

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

    View Slide

  39. fragment TopicFollowButton on Topic {
    id

    name

    isFollowed
    }
    /components/TopicFollowButton/Fragment.graphql

    View Slide

  40. #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

  41. /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

  42. /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

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

    query TopicsPage {
    allTopics {
    id

    ...TopicItem

    }
    }
    /pages/Topics/Query.graphql

    View Slide

  44. TopicPage TopicItem
    TopicImage
    TopicFollowButton

    View Slide

  45. 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

  46. 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

  47. 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

  48. /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

  49. /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

  50. /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

  51. View Slide

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

    View Slide

  53. ! Node interface
    " Connections
    # Mutations
    GraphQL Relay Specification
    https://facebook.github.io/relay/docs/graphql-relay-specification.html

    View Slide


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

    View Slide


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

    View Slide


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

    View Slide


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

    View Slide


  58. 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


  59. 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

  60. query TopicsPage {
    allTopics {

    id

    ...TopicItem

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

    View Slide

  61. 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

  62. 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

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

    edges {

    cursor

    node {

    [item]

    }

    }

    pageInfo {

    endCursor
    startCursor

    hasPreviousPage

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

    View Slide

  64. View Slide

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

    query TopicsPage {
    allTopics {
    id

    ...TopicItem

    }
    }
    /pages/Topics/Query.graphql

    View Slide

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

    query TopicsPage {
    allTopics(first: 10) {

    edges {

    node {

    id

    ...TopicItem

    }

    }

    pageInfo {

    hasNextPage

    }
    }
    }
    /pages/Topics/Query.graphql

    View Slide

  67. /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

  68. 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

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

    View Slide

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

    View Slide

  71. 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

  72. 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

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

    query TopicsPage {
    allTopics(first: 10) {

    edges {

    node {

    id

    ...TopicItem

    }

    }

    pageInfo {

    hasNextPage

    }
    }
    }
    /pages/Topics/Query.graphql

    View Slide

  74. #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

  75. 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

  76. 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

  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, 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

  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, 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

  79. ))}


    );
    }
    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

  80. ))}


    );
    }
    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

  81. View Slide

  82. View Slide

  83. 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

  84. 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

  85. 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

  86. 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

  87. 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

  88. /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

  89. /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

  90. /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

  91. /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

  92. /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

  93. /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

  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 } from 'utils/graphql';
    export function Content({ data }) {
    return (

    Topics

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


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

    View Slide

  95. View Slide

  96. Mutations

    View Slide

  97. ! Node interface
    " Connections
    # Mutations
    GraphQL Relay Specification
    https://facebook.github.io/relay/docs/graphql-relay-specification.html

    View Slide

  98. View Slide

  99. View Slide


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

    id
    isFollowed
    }
    }
    {
    "data": {

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

    View Slide


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

    id
    isFollowed
    }
    }
    {
    "data": {

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

    View Slide


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

    id
    isFollowed
    }
    }
    {
    "data": {

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

    View Slide


  103. 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


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

    clientMutationId
    topic {

    id
    isFollowed

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

    View Slide


  105. 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


  106. 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

  107. View Slide



  108. {topic.name}

    {topic.description}



    View Slide

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

    View Slide

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

    View Slide

  111. 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

  112. 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

  113. 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

  114. import { Component } from 'ph/components/TopicFollowButton';
    describe(Component.name, () => {
    const followed = buildTopic({ isFollowed: true });
    const unfollowed = buildTopic({ isFollowed: false });
    it('renders "Follow" text when is not following', () => {
    expect(shallow()).to.contain('Follow');
    });
    it('renders "Following" text when is following', () => {
    expect(shallow()).to.contain('Following');
    });
    it('can follow a topic', () => {
    const spy = sinon.spy();
    const wrapper = shallow();
    wrapper.simulate('click', { preventDefault() {} });
    expect(spy).to.have.been.calledOnce;
    });
    it('can unfollow a topic', () => {
    const spy = sinon.spy();
    const wrapper = shallow();
    wrapper.simulate('click', { preventDefault() {} });
    expect(spy).to.have.been.calledOnce;
    });
    });
    /components/FollowButton/index.test.js

    View Slide


  115. 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


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

    clientMutationId
    node {

    id
    isFollowed

    }
    }
    }
    {
    "data": {

    "followTopic": {

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

    View Slide


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

    clientMutationId
    node {

    id
    isFollowed

    }
    }
    }
    {
    "data": {

    "followTopic": {

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

    View Slide

  118. Forms

    View Slide

  119. View Slide

  120. #import "./Fragment.graphql"


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

    ...SettingsForm

    }
    }
    }
    /pages/Settings/Mutation.graphql

    View Slide

  121. 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

  122. 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

  123. View Slide


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

    ...SettingsForm

    }
    }
    }
    {
    "data": {

    "updateSettings": {
    "node": {

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

    View Slide


  125. 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


  126. 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

  127. GraphQL

    Apollo

    Relay Specification

    ( Node

    ) Connections

    * Mutations

    + Data Fetching

    , Pagination

    - Mutations

    . Forms
    Recap

    View Slide

  128. View Slide

  129. Thanks /

    View Slide