Mehedi Hassan Piash | Senior Software Engineer | Android | iOS | KMP | Ktor | Jetpack Compose | React-Native.

September 28, 2024

RTK Query example code in react-native

September 28, 2024 Posted by Piash , , , No comments

RTK Query is one of the best ways to handle data fetching efficiently, a data-fetching and caching tool that comes with the Redux Toolkit. In this article, we’ll walk through an example of using RTK Query in a React Native application.

We’ll demonstrate how to set up RTK Query to fetch movie data from TheMovieDB API and display it in your React Native app.

Movie with RTK Query

Prerequisites

Before we dive in, ensure you have the following set up:
• A React Native project
• Redux Toolkit installed
• RTK Query integrated into your Redux setup
• TheMovieDB API Key (Sign up at themoviedb.org)

Step 1: Install Dependencies
First, you need to install the required dependencies if you haven’t already:

npm install @reduxjs/toolkit react-redux
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import {Constants} from "../../appconstants/AppConstants.ts";
import {MovieResult} from "../../types/MovieResult.ts";
import {MovieItem} from "../../types/MovieItem.ts";
import {MovieDetail} from "../../types/MovieDetail.ts";
import {CastAndCrew} from "../../types/ArtistAndCrew.ts";
import {ArtistDetail} from "../../types/ArtistDetail.ts";

export const nowPlayingMovieApi = createApi({
reducerPath: 'nowPlayingMovieApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://api.themoviedb.org/3/' }),
endpoints: (builder) => ({
getNowPlayingMovie: builder.query<MovieItem[], number>({
query: (page) => `movie/now_playing?api_key=${Constants.API_KEY}&language=en-US?page=${page}`,
transformResponse: (response: MovieResult) => response.results
}),
}),
})

export const { useGetNowPlayingMovieQuery } = nowPlayingMovieApi;

export const popularMovieApi = createApi({
reducerPath: 'popularMovieApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://api.themoviedb.org/3/' }),
endpoints: (builder) => ({
getPopularMovie: builder.query<MovieItem[], number>({
query: (page ) => `movie/popular?api_key=${Constants.API_KEY}&language=en-US?page=${page}`,
transformResponse: (response: MovieResult) => response.results
}),
}),
})


export const {useGetPopularMovieQuery} = popularMovieApi;

export const topRatedMovieApi = createApi({
reducerPath: 'topRatedMovieApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://api.themoviedb.org/3/' }),
endpoints: (builder) => ({
getTopRatedMovie: builder.query<MovieItem[], number>({
query: (page) => `movie/top_rated?api_key=${Constants.API_KEY}&language=en-US?page=${page}`,
transformResponse: (response: MovieResult) => response.results
}),
}),
})
export const {useGetTopRatedMovieQuery} = topRatedMovieApi;


export const upcomingMovieApi = createApi({
reducerPath: 'upcomingMovieApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://api.themoviedb.org/3/' }),
endpoints: (builder) => ({
getUpcomingMovie: builder.query<MovieItem[], number>({
query: (page) => `movie/upcoming?api_key=${Constants.API_KEY}&language=en-US?page=${page}`,
transformResponse: (response: MovieResult) => response.results
}),
}),
})

export const {useGetUpcomingMovieQuery} = upcomingMovieApi;

export const movieDetailApi = createApi({
reducerPath: 'movieDetailApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://api.themoviedb.org/3/' }),
endpoints: (builder) => ({
getMovieDetail: builder.query<MovieDetail, number>({
query: (movieId) => `movie/${movieId}?api_key=${Constants.API_KEY}&language=en-US`,
transformResponse: (response: MovieDetail) => response
}),
}),
})

export const {useGetMovieDetailQuery} = movieDetailApi;


export const similarMovieApi = createApi({
reducerPath: 'similarMovieApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://api.themoviedb.org/3/' }),
endpoints: (builder) => ({
getSimilarMovie: builder.query<MovieItem[], number>({
query: (movieId) => `movie/${movieId}/recommendations?api_key=${Constants.API_KEY}&language=en-US`,
transformResponse: (response: MovieResult) => response.results
}),
}),
})
export const {useGetSimilarMovieQuery} = similarMovieApi;

export const artistAndCrewApi = createApi({
reducerPath: 'artistAndCrewApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://api.themoviedb.org/3/' }),
endpoints: (builder) => ({
getArtistAndCrew: builder.query<CastAndCrew, number>({
query: (movieId) => `movie/${movieId}/credits?api_key=${Constants.API_KEY}&language=en-US`,
transformResponse: (response: CastAndCrew) => response
}),
}),
})

export const {useGetArtistAndCrewQuery} = artistAndCrewApi;

export const artistDetailApi = createApi({
reducerPath: 'artistDetailApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://api.themoviedb.org/3/' }),
endpoints: (builder) => ({
getAristDetail: builder.query<ArtistDetail, number>({
query: (personId) => `person/${personId}?api_key=${Constants.API_KEY}&language=en-US`,
transformResponse: (response: ArtistDetail) => response
}),
}),
})

