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