How To Post And Fetch Data Using RTK-Query In React-native

Big Zude
9 min readApr 20, 2023
Big Zude smiling

Introduction

In this article, I’ll teach you how to use RTK Query to fetch and post data in a React Native Expo TypeScript project. We’ll use the JSONPlaceholder API as an example, which provides a REST API for testing and prototyping. At the end of this article, you’ll have a good understanding of how RTK Query works and how to use it to handle API calls in your own React Native Expo apps. Now let’s get started!

Set up a new Expo TypeScript app with RTK Query

Let’s create a new Expo React-Native typescript project with the command below:

Select a blank typescript project when the options appear

npx create-expo-app --template

Install Redux Toolkit

yarn add @reduxjs/toolkit

Run the project on the emulator of your choice

yarn start
running app

Creating API endpoints with RTK-Query

  1. Import createApi and fetchBaseQuery from the ‘@reduxjs/toolkit/query/react’
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

2. Define a new constant named “api” that uses the createApi function to create a new API instance. The createApi function takes an object as its argument with three properties:

  • reducerPath: A string that specifies the name of the slice of the Redux store where the API state will be stored.
  • baseQuery: A function that returns a base configuration object used for every request. The fetchBaseQuery function is a utility function that creates a default base query object with a baseUrl property.
  • endpoints: A function that takes a builder object and returns an object containing all the query and mutation endpoints for your API. In this example, it defines two endpoints: getUsers and createUser. builder.query is a way to define a query endpoint with RTK Query. You define the endpoint’s URL and HTTP method, and RTK Query generates a hook function that you can use to call the endpoint and get the data.
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: 'https://jsonplaceholder.typicode.com' }),
endpoints: (builder) => ({
getUsers: builder.query({
query: () => ({
url: '/users/',
method: 'Get',
}),
}),
createUser: builder.mutation({
query: (body) => ({
url: '/users',
method: 'POST',
body,
}),
}),
}),
});

3. Export two hooks, useGetUsersQuery and useCreateUserMutation, from the api object created in step 2. These hooks are generated automatically by RTK Query based on the endpoints defined in the endpoints property. You can use these hooks to call the getUsers and createUser endpoints respectively.

export const {useGetUsersQuery,useCreateUserMutation} = api

With this API slice set up, we can now use RTK Query to fetch and submit data from our React Native Expo app.

will use ApiProvider to wrap the components in App.tsx; see the code snippet below:

import { ApiProvider } from '@reduxjs/toolkit/dist/query/react';
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
import { api } from './apiSlice';
import { UserList } from './GetUsers';

function AppWrapper() {
return (
<View style={styles.container}>
<StatusBar style="auto" />
<UserList />
</View>
);
}

export default function App(){
return (
<ApiProvider api={api}>
<AppWrapper/>
</ApiProvider>
)
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});

If you app has got a redux store, Add the apiSlice.reducer to your store:

import { configureStore } from '@reduxjs/toolkit';
import { api } from './apiSlice';
import { setupListeners } from '@reduxjs/toolkit/query';

export const store = configureStore({
reducer: {
api: apiSlice.reducer,
// Add your other reducers here
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}).concat(api.middleware),

setupListeners(store.dispatch);

export default store;

The setupListeners function listens for certain events that happen when you use RTK-Query, and it needs access to the “dispatch” method of your Redux store to work. It’s like setting up a pair of ears to hear what’s going on with RTK-Query.

To use the setupListeners function, you can add it to your code alongside the createApi function in the apiSlice file. For example, you can tell your app to automatically fetch new data when the app regains focus or reconnects to the internet by adding “refetchOnFocus: true” and “refetchOnReconnect: true” to the createApi function. see the code below for context

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: 'https://jsonplaceholder.typicode.com' }),
refetchOnFocus: true,
refetchOnReconnect: true,
endpoints: (builder) => ({
getUsers: builder.query({
query: () => ({
url: '/users/',
method: 'Get',
}),
}),
createUser: builder.mutation({
query: (body) => ({
url: '/users',
method: 'POST',
body,
}),
}),
}),
});

Automatic Re-fetching
RTK-Query has a feature called “cache tag” system, which automatically re-fetches query endpoints that have data that could be affected by mutation endpoints. This means that you can set up your API so that firing a particular mutation will cause a specific query endpoint to consider its cached data invalid. Then, it will re-fetch the data if there is an active subscription. This helps ensure that your app always displays the most up-to-date information.

Tags
RTK-Query’s tagTypes help group query and mutation results under a specific tag. For example, if you have a query and a mutation related to “users”, you can group them under a tag called “users”. This way, you can easily invalidate or refetch all queries/mutations related to “users” at once if necessary.

tagTypes: ['users']

