<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Harrison Henri dos Santos Nascimento</title>
    <description>The latest articles on Forem by Harrison Henri dos Santos Nascimento (@harrisonhenri).</description>
    <link>https://forem.com/harrisonhenri</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F652130%2F4ad7773e-d861-4bf8-9b38-5f8ecc74d4c4.jpeg</url>
      <title>Forem: Harrison Henri dos Santos Nascimento</title>
      <link>https://forem.com/harrisonhenri</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/harrisonhenri"/>
    <language>en</language>
    <item>
      <title>Unit tests with react-native testing library and Apollo Graphql</title>
      <dc:creator>Harrison Henri dos Santos Nascimento</dc:creator>
      <pubDate>Sun, 27 Jun 2021 23:51:24 +0000</pubDate>
      <link>https://forem.com/harrisonhenri/unit-tests-with-react-native-testing-library-and-apollo-graphql-1mbo</link>
      <guid>https://forem.com/harrisonhenri/unit-tests-with-react-native-testing-library-and-apollo-graphql-1mbo</guid>
      <description>&lt;p&gt;In this tutorial we will build a react-native shopping cart app using the version 3 of Apollo Graphql. This tutorial is based on &lt;a href="https://dev.to/komyg/creating-an-app-using-react-and-apollo-graphql-1ine"&gt;these great three part series of articles&lt;/a&gt; focusing on Apollo v3 and a basic structure of projects using this technology stack.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:  this tutorial assumes that you have a working knowledge of React-native, typescript and node&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The final source code of this tutorial can be obtained by acessing &lt;a href="https://github.com/HarrisonHenri/rick-morty-react-native-shop" rel="noopener noreferrer"&gt;https://github.com/HarrisonHenri/rick-morty-react-native-shop&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;This tutorial begin at code generated &lt;a href="https://dev.to/harrisonhenri/creating-a-react-native-app-using-apollo-graphql-v3-gj5"&gt;Part 1&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Configuring React-native Testing Lybrary
&lt;/h4&gt;

&lt;p&gt;In this tutorial we are going to use &lt;a href="https://testing-library.com/docs/react-native-testing-library/intro/" rel="noopener noreferrer"&gt;React-native testing lybrary&lt;/a&gt; and Jest to run unit tests on our code. First, let's install the dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add @testing-library/react-native
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then at &lt;em&gt;package.json&lt;/em&gt; we add this two fields at &lt;code&gt;jest&lt;/code&gt; configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    "transformIgnorePatterns": [
      "node_modules/(?!@react-native|react-native)"
    ],
    "setupFiles": [
      "./node_modules/react-native-gesture-handler/jestSetup.js"
    ],
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing the components
&lt;/h2&gt;

&lt;p&gt;Now that we have the &lt;a href="https://testing-library.com/docs/react-native-testing-library/intro/" rel="noopener noreferrer"&gt;React-native testing lybrary&lt;/a&gt; installed and configured, we are able to start to test our components.&lt;/p&gt;

&lt;h4&gt;
  
  
  Character card
&lt;/h4&gt;

&lt;p&gt;To test our &lt;em&gt;CharacterCard.tsx&lt;/em&gt;, first we add the &lt;code&gt;testID&lt;/code&gt; property to each &lt;code&gt;RectButton&lt;/code&gt; at &lt;em&gt;src/common/components/CharacterCard.tsx&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import {Image, StyleSheet, Text, View} from 'react-native';
import {RectButton} from 'react-native-gesture-handler';
import Icon from 'react-native-vector-icons/Entypo';
import {useUpdateChosenQuantity} from '../hooks/use-update-chosen-quantity';

interface Props {
  data: {
    id?: string | null;
    image?: string | null;
    name?: string | null;
    unitPrice?: number;
    chosenQuantity?: number;
  };
}

const CharacterCard: React.FC&amp;lt;Props&amp;gt; = ({data}) =&amp;gt; {
  const {onIncreaseChosenQuantity, onDecreaseChosenQuantity} =
    useUpdateChosenQuantity();

  return (
    &amp;lt;View style={styles.container}&amp;gt;
      {data.image &amp;amp;&amp;amp; &amp;lt;Image source={{uri: data.image}} style={styles.image} /&amp;gt;}
      &amp;lt;View style={styles.details}&amp;gt;
        &amp;lt;Text style={styles.text}&amp;gt;{data.name}&amp;lt;/Text&amp;gt;
        &amp;lt;Text style={styles.text}&amp;gt;{`U$ ${data.unitPrice}`}&amp;lt;/Text&amp;gt;
      &amp;lt;/View&amp;gt;
      &amp;lt;View style={styles.choseQuantityContainer}&amp;gt;
        &amp;lt;RectButton
          testID="charactter-remove-btn"
          onPress={onDecreaseChosenQuantity.bind(null, data.id as string)}&amp;gt;
          &amp;lt;Icon name="minus" size={24} color="#3D7199" /&amp;gt;
        &amp;lt;/RectButton&amp;gt;
        &amp;lt;Text style={styles.choseQuantityText}&amp;gt;{data.chosenQuantity}&amp;lt;/Text&amp;gt;
        &amp;lt;RectButton
          testID="charactter-add-btn"
          onPress={onIncreaseChosenQuantity.bind(null, data.id as string)}&amp;gt;
          &amp;lt;Icon name="plus" size={24} color="#3D7199" /&amp;gt;
        &amp;lt;/RectButton&amp;gt;
      &amp;lt;/View&amp;gt;
    &amp;lt;/View&amp;gt;
  );
};

export default CharacterCard;