export const {useGetAristDetailQuery} = artistDetailApi;
import {configureStore} from '@reduxjs/toolkit'
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas';
import logger from 'redux-logger';
import {
artistAndCrewApi,
artistDetailApi,
movieDetailApi,
nowPlayingMovieApi,
popularMovieApi,
similarMovieApi,
topRatedMovieApi,
upcomingMovieApi
} from './query/RTKQuery.ts'
import {setupListeners} from "@reduxjs/toolkit/query";

const sagaMiddleware = createSagaMiddleware();
const middleware = [sagaMiddleware];

const configurationAppStore = () => {
const store = configureStore({
reducer: {
[nowPlayingMovieApi.reducerPath]: nowPlayingMovieApi.reducer,
[popularMovieApi.reducerPath]: popularMovieApi.reducer,
[topRatedMovieApi.reducerPath]: topRatedMovieApi.reducer,
[upcomingMovieApi.reducerPath]: upcomingMovieApi.reducer,
[movieDetailApi.reducerPath]: movieDetailApi.reducer,
[similarMovieApi.reducerPath]: similarMovieApi.reducer,
[artistAndCrewApi.reducerPath]: artistAndCrewApi.reducer,
[artistDetailApi.reducerPath]: artistDetailApi.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat([...middleware, logger,
nowPlayingMovieApi.middleware,
popularMovieApi.middleware,
topRatedMovieApi.middleware,
upcomingMovieApi.middleware,
movieDetailApi.middleware,
similarMovieApi.middleware,
artistAndCrewApi.middleware,
artistDetailApi.middleware
]),
devTools: process.env.NODE_ENV === 'development'
})
setupListeners(store.dispatch)
sagaMiddleware.run(rootSaga);
return store
}
export default configurationAppStore

Types

import {MovieItem} from "./MovieItem";
import {Dates} from "./Dates";

export interface MovieResult {
dates: Dates
page: number
results: MovieItem[]
total_pages: number
total_results: number
}
export interface MovieItem {
adult: boolean
backdrop_path: string
genre_ids: number[]
id: number
original_language: string
original_title: string
overview: string
popularity: number
poster_path: string
release_date: string
title: string
video: boolean
vote_average: number
vote_count: number
}
export interface Dates {
maximum: string
minimum: string
}

MovieComponent.tsx

import React, {useState} from 'react';
import {FlatList, Image, View, TouchableOpacity, ImageBackground} from "react-native";
import styles from "./MovieListStyle";
import {Constants} from "../../appconstants/AppConstants";
import {MovieItem} from "../../types/MovieItem";

interface MovieItemProps {
movies: Array<MovieItem>;
onPress: (item: MovieItem) => void;
loadMoreData: () => void
}

const MovieComponent = (props: MovieItemProps) => {
const {movies, onPress, loadMoreData} = props;
const [isLoading, setIsLoading] = useState(true)
const movieItem = ({item}: { item: MovieItem }) => {
return (<TouchableOpacity style={styles.movieItemContainer} onPress={() => onPress(item)}>
<ImageBackground
imageStyle={{borderRadius: 18}}
source={isLoading ? require('../../assets/placeholder.jpeg') : {uri: `${Constants.IMAGE_URL}${item.poster_path}`}}
>
<Image
style={styles.imageView}
source={{
uri: `${Constants.IMAGE_URL}${item.poster_path}`,
}}
onLoadEnd={() => {
setIsLoading(false)
}}
/>
</ImageBackground>
</TouchableOpacity>)
};

return (<View style={styles.mainView}>
<FlatList
style={styles.flatListContainer}
data={movies}
renderItem={movieItem}
numColumns={2}
keyExtractor={(item, index) => index.toString()}
onEndReachedThreshold={0.5}
onEndReached={loadMoreData}
/>
</View>);
}

export default MovieComponent

Home.tsx

import React, {useEffect, useState} from 'react';
import Loading from '../../components/loading/Loading';
import MovieComponent from '../../components/movielist/MovieComponent.tsx';
import {View} from 'react-native';
import styles from './HomeStyle'
import {useGetNowPlayingMovieQuery} from "../../redux/query/RTKQuery.ts";
import {useNavigation} from "@react-navigation/native";
import {MovieItem} from "../../types/MovieItem.ts";


const Home = () => {
const navigation = useNavigation();
const [page, setPage] = useState(1);
const [movies, setMovies] = useState<Array<MovieItem>>([]);
const {data, error, isLoading, isFetching} = useGetNowPlayingMovieQuery(page)
useEffect(() => {
if (data && page > 1) {
setMovies((prevMovies) => [...prevMovies, ...data]);
}else {
setMovies(data ?? []);
}
}, [page, data?.length]);

const loadMoreMovies = () => {
if (!isFetching && !isLoading && !error) {
setPage( page + 1);
}
};

if (isLoading) return <Loading/>;

return (<View style={styles.mainView}>
<MovieComponent
movies={movies}
onPress={(item) => navigation.navigate('MovieDetail', {movieId: item.id})}
loadMoreData={loadMoreMovies}/>
</View>);
}
export default Home;

GitHub: https://github.com/piashcse/react-native-movie
Medium: https://piashcse.medium.com/rtk-query-example-code-in-react-native-e9a0bd402d8f