Provide Tags
providesTags is a function that returns an array of tags for a specific query or mutation result. This is used to associate a query/mutation result with one or more tags. For instance, if you have a query that fetches a single user by ID, you can associate the result with the “users” tag using providesTags.

providesTags: ['users']

Invalidate Tags

invalidatesTags is a function that returns an array of tags that should be invalidated when a specific query or mutation is executed. This is used to specify which queries/mutations should be invalidated when a particular action is taken. For example, if you have a mutation that adds a new user, you can invalidate all queries/mutations related to “users” using invalidatesTags.

invalidatesTags: ['users']

Here’s an example of how to use these features in RTK-Query:

const api = createApi({
tagTypes: ['users'], // Declare a tag type to group related queries/mutations
endpoints: (builder) => ({
getUsers: builder.query({
query: () => '/users',
providesTags: ['users'], // associate the result of this mutation with the "users" tag
}),
createUser: builder.mutation({
query: (data) => ({
url: '/users',
method: 'POST',
body: data,
}),
invalidatesTags: ['users'], // invalidate all queries/mutations with the "users" tag when this mutation is executed
}),
}),
});

Fetching data using RTK-Query

Now that we have our API slice set up, let’s use it to fetch data from JSONPlaceholder in our React Native Expo app.

  1. Import necessary React Native components and the useGetUsersQuery hook from apiSlice.
import React from 'react';
import {
Text,
FlatList,
ActivityIndicator,
StyleSheet,
View,
SafeAreaView
} from 'react-native';
import { useGetUsersQuery } from './apiSlice';

2. Define a functional component called UserList.

export const UserList = () => {}

3. Call the useGetUsersQuery hook to fetch data and track loading state.

    const { data, isLoading } = useGetUsersQuery();

4. Check if data is still loading, and display an activity indicator if it is.

    if (isLoading) {
return <ActivityIndicator />;
}

5. If data has loaded, render a FlatList component that displays the user data.

    return (
<SafeAreaView>
<FlatList
data={data}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={styles.itemContainer}>
<Text>id: {item.id}</Text>
<Text>name: {item.name}</Text>
<Text>phone: {item.phone}</Text>
<Text>email: {item.email}</Text>
</View>)}
/>
</SafeAreaView>
);

6. Define the styles for the UserList component.

const styles = StyleSheet.create({
container: {
flex: 1,
},
itemContainer: {
backgroundColor: '#fff',
padding: 5,
paddingHorizontal: 18,
marginTop: 5,
borderWidth: 1,
borderRadius: 10,
alignItems: 'center',
gap: 2
},
});

Below is the entire code put together

import React from 'react';
//import { useGetTodosQuery } from './apiSlice';
import {
Text,
FlatList,
ActivityIndicator,
StyleSheet,
View,
SafeAreaView
} from 'react-native';
import { useGetUsersQuery } from './apiSlice';

export const UserList = () => {
const { data, isLoading } = useGetUsersQuery();

if (isLoading) {
return <ActivityIndicator />;
}

return (
<SafeAreaView>
<FlatList
data={data}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={styles.itemContainer}>
<Text>id: {item.id}</Text>
<Text>name: {item.name}</Text>
<Text>phone: {item.phone}</Text>
<Text>email: {item.email}</Text>
</View>)}
/>
</SafeAreaView>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
},
itemContainer: {
backgroundColor: '#fff',
padding: 5,
paddingHorizontal: 18,
marginTop: 5,
borderWidth: 1,
borderRadius: 10,
alignItems: 'center',
gap: 2
},

});

Here’s the output:

output

This is just a basic example, but RTK Query provides many more features for fetching data, including caching, polling, and optimistic updates. Be sure to check out the RTK Query documentation for more information.

Now let’s move on to using RTK Query to submit data.

Posting data using RTK-Query

Posting data with RTK Query is just as easy as fetching it. Let’s create a generic modal, buttons and a user form.

  1. Import necessary React Native components and the useCreateUserMutation hook from apiSlice.
import React, { useState } from 'react';
import {
Modal,
TextInput,
Button,
StyleSheet,
View,
Alert } from 'react-native';
import { useCreateUserMutation } from './apiSlice';

2. Define a functional component called CreateUser. It takes two props, visible and onClose, which we will use to control the visibility of the modal and to close the modal respectively.

export const CreateUser = ({ visible, onClose }) => {
// component logic here
};

3. We define some state using the useState hook. The useState hook returns an array containing two values: the current value of the state and a function to update the state. For example, setName is a function that updates the name state when called.

const [name, setName] = useState('');
const [phone, setPhone] = useState('');
const [email, setEmail] = useState('');
const [id, setId] = useState('');
const [address, setAddress] = useState('');
const [website, setWebsite] = useState('');
const [company, setCompany] = useState('');

4. Next, we have a line that uses the useCreateUserMutation hook we imported earlier:

const [createUser] = useCreateUserMutation()

This line calls the useCreateUserMutation hook and assigns the first element of the returned array to a constant named createUser. We can use this function to send a request to create a new user using our API.

5. We define a function named handleSubmit which handles form submission

const handleSubmit = () => {
const formData = {
name,
phone,
email,
id
}
createUser(formData)
.unwrap()
.then(() => {
Alert.alert("User created");
setName('');
setPhone('')
setEmail('')
setId('')
setAddress('')
setCompany('')
setWebsite('')
})
.catch(() =>
Alert.alert("there was an error"),
);
onClose();
setName('');
setPhone('')
setEmail('')
setId('')
setAddress('')
setCompany('')
setWebsite('')
};

This code creates a function that is called when the user submits a form. It gets the values of the form and sends them to the server to create a new user. If there are no errors, an alert message appears saying that the user was created. If there are errors, another alert message appears saying that there was an error. The function then closes the form and resets the values of the form fields.

The code uses a special function called “unwrap” to handle the response/error from the server inline. If the response is successful, the “then” block is executed and the user is notified that the user was created. If there is an error, the “catch” block is executed and the user is notified that there was an error. This makes it easier for developers to handle responses from the server in a concise and readable way.

6. Return a form in modal

return (
<Modal visible={visible} animationType="slide">
<View style={styles.modalContainer}>
<TextInput
placeholder="Id"
value={id}
onChangeText={setId}
style={styles.input}
/>
<TextInput
placeholder="Name"
value={name}
onChangeText={setName}
style={styles.input}
/>
<TextInput
placeholder="Phone"
value={phone}
onChangeText={setPhone}
style={styles.input}
/>
<TextInput
placeholder="Email"
value={email}
onChangeText={setEmail}
style={styles.input}
/>
<TextInput
placeholder="Address"
value={address}
onChangeText={setAddress}
style={styles.input}
/>
<TextInput
placeholder="Website"
value={website}
onChangeText={setWebsite}
style={styles.input}
/>
<TextInput
placeholder="Company"
value={company}
onChangeText={setCompany}
style={styles.input}
/>
<Button title="Submit" onPress={handleSubmit} />
<Button title="Cancel" onPress={onClose} />
</View>
</Modal>
);

Below is the complete code for CreatUser component

import React, { useState } from 'react';
import { Modal, TextInput, Button, StyleSheet, View, Alert } from 'react-native';
import { useCreateUserMutation } from './apiSlice';

export const CreateUser = ({ visible, onClose }) => {
const [name, setName] = useState('');
const [phone, setPhone] = useState('');
const [email, setEmail] = useState('');
const [id, setId] = useState('');
const [address, setAddress] = useState('');
const [website, setWebsite] = useState('');
const [company, setCompany] = useState('');
const [createUser] = useCreateUserMutation()

const handleSubmit = () => {
const formData = {
name,
phone,
email,
id
}
createUser(formData)
.unwrap()
.then(() => {
Alert.alert("User created");
setName('');
setPhone('')
setEmail('')
setId('')
setAddress('')
setCompany('')
setWebsite('')
})
.catch(() =>
Alert.alert("there was an error"),
);
onClose();
setName('');
setPhone('')
setEmail('')
setId('')
setAddress('')
setCompany('')
setWebsite('')
};

return (
<Modal visible={visible} animationType="slide">
<View style={styles.modalContainer}>
<TextInput
placeholder="Id"
value={id}
onChangeText={setId}
style={styles.input}
/>
<TextInput
placeholder="Name"
value={name}
onChangeText={setName}
style={styles.input}
/>
<TextInput
placeholder="Phone"
value={phone}
onChangeText={setPhone}
style={styles.input}
/>
<TextInput
placeholder="Email"
value={email}
onChangeText={setEmail}
style={styles.input}
/>
<TextInput
placeholder="Address"
value={address}
onChangeText={setAddress}
style={styles.input}
/>
<TextInput
placeholder="Website"
value={website}
onChangeText={setWebsite}
style={styles.input}
/>
<TextInput
placeholder="Company"
value={company}
onChangeText={setCompany}
style={styles.input}
/>
<Button title="Submit" onPress={handleSubmit} />
<Button title="Cancel" onPress={onClose} />
</View>
</Modal>
);
};

const styles = StyleSheet.create({
modalContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
input: {
borderWidth: 1,
borderRadius: 5,
padding: 10,
marginVertical: 5,
width: '80%',
},
});

That’s it! With just a few lines of code, we can now fetch and submit data to our API using RTK Query in React native expo App.

Conclusion

I hope you found this helpful! Here is the git repo, If you have any questions or feedback, leave a comment below.

--

--

Big Zude

React Frontend Engineer | Mobile Developer | UI Designer