const styles = StyleSheet.create({
  container: {
    width: '100%',
    borderRadius: 20,
    marginVertical: 8,
    paddingHorizontal: 8,
    paddingVertical: 24,
    backgroundColor: '#F0F0F0',
    flexDirection: 'row',
  },
  image: {width: 70, height: 70},
  details: {
    marginLeft: 8,
    justifyContent: 'space-between',
    flex: 1,
  },
  text: {
    fontSize: 16,
    fontWeight: 'bold',
  },
  choseQuantityContainer: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'space-between',
    flexDirection: 'row',
  },
  choseQuantityText: {
    padding: 8,
    borderRadius: 8,
    backgroundColor: '#fff',
    fontSize: 16,
    fontWeight: 'bold',
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now at &lt;em&gt;&lt;strong&gt;tests&lt;/strong&gt;/CharacterCard.spec.tsx&lt;/em&gt; we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {fireEvent, render} from '@testing-library/react-native';
import React from 'react';
import * as useUpdateChosenQuantityModule from '../src/common/hooks/use-update-chosen-quantity';
import CharacterCard from '../src/common/components/CharacterCard';

describe('CharacterCard component', () =&amp;gt; {
  beforeEach(() =&amp;gt; {
    jest
      .spyOn(useUpdateChosenQuantityModule, 'useUpdateChosenQuantity')
      .mockReturnValue({
        onIncreaseChosenQuantity: jest.fn(),
        onDecreaseChosenQuantity: jest.fn(),
      });
  });
  it('should render', () =&amp;gt; {
    const wrapper = render(&amp;lt;CharacterCard data={mockData} /&amp;gt;);
    expect(wrapper).toBeTruthy();
  });
  it('should call onIncreaseChosenQuantity and onDecreaseChosenQuantity on press', () =&amp;gt; {
    const mockOnIncreaseChosenQuantity = jest.fn();
    const mockOnDecreaseChosenQuantity = jest.fn();
    jest
      .spyOn(useUpdateChosenQuantityModule, 'useUpdateChosenQuantity')
      .mockReturnValue({
        onIncreaseChosenQuantity: mockOnIncreaseChosenQuantity,
        onDecreaseChosenQuantity: mockOnDecreaseChosenQuantity,
      });
    const wrapper = render(&amp;lt;CharacterCard data={mockData} /&amp;gt;);
    fireEvent.press(wrapper.getByTestId('charactter-remove-btn'));
    fireEvent.press(wrapper.getByTestId('charactter-add-btn'));
    expect(mockOnIncreaseChosenQuantity).toBeCalled();
    expect(mockOnDecreaseChosenQuantity).toBeCalled();
  });
});

const mockData = {
  id: 'any_id',
  image: 'any_image',
  name: 'any_name',
  unitPrice: 10,
  chosenQuantity: 0,
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, at begginig we test if the component render correctly. Then, after spying our local api, we ensure that each button calls the callbacks from the custom hook. &lt;/p&gt;

&lt;h4&gt;
  
  
  Home screen
&lt;/h4&gt;

&lt;p&gt;Again, we add a &lt;code&gt;testID&lt;/code&gt; at &lt;em&gt;scr/screens/Home.tsx&lt;/em&gt; to help us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import { ActivityIndicator, FlatList, StyleSheet, View } from 'react-native';
import { Character, useGetCharactersQuery } from '../common/generated/graphql';

import CharacterCard from '../common/components/CharacterCard';

const Home = () =&amp;gt; {
  const { data, loading } = useGetCharactersQuery();

  if (loading) {
    return (
      &amp;lt;View testID="progress" style={styles.container}&amp;gt;
        &amp;lt;ActivityIndicator color="#32B768" size="large" /&amp;gt;
      &amp;lt;/View&amp;gt;
    );
  }

  return (
    &amp;lt;View style={styles.container} testID="container"&amp;gt;
      &amp;lt;FlatList
        data={data?.characters?.results}
        renderItem={({ item }) =&amp;gt; &amp;lt;CharacterCard data={item as Character} /&amp;gt;}
        contentContainerStyle={styles.characterList}
      /&amp;gt;
    &amp;lt;/View&amp;gt;
  );
};

export default Home;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#FFFFFF',
  },
  characterList: {
    padding: 16,
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we test(&lt;em&gt;&lt;strong&gt;tests&lt;/strong&gt;/Home.spec.tsx&lt;/em&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {render, waitFor} from '@testing-library/react-native';
import React from 'react';
import {MockedProvider} from '@apollo/client/testing';
import {GetCharactersDocument} from '../src/common/generated/graphql';
import Home from '../src/screens/Home';

describe('Home component', () =&amp;gt; {
  it('should render and show progress on loading', () =&amp;gt; {
    const wrapper = render(
      &amp;lt;MockedProvider addTypename={false} mocks={[mock]}&amp;gt;
        &amp;lt;Home /&amp;gt;
      &amp;lt;/MockedProvider&amp;gt;,
    );

    expect(wrapper.queryByTestId('progress')).toBeTruthy();
  });
  it('should render the flatlist when whe loading is false', async () =&amp;gt; {
    const wrapper = render(
      &amp;lt;MockedProvider addTypename={false} mocks={[mock]}&amp;gt;
        &amp;lt;Home /&amp;gt;
      &amp;lt;/MockedProvider&amp;gt;,
    );

    await waitFor(() =&amp;gt; [
      expect(wrapper.queryByTestId('progress')).toBeFalsy(),
    ]);

    expect(wrapper.queryByTestId('container')?.children.length).toBe(1);
  });
});

const mock = {
  request: {
    query: GetCharactersDocument,
  },
  result: {
    data: {
      characters: {
        __typename: 'Characters',
        results: [
          {
            id: '1',
            __typename: 'Character',
            name: 'Rick Sanchez',
            image: 'https://rickandmortyapi.com/api/character/avatar/1.jpeg',
            species: 'Human',
            unitPrice: 1,
            chosenQuantity: 10,
            origin: {
              id: '1',
              __typename: 'Location',
              name: 'Earth (C-137)',
            },
            location: {
              id: '20',
              __typename: 'Location',
              name: 'Earth (Replacement Dimension)',
            },
          },
        ],
      },
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this test we use the &lt;code&gt;MockedProvider&lt;/code&gt; to, first, ensure that at loading we render the &lt;code&gt;ActivityIndicator&lt;/code&gt;. Then, we ensure that when the data is available, we render the view properly with only one character (since the data array has only one entry). &lt;/p&gt;

&lt;h4&gt;
  
  
  Cart screen
&lt;/h4&gt;

&lt;p&gt;Adding our &lt;code&gt;testID&lt;/code&gt; at &lt;em&gt;scr/screens/Cart.tsx&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useCallback } from 'react';
import { useNavigation } from '@react-navigation/native';
import { StyleSheet, Text, View, SafeAreaView, Button } from 'react-native';
import { useGetShoppingCartQuery } from '../common/generated/graphql';

const Cart = () =&amp;gt; {
  const navigation = useNavigation();
  const { data } = useGetShoppingCartQuery();

  const handleNavigation = useCallback(() =&amp;gt; {
    navigation.navigate('Home');
  }, [navigation]);

  return (
    &amp;lt;SafeAreaView style={styles.container}&amp;gt;
      {data?.shoppingCart?.numActionFigures ? (
        &amp;lt;&amp;gt;
          &amp;lt;View style={styles.content} testID="fulfilled-cart"&amp;gt;
            &amp;lt;Text style={styles.emoji}&amp;gt;🤗&amp;lt;/Text&amp;gt;
            &amp;lt;Text
              style={
                styles.subtitle
              }&amp;gt;{`Total number of items: ${data?.shoppingCart.numActionFigures}`}&amp;lt;/Text&amp;gt;
            &amp;lt;Text
              style={
                styles.subtitle
              }&amp;gt;{`Total price: U$ ${data?.shoppingCart.totalPrice}`}&amp;lt;/Text&amp;gt;
          &amp;lt;/View&amp;gt;
        &amp;lt;/&amp;gt;
      ) : (
        &amp;lt;&amp;gt;
          &amp;lt;View style={styles.content} testID="empty-cart"&amp;gt;
            &amp;lt;Text style={styles.emoji}&amp;gt;😢&amp;lt;/Text&amp;gt;
            &amp;lt;Text style={styles.title}&amp;gt;Empty cart!&amp;lt;/Text&amp;gt;
            &amp;lt;View style={styles.footer}&amp;gt;
              &amp;lt;Button title="Go back to shop" onPress={handleNavigation} /&amp;gt;
            &amp;lt;/View&amp;gt;
          &amp;lt;/View&amp;gt;
        &amp;lt;/&amp;gt;
      )}
    &amp;lt;/SafeAreaView&amp;gt;
  );
};

export default Cart;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  content: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    width: '100%',
  },
  title: {
    fontSize: 24,
    marginTop: 15,
    lineHeight: 32,
    textAlign: 'center',
  },
  subtitle: {
    fontSize: 16,
    lineHeight: 32,
    marginTop: 8,
    textAlign: 'center',
    paddingHorizontal: 20,
  },
  emoji: {
    fontSize: 44,
    textAlign: 'center',
  },
  footer: {
    width: '100%',
    paddingHorizontal: 20,
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then(&lt;em&gt;&lt;strong&gt;tests&lt;/strong&gt;/Cart.spec.tsx&lt;/em&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {fireEvent, render, waitFor} from '@testing-library/react-native';
import React from 'react';
import {MockedProvider} from '@apollo/client/testing';
import Cart from '../src/screens/Cart';

const mockedNavigate = jest.fn();

jest.mock('@react-navigation/native', () =&amp;gt; ({
  useNavigation: () =&amp;gt; ({navigate: mockedNavigate}),
}));

describe('Cart component', () =&amp;gt; {
  it('should render with empty cart and navigate on click', async () =&amp;gt; {
    const resolvers = {
      Query: {
        shoppingCart: jest.fn().mockReturnValue(null),
      },
    };

    const wrapper = render(
      &amp;lt;MockedProvider resolvers={resolvers} addTypename={false} mocks={[]}&amp;gt;
        &amp;lt;Cart /&amp;gt;
      &amp;lt;/MockedProvider&amp;gt;,
    );

    await waitFor(() =&amp;gt; [
      expect(wrapper.queryByTestId('empty-cart')).toBeTruthy(),
      expect(wrapper.queryByTestId('fulfilled-cart')).toBeFalsy(),
    ]);

    fireEvent.press(wrapper.getByRole('button'));
    expect(mockedNavigate).toHaveBeenCalledWith('Home');
  });
  it('should render when the shoppingCart cart is fulfilled', async () =&amp;gt; {
    const resolvers = {
      Query: {
        shoppingCart: jest.fn().mockReturnValue({
          id: 'any_id',
          totalPrice: 10,
          numActionFigures: 10,
        }),
      },
    };

    const wrapper = render(
      &amp;lt;MockedProvider resolvers={resolvers} addTypename={false} mocks={[]}&amp;gt;
        &amp;lt;Cart /&amp;gt;
      &amp;lt;/MockedProvider&amp;gt;,
    );

    await waitFor(() =&amp;gt; [
      expect(wrapper.queryByTestId('empty-cart')).toBeFalsy(),
      expect(wrapper.queryByTestId('fulfilled-cart')).toBeTruthy(),
    ]);
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, different from the home screen test, instead of using the mock of the query result we use resolvers, since this is a local query. The two cases of use tested are: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the cart is empty, we show a button to allow the user go back to the home screen.&lt;/li&gt;
&lt;li&gt;If the cart is fulfilled we show the cart content.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing the custom hook
&lt;/h2&gt;

&lt;p&gt;To test our &lt;code&gt;use-update-chosen-quantity.ts&lt;/code&gt; we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {fireEvent, render} from '@testing-library/react-native';
import React from 'react';
import {MockedProvider} from '@apollo/client/testing';
import {ApolloClient, gql, InMemoryCache} from '@apollo/client';
import {useUpdateChosenQuantity} from '../src/common/hooks/use-update-chosen-quantity';
import {Button} from 'react-native';
import {
  CharacterDataFragment,
  CharacterDataFragmentDoc,
  GetShoppingCartDocument,
  GetShoppingCartQuery,
} from '../src/common/generated/graphql';

describe('useUpdateChosenQuantity hook', () =&amp;gt; {
  let cache: InMemoryCache;
  let client: ApolloClient&amp;lt;any&amp;gt;;

  beforeEach(() =&amp;gt; {
    cache = new InMemoryCache();

    cache.writeQuery({
      query: mockCharactersQuery,
      data: mockCharactersData,
    });

    client = new ApolloClient({
      cache,
    });
  });

  it('should increase the quantity correctly', async () =&amp;gt; {
    const MockComponent = () =&amp;gt; {
      const {onIncreaseChosenQuantity} = useUpdateChosenQuantity();

      return (
        &amp;lt;Button
          title="any_title"
          onPress={() =&amp;gt; onIncreaseChosenQuantity('1')}
        /&amp;gt;
      );
    };

    const wrapper = render(
      &amp;lt;MockedProvider cache={cache}&amp;gt;
        &amp;lt;MockComponent /&amp;gt;
      &amp;lt;/MockedProvider&amp;gt;,
    );

    fireEvent.press(wrapper!.getByRole('button')!);
    fireEvent.press(wrapper!.getByRole('button')!);

    const shoopingCart = client.readQuery&amp;lt;GetShoppingCartQuery&amp;gt;({
      query: GetShoppingCartDocument,
    });

    const character = client.readFragment&amp;lt;CharacterDataFragment&amp;gt;({
      fragment: CharacterDataFragmentDoc,
      id: 'Character:1',
    });

    expect(shoopingCart?.shoppingCart?.numActionFigures).toBe(2);
    expect(shoopingCart?.shoppingCart?.totalPrice).toBe(20);
    expect(character?.chosenQuantity).toBe(2);
  });
  it('should decrease the quantity correctly', async () =&amp;gt; {
    cache.writeQuery({
      query: mockShoppingCartQuery,
      data: mockShoppinData,
    });

    client = new ApolloClient({
      cache,
    });

    const MockComponent = () =&amp;gt; {
      const {onDecreaseChosenQuantity} = useUpdateChosenQuantity();

      return (
        &amp;lt;Button
          title="any_title"
          onPress={() =&amp;gt; onDecreaseChosenQuantity('2')}
        /&amp;gt;
      );
    };

    const wrapper = render(
      &amp;lt;MockedProvider cache={cache}&amp;gt;
        &amp;lt;MockComponent /&amp;gt;
      &amp;lt;/MockedProvider&amp;gt;,
    );

    fireEvent.press(wrapper!.getByRole('button')!);
    fireEvent.press(wrapper!.getByRole('button')!);
    fireEvent.press(wrapper!.getByRole('button')!);
    fireEvent.press(wrapper!.getByRole('button')!);
    fireEvent.press(wrapper!.getByRole('button')!);

    const shoopingCart = client.readQuery&amp;lt;GetShoppingCartQuery&amp;gt;({
      query: GetShoppingCartDocument,
    });

    const character = client.readFragment&amp;lt;CharacterDataFragment&amp;gt;({
      fragment: CharacterDataFragmentDoc,
      id: 'Character:2',
    });

    expect(shoopingCart?.shoppingCart?.numActionFigures).toBe(0);
    expect(shoopingCart?.shoppingCart?.totalPrice).toBe(0);
    expect(character?.chosenQuantity).toBe(0);
  });
});

const mockCharactersQuery = gql`
  fragment characterData on Character {
    id
    __typename
    name
    unitPrice @client
    chosenQuantity @client
  }

  query GetCharacters {
    characters {
      __typename
      results {
        ...characterData
        image
        species
        origin {
          id
          __typename
          name
        }
        location {
          id
          __typename
          name
        }
      }
    }
  }
`;

const mockCharactersData = {
  characters: {
    __typename: 'Characters',
    results: [
      {
        id: '1',
        __typename: 'Character',
        name: 'Rick Sanchez',
        image: 'https://rickandmortyapi.com/api/character/avatar/1.jpeg',
        species: 'Human',
        unitPrice: 10,
        chosenQuantity: 0,
        origin: {
          id: '1',
          __typename: 'Location',
          name: 'Earth (C-137)',
        },
        location: {
          id: '20',
          __typename: 'Location',
          name: 'Earth (Replacement Dimension)',
        },
      },
      {
        id: '2',
        __typename: 'Character',
        name: 'Rick Sanchez',
        image: 'https://rickandmortyapi.com/api/character/avatar/1.jpeg',
        species: 'Human',
        unitPrice: 10,
        chosenQuantity: 1,
        origin: {
          id: '1',
          __typename: 'Location',
          name: 'Earth (C-137)',
        },
        location: {
          id: '20',
          __typename: 'Location',
          name: 'Earth (Replacement Dimension)',
        },
      },
    ],
  },
};

const mockShoppingCartQuery = gql`
  query GetShoppingCart {
    shoppingCart @client {
      id
      totalPrice
      numActionFigures
    }
  }
`;

const mockShoppinData = {
  shoppingCart: {
    id: 'ShoppingCart:1',
    totalPrice: 10,
    numActionFigures: 1,
  },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here it was presented a lot of code, so let's carve up what we are really doing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At bottom of the file we add some &lt;em&gt;gql&lt;/em&gt; to mock our local queries and results.&lt;/li&gt;
&lt;li&gt;Before each test we write the caracters data into local cache.&lt;/li&gt;
&lt;li&gt;Then, when testing each callback, we check the state of the cache after executing the hook to both: character fragments and the shopping cart.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If everything goes fine, when running &lt;code&gt;yarn test&lt;/code&gt; all tests should pass!. Again, I'll be happy if you could provide me any feedback about the  code, structure, doubt or anything that could make me a better developer!&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>testing</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Creating a React Native app using Apollo Graphql v3</title>
      <dc:creator>Harrison Henri dos Santos Nascimento</dc:creator>
      <pubDate>Sun, 27 Jun 2021 23:51:03 +0000</pubDate>
      <link>https://forem.com/harrisonhenri/creating-a-react-native-app-using-apollo-graphql-v3-gj5</link>
      <guid>https://forem.com/harrisonhenri/creating-a-react-native-app-using-apollo-graphql-v3-gj5</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this tutorial we will build a react-native shopping cart app using the version 3 of Apollo Graphql. This tutorial is based on &lt;a href="https://dev.to/komyg/creating-an-app-using-react-and-apollo-graphql-1ine"&gt;these great three part series of articles&lt;/a&gt; focusing on Apollo v3 and a basic structure of projects using this technology stack.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:  this tutorial assumes that you have a working knowledge of React-native, typescript and node&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The final source code of this tutorial can be obtained by acessing &lt;a href="https://github.com/HarrisonHenri/rick-morty-react-native-shop" rel="noopener noreferrer"&gt;https://github.com/HarrisonHenri/rick-morty-react-native-shop&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beginning
&lt;/h2&gt;

&lt;p&gt;Firstly, we create a brand new react-native app using &lt;a href="https://reactnative.dev/docs/environment-setup" rel="noopener noreferrer"&gt;react-native cli&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx react-native init rickmortyshop --template react-native-template-typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then, we remove the &lt;em&gt;App.tsx&lt;/em&gt; file, add a new &lt;em&gt;index.tsx&lt;/em&gt; file at &lt;em&gt;src&lt;/em&gt; and modify the content of &lt;em&gt;index.js&lt;/em&gt; to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**
 * @format
 */

import { AppRegistry } from 'react-native';
import App from './src/index';
import { name as appName } from './app.json';

AppRegistry.registerComponent(appName, () =&amp;gt; App);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Apollo graphql installation and setup
&lt;/h2&gt;

&lt;p&gt;Now we will Add the necessary packages from apollo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add graphql @apollo/client graphql-tag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The setup is very simillar to presented at &lt;a href="https://dev.to/komyg/creating-an-app-using-react-and-apollo-graphql-1ine"&gt;https://dev.to/komyg/creating-an-app-using-react-and-apollo-graphql-1ine&lt;/a&gt;, so I'll describe this part very succinctly:&lt;/p&gt;

&lt;h4&gt;
  
  
  Apollo client
&lt;/h4&gt;

&lt;p&gt;To create our connection between the client app and the Graphql server at &lt;em&gt;src/common/config/apollo/http-link.ts&lt;/em&gt; we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { HttpLink } from '@apollo/client/link/http';

export function createHttpLink() {
  return new HttpLink({
    uri: 'https://rickandmortyapi.com/graphql',
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Error link
&lt;/h4&gt;

&lt;p&gt;Now, to to handle errors at our requests at &lt;em&gt;src/common/config/apollo/error-link.ts&lt;/em&gt; we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { onError } from '@apollo/client/link/error';

export const errorLink = onError(({ graphQLErrors, networkError, response, operation }) =&amp;gt; {
  if (graphQLErrors) {
    for (const error of graphQLErrors) {
      console.error(
        `[GraphQL error]: Message: ${error.message}, Location: ${error.locations}, Path: ${error.path}`,
        operation,
        response
      );
    }
  }
  if (networkError) {
    console.error(`[Network error]: ${networkError}`, operation, response);
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Apollo local cache
&lt;/h4&gt;

&lt;p&gt;Here, different from &lt;a href="https://dev.to/komyg/creating-an-app-using-react-and-apollo-graphql-1ine"&gt;https://dev.to/komyg/creating-an-app-using-react-and-apollo-graphql-1ine&lt;/a&gt;, we just write (at &lt;em&gt;src/common/config/apollo/local-cache.ts&lt;/em&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {InMemoryCache} from '@apollo/client';

export const localCache = new InMemoryCache();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's why, we don't need to &lt;code&gt;freezeResults&lt;/code&gt; anymore because now this is the default behavior.&lt;/p&gt;

&lt;h4&gt;
  
  
  Wrappping all
&lt;/h4&gt;

&lt;p&gt;At &lt;em&gt;src/common/apollo-client.ts&lt;/em&gt; we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ApolloClient, ApolloLink } from '@apollo/client';
import { errorLink } from './apollo/error-link';
import { createHttpLink } from './apollo/http-link';
import { localCache } from './apollo/local-cache';

export function createApolloClient() {
  const httpLink = createHttpLink();

  const apolloClient = new ApolloClient({
    link: ApolloLink.from([errorLink, httpLink]),
    connectToDevTools: process.env.NODE_ENV !== 'production',
    cache: localCache,
    assumeImmutableResults: true,
  });

  return apolloClient;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are putting everything together and creating our &lt;code&gt;ApolloClient&lt;/code&gt; object. Now at &lt;code&gt;src/index.tsx&lt;/code&gt; we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import { ApolloProvider } from '@apollo/client';
import { Text, View } from 'react-native';
import { createApolloClient } from './common/config/apollo-client';

const apolloClient = createApolloClient();

const App = () =&amp;gt; {
  return (
    &amp;lt;ApolloProvider client={apolloClient}&amp;gt;
      &amp;lt;View&amp;gt;
        &amp;lt;Text&amp;gt;Hello&amp;lt;/Text&amp;gt;
      &amp;lt;/View&amp;gt;
    &amp;lt;/ApolloProvider&amp;gt;
  );
};

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in order to make the ApolloProvider available globally. &lt;/p&gt;

&lt;h2&gt;
  
  
  Graphql codegen
&lt;/h2&gt;

&lt;p&gt;Now we will install and setup the &lt;a href="https://github.com/dotansimha/graphql-code-generator" rel="noopener noreferrer"&gt;graphql codegen&lt;/a&gt;,a tool that automatically generates typescript types based on the server schema. After installing, execute the cli using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn graphql-codegen init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then, follow the steps bellow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Application built with React.&lt;/li&gt;
&lt;li&gt;Type the Rick and Morty Graphql api url: &lt;a href="https://rickandmortyapi.com/graphql" rel="noopener noreferrer"&gt;https://rickandmortyapi.com/graphql&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Use the default value (&lt;code&gt;src/**/*.graphql&lt;/code&gt;) for the fragment and operations.&lt;/li&gt;
&lt;li&gt;Then pick the following plugins: &lt;code&gt;TypeScript&lt;/code&gt;, &lt;code&gt;TypeScript Operators&lt;/code&gt;, &lt;code&gt;TypeScript React Apollo&lt;/code&gt; and &lt;code&gt;Introspection Fragment Matcher&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Type the following value: &lt;code&gt;src/common/generated/graphql.tsx&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Answer no when it asks if you want to generate an introspection file.&lt;/li&gt;
&lt;li&gt;Use the default value for the name of the config file.&lt;/li&gt;
&lt;li&gt;Type in &lt;code&gt;gen-graphql&lt;/code&gt; when it asks the name of the script in the package.json that will be used to generate the graphql files.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;and the &lt;code&gt;yarn install&lt;/code&gt; to install all necessary plugins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the home screen
&lt;/h2&gt;

&lt;p&gt;First, we will create our first query to retrieve all characters from Rick and Morty cartoon. To achieve this, at &lt;em&gt;src/common/graphql/queries/get-characters.query.graphql&lt;/em&gt; we paste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query GetCharacters {
  characters {
    __typename
    results {
      id
      __typename
      name
      image
      species
      origin {
        id
        __typename
        name
      }
      location {
        id
        __typename
        name
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn gen-graphql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and if the command runs successfully, you should see that a &lt;em&gt;graphql.tsx&lt;/em&gt; file was created. Now, at &lt;em&gt;src/common/components/CharacterCard.tsx&lt;/em&gt; we paste the code bellow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import { Image, StyleSheet, Text, View } from 'react-native';

interface Props {
  data: {
    image?: string | null;
    name?: string | null;
  };
}

const CharacterCard: React.FC&amp;lt;Props&amp;gt; = ({ data }) =&amp;gt; {
   return (
    &amp;lt;View style={styles.container}&amp;gt;
      {data.image &amp;amp;&amp;amp; (
        &amp;lt;Image source={{ uri: data.image }} style={styles.image} /&amp;gt;
      )}
      &amp;lt;View style={styles.details}&amp;gt;
        &amp;lt;Text style={styles.text}&amp;gt;{data.name}&amp;lt;/Text&amp;gt;
      &amp;lt;/View&amp;gt;
    &amp;lt;/View&amp;gt;
  );
};

export default CharacterCard;

const styles = StyleSheet.create({
  container: {
    width: '100%',
    borderRadius: 20,
    marginVertical: 8,
    paddingHorizontal: 8,
    paddingVertical: 24,
    backgroundColor: '#F0F0F0',
    flexDirection: 'row',
  },
  image: { width: 70, height: 70 },
  details: {
    marginLeft: 8,
    justifyContent: 'space-between',
    flex: 1,
  },
  text: {
    fontSize: 16,
    fontWeight: 'bold',
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, at &lt;em&gt;scr/screens/Home.tsx&lt;/em&gt; we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import { ActivityIndicator, FlatList, StyleSheet, View } from 'react-native';
import { Character, useGetCharactersQuery } from '../common/generated/graphql';

import CharacterCard from '../common/components/CharacterCard';

const Home = () =&amp;gt; {
  const { data, loading } = useGetCharactersQuery();

  if (loading) {
    return (
      &amp;lt;View style={styles.container}&amp;gt;
        &amp;lt;ActivityIndicator color="#32B768" size="large" /&amp;gt;
      &amp;lt;/View&amp;gt;
    );
  }

  return (
    &amp;lt;View style={styles.container}&amp;gt;
      &amp;lt;FlatList
        data={data?.characters?.results}
        renderItem={({ item }) =&amp;gt; &amp;lt;CharacterCard data={item as Character} /&amp;gt;}
        contentContainerStyle={styles.characterList}
      /&amp;gt;
    &amp;lt;/View&amp;gt;
  );
};

export default Home;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#FFFFFF',
  },
  characterList: {
    padding: 16,
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are using the &lt;code&gt;useGetCharactersQuery&lt;/code&gt; hook provided by the &lt;a href="https://github.com/dotansimha/graphql-code-generator" rel="noopener noreferrer"&gt;graphql codegen&lt;/a&gt;. The hook provided useful fetch tools like the server response (&lt;code&gt;data&lt;/code&gt;) and the &lt;code&gt;loading&lt;/code&gt; state. Then, finally at &lt;em&gt;src/index.tsx&lt;/em&gt; we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import { ApolloProvider } from '@apollo/client';
import { createApolloClient } from './common/config/apollo-client';

import Home from './screens/Home';

const apolloClient = createApolloClient();

const App = () =&amp;gt; {
  return (
    &amp;lt;ApolloProvider client={apolloClient}&amp;gt;
      &amp;lt;Home /&amp;gt;
    &amp;lt;/ApolloProvider&amp;gt;
  );
};

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything goes well, when you run &lt;code&gt;yarn start&lt;/code&gt; you will se your app cards with all Ricky Morty characters presented! &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the shopping cart logic
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Updating queries
&lt;/h4&gt;

&lt;p&gt;Now that we are retrieving the data from the remote server we can start to create the shopping cart logic. First we will write our local-shema at &lt;em&gt;src/common/config/local-schema.graphql&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Query {
  shoppingCart: ShoppingCart!
}

extend type Character {
  chosenQuantity: Int!
  unitPrice: Int!
}

type ShoppingCart {
  id: ID!
  totalPrice: Int!
  numActionFigures: Int!
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then, at &lt;em&gt;src/common/graphql/fragments/character.fragment.graphql&lt;/em&gt; we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fragment characterData on Character {
  id
  __typename
  name
  unitPrice @client
  chosenQuantity @client
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we create this fragment to insert @client fields (fields that only exists at our local cache state) and also to use this fragment at our character query. So, in order to do this, we update the &lt;em&gt;src/common/graphql/queries/get-characters.query.graphql&lt;/em&gt; pasting the code bellow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query GetCharacters {
  characters {
    __typename
    results {
      ...characterData
      image
      species
      origin {
        id
        __typename
        name
      }
      location {
        id
        __typename
        name
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we paste the code bellow at &lt;em&gt;src/common/graphql/queries/shopping-cart.query.graphql&lt;/em&gt; to create our shopping cart query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query GetShoppingCart {
  shoppingCart @client {
    id
    totalPrice
    numActionFigures
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Updating local cache initialization
&lt;/h4&gt;

&lt;p&gt;Now that we have our local queries, we will update our local cache at &lt;em&gt;src/common/config/apollo/local-cache.ts&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { gql, InMemoryCache } from '@apollo/client';

export const localCache = new InMemoryCache();

export const LocalCacheInitQuery = gql`
  query LocalCacheInit {
    shoppingCart
  }
`;

export function initialLocalCache() {
  localCache.writeQuery({
    query: LocalCacheInitQuery,
    data: {
      shoppingCart: null,
    },
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are initializating our shopping cart with a null value. After that, at &lt;em&gt;src/common/config/apollo-client.ts&lt;/em&gt; we paste the code bellow to initializate our local cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ApolloClient, ApolloLink } from '@apollo/client';
import { errorLink } from './apollo/error-link';
import { createHttpLink } from './apollo/http-link';
import { initialLocalCache, localCache } from './apollo/local-cache';

export function createApolloClient() {
  const httpLink = createHttpLink();

  const apolloClient = new ApolloClient({
    link: ApolloLink.from([errorLink, httpLink]),
    connectToDevTools: process.env.NODE_ENV !== 'production',
    cache: localCache,
    assumeImmutableResults: true,
  });

  return apolloClient;
}

initialLocalCache();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Creating our fieldPolicies
&lt;/h4&gt;

&lt;p&gt;Now, instead of using resolvers (they are being deprecated in Apollo v3) we will use the fieldPolicies to initialize the local fields. Back to &lt;em&gt;src/common/config/apollo/local-cache.ts&lt;/em&gt; we paste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { gql, InMemoryCache } from '@apollo/client';

export const localCache = new InMemoryCache({
  typePolicies: {
    Character: {
      fields: {
        chosenQuantity: {
          read(chosenQuantity) {
            if (chosenQuantity === undefined || chosenQuantity === null) {
              return 0;
            }
            return chosenQuantity;
          },
        },
        unitPrice: {
          read(_, { readField }) {
            const charName = readField('name');
            switch (charName) {
              case 'Albert Einstein':
                return 25;
              case 'Rick Sanchez':
              case 'Morty Smith':
                return 10;

              default:
                return 5;
            }
          },
        },
      },
    },
  },
});

export const LocalCacheInitQuery = gql`
  query LocalCacheInit {
    shoppingCart
  }
`;

export function initialLocalCache() {
  localCache.writeQuery({
    query: LocalCacheInitQuery,
    data: {
      shoppingCart: null,
    },
  });
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's dive in what are we doing here, piece by piece:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We modify our InMemoryCache object, adding field policies to the Character type.&lt;/li&gt;
&lt;li&gt;At the chosenQuantity's field policy we add a &lt;a href="https://www.apollographql.com/docs/react/caching/cache-field-behavior/#the-read-function" rel="noopener noreferrer"&gt;read function&lt;/a&gt; based on the existing value of &lt;code&gt;chosenQuantity&lt;/code&gt; field. If this field is not &lt;code&gt;falsy&lt;/code&gt; we return its value, if not, return &lt;code&gt;0&lt;/code&gt;. Since this is a local field, the value obtained from the server is initially falsy.&lt;/li&gt;
&lt;li&gt;At the unitPrice's field policy we add another read function based on the value of name field. To achieve this we use the &lt;code&gt;readField&lt;/code&gt; function helper, passing the name of the field that we are interested. In this particular case, we are looking for change the character's &lt;code&gt;unitPrice&lt;/code&gt; based on its own &lt;code&gt;name&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, at &lt;em&gt;codegen.yml&lt;/em&gt; we paste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;overwrite: true
schema: "https://rickandmortyapi.com/graphql"
documents: "src/**/*.graphql"
generates:
  src/common/generated/graphql.tsx:
    schema: "./src/common/config/local-schema.graphql"
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-react-apollo"
      - "fragment-matcher"

  src/common/generated/fragment-matcher.json:
    schema: "./src/common/config/local-schema.graphql"
    plugins:
      - "fragment-matcher"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finallly we can generate the typing again running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn gen-graphql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Updating the character card
&lt;/h4&gt;

&lt;p&gt;After that, we install some dependencies that will be used now, and later on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add react-native-vector-icons @types/react-native-vector-icons @react-navigation/native react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and update the &lt;em&gt;src/common/components/CharacterCard.tsx&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import { Image, StyleSheet, Text, View } from 'react-native';
import { RectButton } from 'react-native-gesture-handler';
import Icon from 'react-native-vector-icons/Entypo';

interface Props {
  data: {
    image?: string | null;
    name?: string | null;
    unitPrice?: number;
    chosenQuantity?: number;
  };
}

const CharacterCard: React.FC&amp;lt;Props&amp;gt; = ({ data }) =&amp;gt; {
  return (
    &amp;lt;View style={styles.container}&amp;gt;
      {data.image &amp;amp;&amp;amp; (
        &amp;lt;Image source={{ uri: data.image }} style={styles.image} /&amp;gt;
      )}
      &amp;lt;View style={styles.details}&amp;gt;
        &amp;lt;Text style={styles.text}&amp;gt;{data.name}&amp;lt;/Text&amp;gt;
        &amp;lt;Text style={styles.text}&amp;gt;{`U$ ${data.unitPrice}`}&amp;lt;/Text&amp;gt;
      &amp;lt;/View&amp;gt;
      &amp;lt;View style={styles.choseQuantityContainer}&amp;gt;
        &amp;lt;RectButton&amp;gt;
          &amp;lt;Icon name="minus" size={24} color="#3D7199" /&amp;gt;
        &amp;lt;/RectButton&amp;gt;
        &amp;lt;Text style={styles.choseQuantityText}&amp;gt;{data.chosenQuantity}&amp;lt;/Text&amp;gt;
        &amp;lt;RectButton&amp;gt;
          &amp;lt;Icon name="plus" size={24} color="#3D7199" /&amp;gt;
        &amp;lt;/RectButton&amp;gt;
      &amp;lt;/View&amp;gt;
    &amp;lt;/View&amp;gt;
  );
};

export default CharacterCard;

const styles = StyleSheet.create({
  container: {
    width: '100%',
    borderRadius: 20,
    marginVertical: 8,
    paddingHorizontal: 8,
    paddingVertical: 24,
    backgroundColor: '#F0F0F0',
    flexDirection: 'row',
  },
  image: { width: 70, height: 70 },
  details: {
    marginLeft: 8,
    justifyContent: 'space-between',
    flex: 1,
  },
  text: {
    fontSize: 16,
    fontWeight: 'bold',
  },
  choseQuantityContainer: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'space-between',
    flexDirection: 'row',
  },
  choseQuantityText: {
    padding: 8,
    borderRadius: 8,
    backgroundColor: '#fff',
    fontSize: 16,
    fontWeight: 'bold',
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this update we are presenting the new local fields &lt;code&gt;unitPrice&lt;/code&gt;, &lt;code&gt;chosenQuantity&lt;/code&gt; and the &lt;code&gt;RectButton&lt;/code&gt; to increase and decrease the quantities. Now we will build this logic to update the &lt;code&gt;chosenQuantity&lt;/code&gt; at &lt;em&gt;src/common/hooks/use-update-chosen-quantity.ts&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useApolloClient } from '@apollo/client';
import { useCallback } from 'react';
import {
  CharacterDataFragment,
  CharacterDataFragmentDoc,
  GetShoppingCartDocument,
  GetShoppingCartQuery,
} from '../generated/graphql';

interface UpdateChosenQuantity {
  (): {
    onIncreaseChosenQuantity: (id: string) =&amp;gt; void;
    onDecreaseChosenQuantity: (id: string) =&amp;gt; void;
  };
}

export const useUpdateChosenQuantity: UpdateChosenQuantity = () =&amp;gt; {
  const client = useApolloClient();

  const getCharacter = useCallback(
    (id: string) =&amp;gt;
      client.readFragment&amp;lt;CharacterDataFragment&amp;gt;({
        fragment: CharacterDataFragmentDoc,
        id: `Character:${id}`,
      }),
    [client],
  );

  const getShoppingCartParams = useCallback(() =&amp;gt; {
    const shoppingCart = client.readQuery&amp;lt;GetShoppingCartQuery&amp;gt;({
      query: GetShoppingCartDocument,
    })?.shoppingCart;

    if (!shoppingCart) {
      return {
        id: 'ShoppingCart:1',
        totalPrice: 0,
        numActionFigures: 0,
      };
    }

    return {
      ...shoppingCart,
    };
  }, [client]);

  const increaseShoppingCart = useCallback(
    (unitPrice: number) =&amp;gt; {
      let { id, totalPrice, numActionFigures } = getShoppingCartParams();

      totalPrice = totalPrice + unitPrice;
      numActionFigures = numActionFigures + 1;

      client.writeQuery&amp;lt;GetShoppingCartQuery&amp;gt;({
        query: GetShoppingCartDocument,
        data: {
          shoppingCart: {
            id,
            numActionFigures,
            totalPrice,
          },
        },
      });
    },
    [client, getShoppingCartParams],
  );

  const decreaseShoppingCart = useCallback(
    (unitPrice: number) =&amp;gt; {
      let { id, totalPrice, numActionFigures } = getShoppingCartParams();

      totalPrice = totalPrice - unitPrice;
      numActionFigures = numActionFigures - 1;

      if (totalPrice &amp;lt; 0) {
        totalPrice = 0;
      }
      if (numActionFigures &amp;lt; 0) {
        numActionFigures = 0;
      }

      client.writeQuery&amp;lt;GetShoppingCartQuery&amp;gt;({
        query: GetShoppingCartDocument,
        data: {
          shoppingCart: {
            id,
            numActionFigures,
            totalPrice,
          },
        },
      });
    },
    [client, getShoppingCartParams],
  );

  const onIncreaseChosenQuantity = useCallback(
    (id: string) =&amp;gt; {
      const character = getCharacter(id);

      client.writeFragment&amp;lt;CharacterDataFragment&amp;gt;({
        fragment: CharacterDataFragmentDoc,
        id: `Character:${id}`,
        data: {
          ...(character as CharacterDataFragment),
          chosenQuantity: (character?.chosenQuantity ?? 0) + 1,
        },
      });
      increaseShoppingCart(character?.unitPrice as number);
    },
    [client, getCharacter, increaseShoppingCart],
  );

  const onDecreaseChosenQuantity = useCallback(
    (id: string) =&amp;gt; {
      const character = getCharacter(id);

      let chosenQuantity = (character?.chosenQuantity ?? 0) - 1;

      if (chosenQuantity &amp;lt; 0) {
        chosenQuantity = 0;
      }

      client.writeFragment&amp;lt;CharacterDataFragment&amp;gt;({
        fragment: CharacterDataFragmentDoc,
        id: `Character:${id}`,
        data: {
          ...(character as CharacterDataFragment),
          chosenQuantity,
        },
      });
      decreaseShoppingCart(character?.unitPrice as number);
    },
    [client, getCharacter, decreaseShoppingCart],
  );

  return {
    onIncreaseChosenQuantity,
    onDecreaseChosenQuantity,
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's carve up what we are doing here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firs't we import our types from the generated file.&lt;/li&gt;
&lt;li&gt;Then we create an interface to type our local api.&lt;/li&gt;
&lt;li&gt;Then we get the &lt;code&gt;useApolloClient()&lt;/code&gt; hook.&lt;/li&gt;
&lt;li&gt;After that we create a helper &lt;code&gt;getCharacter&lt;/code&gt; to read our character fragment, passing the fragment doc and the &lt;code&gt;id&lt;/code&gt; of the fragment (usually the apollo saves the fragments in a normalized way, using the &lt;code&gt;typename:id&lt;/code&gt; as a unique key).&lt;/li&gt;
&lt;li&gt;After this we create the &lt;code&gt;getShoppingCartParams&lt;/code&gt; to retrive the &lt;code&gt;shoppingCart&lt;/code&gt; data from the cache. If the &lt;code&gt;shoppingCart&lt;/code&gt; is null we return some default values.&lt;/li&gt;
&lt;li&gt;On &lt;code&gt;increaseShoppingCart&lt;/code&gt; we retrieve the data from &lt;code&gt;getShoppingCartParams&lt;/code&gt; and add the &lt;code&gt;unitPrice&lt;/code&gt; from the character being edited. The same happens to &lt;em&gt;decreaseShoppingCart&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;On &lt;code&gt;onIncreaseChosenQuantity&lt;/code&gt; we &lt;code&gt;getCharacter&lt;/code&gt;, update his chosenQuantity properly and passes its &lt;code&gt;unitPrice&lt;/code&gt; to the &lt;code&gt;increaseShoppingCart&lt;/code&gt;. The similar ocurs with the &lt;em&gt;onDecreaseChosenQuantity&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Finally we expose this api.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Finishing the app
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Updating the character card
&lt;/h4&gt;

&lt;p&gt;At &lt;em&gt;src/common/components/character-cart.tsx&lt;/em&gt; we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import { Image, StyleSheet, Text, View } from 'react-native';
import { RectButton } from 'react-native-gesture-handler';
import Icon from 'react-native-vector-icons/Entypo';
import { useUpdateChosenQuantity } from '../hooks/use-update-chosen-quantity';

interface Props {
  data: {
    id?: string | null;
    image?: string | null;
    name?: string | null;
    unitPrice?: number;
    chosenQuantity?: number;
  };
}

const CharacterCard: React.FC&amp;lt;Props&amp;gt; = ({ data }) =&amp;gt; {
  const { onIncreaseChosenQuantity, onDecreaseChosenQuantity } =
    useUpdateChosenQuantity();

  return (
    &amp;lt;View style={styles.container}&amp;gt;
      {data.image &amp;amp;&amp;amp; (
        &amp;lt;Image source={{ uri: data.image }} style={styles.image} /&amp;gt;
      )}
      &amp;lt;View style={styles.details}&amp;gt;
        &amp;lt;Text style={styles.text}&amp;gt;{data.name}&amp;lt;/Text&amp;gt;
        &amp;lt;Text style={styles.text}&amp;gt;{`U$ ${data.unitPrice}`}&amp;lt;/Text&amp;gt;
      &amp;lt;/View&amp;gt;
      &amp;lt;View style={styles.choseQuantityContainer}&amp;gt;
        &amp;lt;RectButton
          onPress={onDecreaseChosenQuantity.bind(null, data.id as string)}&amp;gt;
          &amp;lt;Icon name="minus" size={24} color="#3D7199" /&amp;gt;
        &amp;lt;/RectButton&amp;gt;
        &amp;lt;Text style={styles.choseQuantityText}&amp;gt;{data.chosenQuantity}&amp;lt;/Text&amp;gt;
        &amp;lt;RectButton
          onPress={onIncreaseChosenQuantity.bind(null, data.id as string)}&amp;gt;
          &amp;lt;Icon name="plus" size={24} color="#3D7199" /&amp;gt;
        &amp;lt;/RectButton&amp;gt;
      &amp;lt;/View&amp;gt;
    &amp;lt;/View&amp;gt;
  );
};

export default CharacterCard;

const styles = StyleSheet.create({
  container: {
    width: '100%',
    borderRadius: 20,
    marginVertical: 8,
    paddingHorizontal: 8,
    paddingVertical: 24,
    backgroundColor: '#F0F0F0',
    flexDirection: 'row',
  },
  image: { width: 70, height: 70 },
  details: {
    marginLeft: 8,
    justifyContent: 'space-between',
    flex: 1,
  },
  text: {
    fontSize: 16,
    fontWeight: 'bold',
  },
  choseQuantityContainer: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'space-between',
    flexDirection: 'row',
  },
  choseQuantityText: {
    padding: 8,
    borderRadius: 8,
    backgroundColor: '#fff',
    fontSize: 16,
    fontWeight: 'bold',
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are just adding the &lt;code&gt;onPress&lt;/code&gt; event listener using our local api &lt;code&gt;useUpdateChosenQuantity&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating the cart screen
&lt;/h4&gt;

&lt;p&gt;Now that we have our shopping cart logic, we can build our cart screen (&lt;code&gt;src/screens/Cart.tsx&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useCallback } from 'react';
import { useNavigation } from '@react-navigation/native';
import { StyleSheet, Text, View, SafeAreaView, Button } from 'react-native';
import { useGetShoppingCartQuery } from '../common/generated/graphql';

const Cart = () =&amp;gt; {
  const navigation = useNavigation();
  const { data } = useGetShoppingCartQuery();

  const handleNavigation = useCallback(() =&amp;gt; {
    navigation.navigate('Home');
  }, [navigation]);

  return (
    &amp;lt;SafeAreaView style={styles.container}&amp;gt;
      {data?.shoppingCart?.numActionFigures ? (
        &amp;lt;&amp;gt;
          &amp;lt;View style={styles.content}&amp;gt;
            &amp;lt;Text style={styles.emoji}&amp;gt;🤗&amp;lt;/Text&amp;gt;
            &amp;lt;Text
              style={
                styles.subtitle
              }&amp;gt;{`Total number of items: ${data?.shoppingCart.numActionFigures}`}&amp;lt;/Text&amp;gt;
            &amp;lt;Text
              style={
                styles.subtitle
              }&amp;gt;{`Total price: U$ ${data?.shoppingCart.totalPrice}`}&amp;lt;/Text&amp;gt;
          &amp;lt;/View&amp;gt;
        &amp;lt;/&amp;gt;
      ) : (
        &amp;lt;&amp;gt;
          &amp;lt;View style={styles.content}&amp;gt;
            &amp;lt;Text style={styles.emoji}&amp;gt;😢&amp;lt;/Text&amp;gt;
            &amp;lt;Text style={styles.title}&amp;gt;Empty cart!&amp;lt;/Text&amp;gt;
            &amp;lt;View style={styles.footer}&amp;gt;
              &amp;lt;Button title="Go back to shop" onPress={handleNavigation} /&amp;gt;
            &amp;lt;/View&amp;gt;
          &amp;lt;/View&amp;gt;
        &amp;lt;/&amp;gt;
      )}
    &amp;lt;/SafeAreaView&amp;gt;
  );
};

export default Cart;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  content: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    width: '100%',
  },
  title: {
    fontSize: 24,
    marginTop: 15,
    lineHeight: 32,
    textAlign: 'center',
  },
  subtitle: {
    fontSize: 16,
    lineHeight: 32,
    marginTop: 8,
    textAlign: 'center',
    paddingHorizontal: 20,
  },
  emoji: {
    fontSize: 44,
    textAlign: 'center',
  },
  footer: {
    width: '100%',
    paddingHorizontal: 20,
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are just adding our Cart view, using the &lt;code&gt;shoppingCart&lt;/code&gt; query to printing the info. Finally we install the &lt;a href="https://reactnavigation.org/docs/bottom-tab-navigator/" rel="noopener noreferrer"&gt;react-native bottom tabs&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add @react-navigation/bottom-tabs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At &lt;em&gt;src/routes.tsx&lt;/em&gt; we write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { useGetShoppingCartQuery } from './common/generated/graphql';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';

import Home from './screens/Home';
import Cart from './screens/Cart';

const TabRoutes = createBottomTabNavigator();

const Routes: React.FC = () =&amp;gt; {
  const { data } = useGetShoppingCartQuery();

  return (
    &amp;lt;TabRoutes.Navigator
      tabBarOptions={{
        labelPosition: 'beside-icon',
        style: {
          height: 64,
          alignItems: 'center',
        },
      }}&amp;gt;
      &amp;lt;TabRoutes.Screen
        name="Home"
        component={Home}
        options={{
          tabBarIcon: ({ size, color }) =&amp;gt; (
            &amp;lt;MaterialIcons size={size * 1.2} color={color} name="home" /&amp;gt;
          ),
        }}
      /&amp;gt;
      &amp;lt;TabRoutes.Screen
        name="Cart"
        component={Cart}
        options={{
          tabBarIcon: ({ size, color }) =&amp;gt;
            data?.shoppingCart?.numActionFigures ? (
              &amp;lt;View style={styles.badgeIconView}&amp;gt;
                &amp;lt;Text style={styles.badge}&amp;gt;
                  {data?.shoppingCart?.numActionFigures}
                &amp;lt;/Text&amp;gt;
                &amp;lt;MaterialIcons
                  size={size * 1.2}
                  color={color}
                  name="shopping-cart"
                /&amp;gt;
              &amp;lt;/View&amp;gt;
            ) : (
              &amp;lt;MaterialIcons
                size={size * 1.2}
                color={color}
                name="shopping-cart"
              /&amp;gt;
            ),
        }}
      /&amp;gt;
    &amp;lt;/TabRoutes.Navigator&amp;gt;
  );
};
export default Routes;

const styles = StyleSheet.create({
  badgeIconView: {
    position: 'relative',
  },
  badge: {
    position: 'absolute',
    zIndex: 10,
    left: 24,
    bottom: 20,
    padding: 1,
    borderRadius: 20,
    fontSize: 14,
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then at &lt;em&gt;src/index.tsx&lt;/em&gt; we finally update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import { ApolloProvider } from '@apollo/client';
import { NavigationContainer } from '@react-navigation/native';
import { createApolloClient } from './common/config/apollo-client';

import Routes from './routes';

const apolloClient = createApolloClient();

const App = () =&amp;gt; {
  return (
    &amp;lt;ApolloProvider client={apolloClient}&amp;gt;
      &amp;lt;NavigationContainer&amp;gt;
        &amp;lt;Routes /&amp;gt;
      &amp;lt;/NavigationContainer&amp;gt;
    &amp;lt;/ApolloProvider&amp;gt;
  );
};

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If all goes well, when you run our app you should be able to increase and decrease the desired quantity of action figures and see the cart screen with the total price and the total number of action figures in the shopping cart. Finally, I'll be happy if you could provide me any feedback about the  code, structure, doubt or anything that could make me a better developer!&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>reactnative</category>
      <category>testing</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
