In this updated tutorial, you'll learn how to build a mobile app using React Native (2025 standards) and Apollo GraphQL v3+. The original version was based on class components and older Apollo client packages. This edition uses modern tools like React Hooks, the new Apollo Client, and a simplified GraphQL workflow.
By the end, you'll have a working mobile app that fetches and modifies data using GraphQL — cleanly and efficiently.
Prerequisites
-
Node.js 18+
-
React Native CLI or Expo CLI
-
Basic understanding of GraphQL
-
Access to a GraphQL server (or use a mock/public one)
Step 1: Create a New React Native Project
We’ll use React Native Community CLI for full control, but this also works with Expo.
npx @react-native-community/cli init RNGraphQL2025
cd RNGraphQL2025
The app or project folder will contain these folders and files.
├── App.tsx
├── Gemfile
├── README.md
├── __tests__
│ └── App.test.tsx
├── android
│ ├── app
│ │ ├── build.gradle
│ │ ├── debug.keystore
│ │ ├── proguard-rules.pro
│ │ └── src
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ └── rngraphql2025
│ │ │ ├── MainActivity.kt
│ │ │ └── MainApplication.kt
│ │ └── res
│ ├── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── app.json
├── babel.config.js
├── index.js
├── ios
│ ├── Podfile
│ ├── RNGraphQL2025
│ │ ├── AppDelegate.swift
│ │ ├── Images.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ ├── LaunchScreen.storyboard
│ │ └── PrivacyInfo.xcprivacy
│ └── RNGraphQL2025.xcodeproj
│ ├── project.pbxproj
│ └── xcshareddata
│ └── xcschemes
│ └── RNGraphQL2025.xcscheme
├── jest.config.js
├── metro.config.js
├── node_modules
├── package-lock.json
├── package.json
└── tsconfig.json
Step 2: Install Dependencies
Install Apollo Client and GraphQL packages:
npm install @apollo/client graphql
Optionally, install React Native's fetch
Polyfill if needed for older Android:
npm install whatwg-fetch
Step 3: Set up Apollo Client
Create a new folder src/apollo and a file inside it, client.js:
mkdir src
mkdir src/apollo
touch src/apollo/client.js
In src/apollo/client.js, add:
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
const client = new ApolloClient({
link: new HttpLink({
uri: 'https://your-graphql-api.com/graphql',
}),
cache: new InMemoryCache(),
});
export default client;
Step 4: Wrap Your App with ApolloProvider
In App.tsx:
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import client from './src/apollo/client';
import MainScreen from './src/screens/MainScreen';
export default function App() {
return (
<ApolloProvider client={client}>
<MainScreen />
</ApolloProvider>
);
}
Step 5: Query Data Using useQuery
Create a new folder src/screens and a file MainScreen.tsx:
mkdir src/screens
touch src/screens/MainScreen.js
Example Query: Get List of Posts
src/screens/MainScreen.js
import React from 'react';
import { Text, View, FlatList, ActivityIndicator } from 'react-native';
import { gql, useQuery } from '@apollo/client';
const GET_POSTS = gql`
query {
posts {
id
title
content
}
}
`;
const MainScreen = () => {
const { loading, error, data } = useQuery(GET_POSTS);
if (loading) return <ActivityIndicator />;
if (error) return <Text>Error: {error.message}</Text>;
return (
<FlatList
data={data.posts}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={{ padding: 10 }}>
<Text style={{ fontWeight: 'bold' }}>{item.title}</Text>
<Text>{item.content}</Text>
</View>
)}
/>
);
};
export default MainScreen;
Step 6: Mutate Data with useMutation
Create a new src/components folder and a file CreatePost.js:
mkdir src/components
touch src/components/CreatePost.js
In src/components/CreatePost.js, add:
import React, { useState } from 'react';
import { Button, TextInput, View } from 'react-native';
import { gql, useMutation } from '@apollo/client';
const ADD_POST = gql`
mutation AddPost($title: String!, $content: String!) {
createPost(title: $title, content: $content) {
id
title
}
}
`;
const CreatePost = () => {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [addPost, { data, loading }] = useMutation(ADD_POST);
return (
<View>
<TextInput placeholder="Title" value={title} onChangeText={setTitle} />
<TextInput placeholder="Content" value={content} onChangeText={setContent} multiline />
<Button
title={loading ? 'Saving...' : 'Create Post'}
onPress={() => addPost({ variables: { title, content } })}
/>
</View>
);
};
export default CreatePost;
Step 7: Install and Set up React Navigation
npm install @react-navigation/native
npm install react-native-screens react-native-safe-area-context react-native-gesture-handler
npm install @react-navigation/native-stack
Also, follow the official React Navigation setup to wrap the app properly.
Update App.tsx:
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import client from './src/apollo/client';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './src/screens/HomeScreen';
import PostDetail from './src/screens/PostDetail';
import CreatePost from './src/components/CreatePost';
import EditPost from './src/screens/EditPost';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<ApolloProvider client={client}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="PostDetail" component={PostDetail} />
<Stack.Screen name="CreatePost" component={CreatePost} />
<Stack.Screen name="EditPost" component={EditPost} />
</Stack.Navigator>
</NavigationContainer>
</ApolloProvider>
);
}
Step 8: Complete CRUD Screens
In src/screens/HomeScreen.js, add:
import React from 'react';
import { View, Text, FlatList, Button, TouchableOpacity } from 'react-native';
import { gql, useQuery } from '@apollo/client';
const GET_POSTS = gql`
query {
posts {
id
title
}
}
`;
export default function HomeScreen({ navigation }) {
const { data, loading, error } = useQuery(GET_POSTS);
if (loading) return <Text>Loading...</Text>;
if (error) return <Text>Error: {error.message}</Text>;
return (
<View>
<Button title="Create Post" onPress={() => navigation.navigate('CreatePost')} />
<FlatList
data={data.posts}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<TouchableOpacity onPress={() => navigation.navigate('PostDetail', { id: item.id })}>
<View style={{ padding: 10 }}>
<Text style={{ fontWeight: 'bold' }}>{item.title}</Text>
</View>
</TouchableOpacity>
)}
/>
</View>
);
}
In src/screens/PostDetail.js, add:
import React from 'react';
import { View, Text, Button } from 'react-native';
import { gql, useQuery, useMutation } from '@apollo/client';
const GET_POST = gql`
query GetPost($id: ID!) {
post(id: $id) {
id
title
content
}
}
`;
const DELETE_POST = gql`
mutation DeletePost($id: ID!) {
deletePost(id: $id)
}
`;
export default function PostDetail({ route, navigation }) {
const { id } = route.params;
const { data, loading, error } = useQuery(GET_POST, { variables: { id } });
const [deletePost] = useMutation(DELETE_POST, {
onCompleted: () => navigation.goBack(),
});
if (loading) return <Text>Loading...</Text>;
if (error) return <Text>Error: {error.message}</Text>;
const { title, content } = data.post;
return (
<View style={{ padding: 10 }}>
<Text style={{ fontSize: 24, fontWeight: 'bold' }}>{title}</Text>
<Text style={{ marginTop: 10 }}>{content}</Text>
<Button title="Edit" onPress={() => navigation.navigate('EditPost', { id })} />
<Button title="Delete" color="red" onPress={() => deletePost({ variables: { id } })} />
</View>
);
}
In src/screens/EditPost.js, add:
import React, { useState, useEffect } from 'react';
import { View, TextInput, Button } from 'react-native';
import { gql, useQuery, useMutation } from '@apollo/client';
const GET_POST = gql`
query GetPost($id: ID!) {
post(id: $id) {
id
title
content
}
}
`;
const UPDATE_POST = gql`
mutation UpdatePost($id: ID!, $title: String!, $content: String!) {
updatePost(id: $id, title: $title, content: $content) {
id
}
}
`;
export default function EditPost({ route, navigation }) {
const { id } = route.params;
const { data } = useQuery(GET_POST, { variables: { id } });
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
useEffect(() => {
if (data?.post) {
setTitle(data.post.title);
setContent(data.post.content);
}
}, [data]);
const [updatePost] = useMutation(UPDATE_POST, {
onCompleted: () => navigation.navigate('PostDetail', { id }),
});
return (
<View style={{ padding: 10 }}>
<TextInput placeholder="Title" value={title} onChangeText={setTitle} />
<TextInput placeholder="Content" value={content} onChangeText={setContent} multiline />
<Button title="Update" onPress={() => updatePost({ variables: { id, title, content } })} />
</View>
);
}
Now your app supports full CRUD operations with a clean UI navigation flow using Apollo Client v3 and React Native!
Conclusion
In this 2025 edition of the React Native and Apollo GraphQL tutorial, you’ve built a fully functional mobile app using the latest tools and best practices. With React Native Hooks and Apollo Client v3, you've implemented a complete CRUD flow:
-
🟢 Create new posts using GraphQL mutations
-
🔵 Read posts with
useQuery
, including fetching by ID -
🟠 Update existing posts through a simple edit form
-
🔴 Delete posts with confirmation and clean navigation
You’ve also integrated React Navigation to create a seamless multi-screen experience, setting up a scalable foundation for real-world apps.
Apollo Client continues to simplify GraphQL integration while React Native remains a powerful tool for building performant mobile applications.
You can find the full source code on our GitHub.
That's just the basics. If you need more deep learning about React.js, React Native, or related, you can take the following cheap course:
- Master React Native Animations
- Advanced React Native Topics
- React Native
- Learning React Native Development
- React: React Native Mobile Development: 3-in-1
Thanks!