DEV Community

Cover image for Integrating React Native with GraphQL: A Comprehensive Guide
Paulo Messias
Paulo Messias

Posted on

Integrating React Native with GraphQL: A Comprehensive Guide

Heys devs!

React Native is a powerful tool for developing cross-platform mobile applications, while GraphQL offers a flexible and efficient approach to consuming APIs. Together, they can make app development faster and less error-prone. In this post, we will explore how to set up and use GraphQL in a React Native application with TypeScript, including installation, code examples (queries and mutations), tests, and best practices.

Installation

1. Setting Up the Environment

First, ensure you have your React Native development environment set up. If you haven't done this yet, follow the instructions in the official documentation to configure the React Native CLI.

2. Creating a New React Native Project

Create a new React Native project using the following command:

npx react-native init MyGraphQLApp --template react-native-template-typescript
cd MyGraphQLApp
Enter fullscreen mode Exit fullscreen mode

3. Installing Necessary Dependencies

To use GraphQL with React Native, we'll need some additional libraries. The main one is Apollo Client, a popular GraphQL client library for JavaScript.

Install Apollo Client and other necessary dependencies:

npm install @apollo/client graphql
Enter fullscreen mode Exit fullscreen mode

Setting Up Apollo Client

1. Configuring Apollo Client

Create a file named ApolloClient.ts in your project's src folder:

// src/ApolloClient.ts

import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({
  uri: 'https://your-graphql-endpoint.com/graphql',
});

const authLink = setContext((_, { headers }) => {
  const token = 'your-auth-token'; // Replace with your authentication token
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : '',
    }
  };
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});

export default client;
Enter fullscreen mode Exit fullscreen mode

2. Setting Up ApolloProvider

In the App.tsx file, configure ApolloProvider to provide the Apollo client to the entire application:

// App.tsx

import React from 'react';
import { ApolloProvider } from '@apollo/client';
import client from './src/ApolloClient';
import HomeScreen from './src/HomeScreen';

