GraphQL

&
 Apollo Client

&

React

&
React Native

Fetch and show stuff

Staré workflow

Napsat reducer

Napsat akce

Napsat Thunky / Sagy

Definovat state

Napsat selectory

Napojit komponentu na state

✨Nové workflow

Napsat queries, mutace, fragmenty

Vygenerovat TS typy

Volat graphql z componenty

VS Code integrace

 Apollo GraphQL Extension

Co je to sakra Fragment

import gql from 'graphql-tag'

const CompleteSong = gql`
  fragment CompleteSong on Song {
    id
    name
    artist
    audio
    comments {
      text
      user {
        avatar
        isArtist
        name
      }
    }
    cover
    description
    isLiked
    listens
    tags {
      isImportant
      value
    }
  }
`
export default CompleteSong

CompleteSong

import gql from 'graphql-tag';

const PreviewSong = gql`
  fragment PreviewSong on Song {
    id
    name
    artist
    cover
    isLiked
  }
`
export default PreviewSong

PreviewSong

Query

import CompleteSong from 'gql/fragments/completeSong'
import gql from 'graphql-tag'

const All_SONGS = gql`
  query AllSongs {
    songs {
      ...CompleteSong
    }
  }
  ${CompleteSong}
`
export default All_SONGS
const SEARCH_SONG = gql`
  query Search($name: String!) {
    search(name: $name) {
      ...CompleteSong
    }
  }
  ${CompleteSong}
`
export default SEARCH_SONG

React & Query

const Playlists = ({ navigation: { navigate } }: NavigationInjectedProps) => {
  const { data, loading, error } = useQuery<AllSongsQuery>(All_SONGS)
  const handleSongClick = (song: Song) => navigate({ routeName: 'Song', params: song })

  if (loading) return <ActivityIndicator />
  if (error) return <Headline>{error.message}</Headline>

  const likedSongs = data.songs.filter(song => song.isLiked)
  const discover = data.songs.filter(song => !song.isLiked)

  return (
    <StyledSafeView>
      <Container>
        <PlaylistHeader>Songs you love</PlaylistHeader>
        <SongsList songs={likedSongs} onSongClick={handleSongClick} />
        <PlaylistHeader>Discover</PlaylistHeader>
        <SongsList songs={discover} onSongClick={handleSongClick} />
      </Container>
    </StyledSafeView>
  )
}

Mutace

import CompleteSong from 'gql/fragments/completeSong'
import gql from 'graphql-tag'

const SET_LIKE = gql`
  mutation SetLike($songId: ID!, $like: Toggle!) {
    setLike(songId: $songId, like: $like) {
      ...CompleteSong
    }
  }
  ${CompleteSong}
`
export default SET_LIKE
import CompleteSong from 'gql/fragments/completeSong'
import gql from 'graphql-tag'

const ADD_COMMENT = gql`
  mutation AddComment($songId: ID!, $comment: CommentInput!) {
    addComment(songId: $songId, comment: $comment) {
      ...CompleteSong
    }
  }
  ${CompleteSong}
`
export default ADD_COMMENT

React & Mutace

  const [setLike, { data, loading, error }] = useMutation<SetLikeMutation, MutationSetLikeArgs>(
    SET_LIKE,
  )
          <IconButton
            onPress={() => {
              const like = isSongLiked ? Toggle.Remove : Toggle.Add
              setLike({ variables: { songId: id, like } })
            }}
            icon={() => (
              <Ionicons
                name="md-heart"
                size={42}
                color={isSongLiked ? Colors.red400 : Colors.grey400}
              />
            )}

Subscription

import gql from 'graphql-tag'

const ON_COMMENT_ADDED = gql`
  subscription OnCommentAdded($songId: ID!) {
    commentAdded(songId: $songId) {
      text
      user {
        avatar
        name
        isArtist
      }
    }
  }
`
export default ON_COMMENT_ADDED

React & Subscription

  useSubscription<OnCommentAddedSubscription, SubscriptionCommentAddedArgs>(ON_COMMENT_ADDED, {
    variables: {
      songId,
    },
    onSubscriptionData: ({ subscriptionData: { data }, client }) => {
      const { song } = client.readQuery<GetSongQuery, GetSongQueryVariables>({
        query: SONG,
        variables: { songId },
      })

      client.writeQuery<GetSongQuery, GetSongQueryVariables>({
        query: SONG,
        data: {
          song: {
            ...song,
            comments: [...(song.comments || []), data.commentAdded],
          },
        },
      })
    },
  })

Lokální stav

@client

  extend type Song {
    previewUri: String
  }
  fragment CompleteSong on Song {
    previewUri @client
    id
    name
    artist 
   .
   .
   .
  }
export const client = new ApolloClient({
  link,
  cache,
  resolvers: {
    Song: {
      previewUri: async ({ id }: Song) => {
        try {
          const { preview_url } = await fetch(`https://api.spotify.com/v1/tracks/${id}`, {
            headers: { Authorization: token },
          }).then(res => res.json())
          return preview_url
        } catch {
          return null
        }
      },
    },
  },
  typeDefs,
})

Mutace lokálního stavu

  extend type Mutation {
    setSongSeen(songId: ID!): Song
  }
import CompleteSong from 'gql/fragments/completeSong'
import gql from 'graphql-tag'

const SET_SONG_SEEN = gql`
  mutation SetSongSeen($songId: ID!) {
    setSongSeen(songId: $songId) @client {
      ...CompleteSong
    }
  }
  ${CompleteSong}
`
export default SET_SONG_SEEN
    Mutation: {
      setSongSeen: (_, variables, { cache }): Song => {
        const { songs } = cache.readQuery({ query: ALL_SONGS })
        const song = songs.find(({ id }) => id === variables.songId)
        return { ...song, isSeen: true }
      },
    },
  const [setSongSeen] = useMutation<SetSongSeenMutation, MutationSetSongSeenArgs>(ADD_SEEN_SONG, {
    variables: { songId: id },
  })

  useEffect(() => {
    if (!isSeen) setSongSeen()
  }, [isSeen])

Testing


  it('renders settings menu', () => {
    const { getByTestId } = render(
    	<ApolloProvider client={client}>
      		<Song id="test" />
    	</ApolloProvider>)

    const name = getByTestId('song-name')
    expect(name).toHaveTextContent('test song')
  })

Mocked provider

const mocks = [
  {
    request: {
      query: GET_SONG,
      variables: {
        songId: 'test',
      },
    },
    result: {
      data: {
        song: { id: 'test', name: 'test song', artist: 'Vlasta Plamínek' },
      },
    },
  },
];

  it('renders settings menu', async () => {
    const { getByTestId } = render(
    	<MockedProvider mocks={mocks} addTypename={false}>
      		<Song id="test" />
    	</MockedProvider>)
    
    await wait()

    const name = getByTestId('song-name')
    expect(name).toHaveTextContent('test song')
  })

Díky!

sli.do - #QEETUP8

Apollo + React

By Pepa Vidlák

Apollo + React

  • 29
Loading comments...

More from Pepa Vidlák