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
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
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;
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;
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
}
}
`;
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;
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
}
}
`;
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;
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
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)"
]
}
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;
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();
});
});
});
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;
Running the Tests
Now, you can run the tests using the command:
npm test
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.
Source link
lol