Before getting started ๐
This post uses Expo, but the approach applies to all React Native environments.
It assumes a project with the basic configuration already in place.
What is an indicator? ๐
An indicator is a UI element that tells users work is in progress, such as loading data or navigating to a page.
Without it, users cannot tell whether the operation is running, reducing usability.
There are several kinds, but we will try two common options:
ActivityIndicator and
react-native-skeleton-placeholder.
ActivityIndicator is the built-in circular loading indicator, while
react-native-skeleton-placeholder shows an empty skeleton of the upcoming interface.
Choosing an appropriate indicator for the situation can improve the user experience.
Building a test screen ๐
Open
App.js and create the following test screen.
Every refresh displays the current time after a two-second delay.
For the refresh behavior, see the
React Native RefreshControl post .
import React, { useEffect, useState } from 'react' ;
import { Text, ScrollView, RefreshControl, View, SafeAreaView } from 'react-native' ;
const Card = () => {
const [ now , setNow ] = useState ( '' );
useEffect (() => {
setTimeout (() => {
setNow ( new Date (). toLocaleString ());
}, 2000 );
}, []);
return (
< View style ={ { flex: 1 } } >
{ Array. from ({ length: 100 }). map (( _ , index ) => (
< Text key ={ index } style ={ { textAlign: 'center' , justifyContent: 'center' , fontSize: 20 } } > { `${ now }` } </ Text >
)) }
</ View >
);
};
const List = () => {
const [ key , setKey ] = useState ( new Date (). getTime ());
const [ refreshing , setRefreshing ] = useState ( false );
const handleRefresh = () => {
setRefreshing ( true );
setKey ( new Date (). getTime ());
setRefreshing ( false );
};
return (
< SafeAreaView style ={ { flex: 1 , backgroundColor: 'white' , padding: 20 } } >
< ScrollView
refreshControl ={
< RefreshControl refreshing ={ refreshing } onRefresh ={ handleRefresh } colors ={ [ 'blue' , 'red' , 'green' ] } />
}
>
< View style ={ { flex: 1 , flexDirection: 'row' } } >
< Card key ={ key } />
</ View >
</ ScrollView >
</ SafeAreaView >
);
};
export default List;
Before the refresh data loads, the screen is empty, leaving users unsure whether work is in progress or an error occurred.
Let's improve this with an indicator.
1. Using ActivityIndicator ๐
ActivityIndicator is a built-in circular loading indicator.
Because
ActivityIndicator is built in, no separate installation is necessary.
Use
ActivityIndicator as follows.
I made it blue to distinguish it from the refresh indicator and added
isLoading so it appears only while data is loading.
import React, { useEffect, useState } from 'react' ;
import { Text, ScrollView, RefreshControl, View, SafeAreaView, ActivityIndicator } from 'react-native' ;
const Card = () => {
const [ now , setNow ] = useState ( '' );
const [ isLoading , setIsLoading ] = useState ( true ); // isLoading ์ถ๊ฐ
useEffect (() => {
setIsLoading ( true ); // ๋ฐ์ดํฐ ๋ก๋ฉ ์์
setTimeout (() => {
setNow ( new Date (). toLocaleString ());
setIsLoading ( false ); // ๋ฐ์ดํฐ ๋ก๋ฉ ์๋ฃ
}, 2000 );
}, []);
return (
< View style ={ { flex: 1 } } >
{ isLoading ? (
< View style ={ { flex: 1 , justifyContent: 'center' , alignItems: 'center' , height: 200 } } >
< ActivityIndicator size = "large" color = "blue" /> // ActivityIndicator ์ถ๊ฐ
</ View >
) : (
Array. from ({ length: 100 }). map (( _ , index ) => (
< Text key ={ index } style ={ { textAlign: 'center' , justifyContent: 'center' , fontSize: 20 } } > { `${ now }` } </ Text >
))
) }
</ View >
);
};
const List = () => {
const [ key , setKey ] = useState ( new Date (). getTime ());
const [ refreshing , setRefreshing ] = useState ( false );
const handleRefresh = () => {
setRefreshing ( true );
setKey ( new Date (). getTime ());
setRefreshing ( false );
};
return (
< SafeAreaView style ={ { flex: 1 , backgroundColor: 'white' , padding: 20 } } >
< ScrollView
refreshControl ={
< RefreshControl refreshing ={ refreshing } onRefresh ={ handleRefresh } colors ={ [ 'blue' , 'red' , 'green' ] } />
}
>
< View style ={ { flex: 1 , flexDirection: 'row' } } >
< Card key ={ key } />
</ View >
</ ScrollView >
</ SafeAreaView >
);
};
export default List;
The indicator appears during the two-second load, then disappears when the requested data is displayed.
2. Using react-native-skeleton-placeholder ๐
react-native-skeleton-placeholder displays an empty skeleton of the upcoming UI.
Install
react-native-skeleton-placeholder using either of these commands.
npm ์ฌ์ฉ ์ npm install @react-native-masked-view/masked-view react-native-linear-gradient --save
npm install react-native-skeleton-placeholder --save
yarn ์ฌ์ฉ ์ yarn add @react-native-masked-view/masked-view react-native-linear-gradient
yarn add react-native-skeleton-placeholder
Use react-native-skeleton-placeholder as follows.
import React, { useEffect, useState } from 'react' ;
import { Text, ScrollView, RefreshControl, View, SafeAreaView } from 'react-native' ;
import SkeletonPlaceholder from 'react-native-skeleton-placeholder' ;
const Card = () => {
const [ now , setNow ] = useState ( '' );
const [ isLoading , setIsLoading ] = useState ( true );
useEffect (() => {
setIsLoading ( true );
setTimeout (() => {
setNow ( new Date (). toLocaleString ());
setIsLoading ( false );
}, 2000 );
}, []);
return (
< View style ={ { flex: 1 } } >
{ isLoading ? (
< SkeletonPlaceholder borderRadius ={ 4 } >
< Text style ={ { marginTop: 6 , fontSize: 14 , lineHeight: 18 } } >Hello world</ Text >
</ SkeletonPlaceholder >
) : (
Array. from ({ length: 100 }). map (( _ , index ) => (
< Text key ={ index } style ={ { textAlign: 'center' , justifyContent: 'center' , fontSize: 20 } } > { `${ now }` } </ Text >
))
) }
</ View >
);
};
const List = () => {
const [ key , setKey ] = useState ( new Date (). getTime ());
const [ refreshing , setRefreshing ] = useState ( false );
const handleRefresh = () => {
setRefreshing ( true );
setKey ( new Date (). getTime ());
setRefreshing ( false );
};
return (
< SafeAreaView style ={ { flex: 1 , backgroundColor: 'white' , padding: 20 } } >
< ScrollView
refreshControl ={
< RefreshControl refreshing ={ refreshing } onRefresh ={ handleRefresh } colors ={ [ 'blue' , 'red' , 'green' ] } />
}
>
< View style ={ { flex: 1 , flexDirection: 'row' } } >
< Card key ={ key } />
</ View >
</ ScrollView >
</ SafeAreaView >
);
};
export default List;
I am currently investigating the error
ERROR Warning: TypeError: Cannot read property 'bubblingEventTypes' of null.
I will update this post when I find a solution.
GitHub issueโ
Which kind of loading indicator best suits your situation?
Under which conditions should you display it?