const App: React.FC = () => {
  return (
    <ApolloProvider client={client}>
      <HomeScreen />
    </ApolloProvider>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Consuming Data with GraphQL

1. Writing a GraphQL Query

Create a queries.ts file in the src folder to store your queries:

// src/queries.ts

import { gql } from '@apollo/client';

export const GET_DATA = gql`
  query GetData {
    data {
      id
      name
      description
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

2. Using the Query in a Component

In your HomeScreen.tsx component, use the GraphQL query with Apollo's useQuery hook:

// src/HomeScreen.tsx

import React from 'react';
import { View, Text, ActivityIndicator, FlatList } from 'react-native';
import { useQuery } from '@apollo/client';
import { GET_DATA } from './queries';

interface DataItem {
  id: string;
  name: string;
  description: string;
}

interface GetDataResult {
  data: DataItem[];
}

const HomeScreen: React.FC = () => {
  const { loading, error, data } = useQuery<GetDataResult>(GET_DATA);

  if (loading) return <ActivityIndicator testID="loading" size="large" color="#0000ff" />;
  if (error) return <Text>Error: {error.message}</Text>;

  return (
    <View>
      <FlatList
        data={data?.data}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <View>
            <Text>{item.name}</Text>
            <Text>{item.description}</Text>
          </View>
        )}
      />
    </View>
  );
};

export default HomeScreen;
Enter fullscreen mode Exit fullscreen mode

Executing Mutations with GraphQL

1. Writing a GraphQL Mutation

Create a mutations.ts file in the src folder to store your mutations:

// src/mutations.ts

import { gql } from '@apollo/client';

export const ADD_DATA = gql`
  mutation AddData($name: String!, $description: String!) {
    addData(name: $name, description: $description) {
      id
      name
      description
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

2. Using the Mutation in a Component

In your HomeScreen.tsx component, use the GraphQL mutation with Apollo's useMutation hook:

// src/HomeScreen.tsx

import React, { useState } from 'react';
import { View, Text, TextInput, Button, ActivityIndicator, FlatList } from 'react-native';
import { useQuery, useMutation } from '@apollo/client';
import { GET_DATA } from './queries';
import { ADD_DATA } from './mutations';

interface DataItem {
  id: string;
  name: string;
  description: string;
}

interface GetDataResult {
  data: DataItem[];
}

interface AddDataVars {
  name: string;
  description: string;
}

const HomeScreen: React.FC = () => {
  const { loading, error, data } = useQuery<GetDataResult>(GET_DATA);
  const [addData] = useMutation<DataItem, AddDataVars>(ADD_DATA);

  const [name, setName] = useState('');
  const [description, setDescription] = useState('');

  const handleAddData = () => {
    addData({ 
      variables: { name, description },
      refetchQueries: [{ query: GET_DATA }]
    });
  };

  if (loading) return <ActivityIndicator testID="loading" size="large" color="#0000ff" />;
  if (error) return <Text>Error: {error.message}</Text>;

  return (
    <View>
      <TextInput 
        placeholder="Name"
        value={name}
        onChangeText={setName}
        testID="name-input"
      />
      <TextInput 
        placeholder="Description"
        value={description}
        onChangeText={setDescription}
        testID="description-input"
      />
      <Button title="Add Data" onPress={handleAddData} testID="add-button" />

      <FlatList
        data={data?.data}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <View>
            <Text>{item.name}</Text>
            <Text>{item.description}</Text>
          </View>
        )}
      />
    </View>
  );
};

export default HomeScreen;
Enter fullscreen mode Exit fullscreen mode

Setting Up Tests

1. Installing Test Dependencies

Let's install the necessary libraries for testing. We will use jest, react-native-testing-library, and @testing-library/react-native.

npm install --save-dev jest @testing-library/react-native @testing-library/jest-native @types/jest
Enter fullscreen mode Exit fullscreen mode

2. Configuring Jest

Add the Jest configuration to your package.json:

"jest": {
  "preset": "react-native",
  "setupFilesAfterEnv": [
    "@testing-library/jest-native/extend-expect"
  ],
  "transformIgnorePatterns": [
    "node_modules/(?!(jest-)?react-native|@react-native|@react-native-community|@testing-library|@react-navigation)"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Writing Tests

1. Testing Apollo Client

We'll create a mock of the Apollo Client to use in our tests. Create a file called ApolloMockProvider.tsx in the src/test folder:

// src/test/ApolloMockProvider.tsx

import React, { ReactNode } from 'react';
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
import { MockedProvider } from '@apollo/client/testing';

interface Props {
  children: ReactNode;
  mocks: any[];
}

const ApolloMockProvider: React.FC<Props> = ({ children, mocks }) => {
  const client = new ApolloClient({
    cache: new InMemoryCache(),
  });

  return (
    <MockedProvider mocks={mocks} addTypename={false}>
      <ApolloProvider client={client}>
        {children}
      </ApolloProvider>
    </MockedProvider>
  );
};

export default ApolloMockProvider;
Enter fullscreen mode Exit fullscreen mode

2. Testing the HomeScreen Component

Create a test file called HomeScreen.test.tsx in the src/__tests__ folder:

// src/__tests__/HomeScreen.test.tsx

import React from 'react';
import { render, waitFor, fireEvent } from '@testing-library/react-native';
import HomeScreen from '../HomeScreen';
import ApolloMockProvider from '../test/ApolloMockProvider';
import { GET_DATA, ADD_DATA } from '../queries';
import { MockedResponse } from '@apollo/client/testing';

const mocks: MockedResponse[] = [
  {
    request: {
      query: GET_DATA,
    },
    result: {
      data: {
        data: [
          { id: '1', name: 'Test Name', description:
 'Test Description' },
        ],
      },
    },
  },
  {
    request: {
      query: ADD_DATA,
      variables: {
        name: 'New Name',
        description: 'New Description',
      },
    },
    result: {
      data: {
        addData: {
          id: '2',
          name: 'New Name',
          description: 'New Description',
        },
      },
    },
  },
];

describe('HomeScreen', () => {
  it('renders loading state initially', () => {
    const { getByTestId } = render(
      <ApolloMockProvider mocks={[]}>
        <HomeScreen />
      </ApolloMockProvider>
    );
    expect(getByTestId('loading')).toBeTruthy();
  });

  it('renders data correctly', async () => {
    const { getByText } = render(
      <ApolloMockProvider mocks={mocks}>
        <HomeScreen />
      </ApolloMockProvider>
    );

    await waitFor(() => {
      expect(getByText('Test Name')).toBeTruthy();
      expect(getByText('Test Description')).toBeTruthy();
    });
  });

  it('adds new data correctly', async () => {
    const { getByPlaceholderText, getByText } = render(
      <ApolloMockProvider mocks={mocks}>
        <HomeScreen />
      </ApolloMockProvider>
    );

    const nameInput = getByPlaceholderText('Name');
    const descriptionInput = getByPlaceholderText('Description');
    const addButton = getByText('Add Data');

    fireEvent.changeText(nameInput, 'New Name');
    fireEvent.changeText(descriptionInput, 'New Description');
    fireEvent.press(addButton);

    await waitFor(() => {
      expect(getByText('New Name')).toBeTruthy();
      expect(getByText('New Description')).toBeTruthy();
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

Updating the HomeScreen Component

Add test identifiers to the HomeScreen.tsx component to make it easier to select elements during tests:

// src/HomeScreen.tsx

import React, { useState } from 'react';
import { View, Text, TextInput, Button, ActivityIndicator, FlatList } from 'react-native';
import { useQuery, useMutation } from '@apollo/client';
import { GET_DATA } from './queries';
import { ADD_DATA } from './mutations';

interface DataItem {
  id: string;
  name: string;
  description: string;
}

interface GetDataResult {
  data: DataItem[];
}

interface AddDataVars {
  name: string;
  description: string;
}

const HomeScreen: React.FC = () => {
  const { loading, error, data } = useQuery<GetDataResult>(GET_DATA);
  const [addData] = useMutation<DataItem, AddDataVars>(ADD_DATA);

  const [name, setName] = useState('');
  const [description, setDescription] = useState('');

  const handleAddData = () => {
    addData({ 
      variables: { name, description },
      refetchQueries: [{ query: GET_DATA }]
    });
  };

  if (loading) return <ActivityIndicator testID="loading" size="large" color="#0000ff" />;
  if (error) return <Text>Error: {error.message}</Text>;

  return (
    <View>
      <TextInput 
        placeholder="Name"
        value={name}
        onChangeText={setName}
        testID="name-input"
      />
      <TextInput 
        placeholder="Description"
        value={description}
        onChangeText={setDescription}
        testID="description-input"
      />
      <Button title="Add Data" onPress={handleAddData} testID="add-button" />

      <FlatList
        data={data?.data}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <View>
            <Text>{item.name}</Text>
            <Text>{item.description}</Text>
          </View>
        )}
      />
    </View>
  );
};

export default HomeScreen;
Enter fullscreen mode Exit fullscreen mode

Running the Tests

Now, you can run the tests using the command:

npm test
Enter fullscreen mode Exit fullscreen mode

Conclusion

Adding tests to your React Native project with GraphQL and TypeScript is a crucial step to ensure the quality and robustness of your application. With the provided configurations and examples, you are well on your way to creating a comprehensive test suite for your application.

Top comments (1)

Collapse
 
mensonones profile image
Emerson Vieira

excelente! parabens!