Loading
ActivityIndicator, skeleton screens, and splash screen configuration for loading states.
Loading
ScaleRocket Mobile provides multiple loading patterns for different contexts — inline spinners, skeleton placeholders, and the initial splash screen.
ActivityIndicator
The simplest loading state uses React Native's built-in ActivityIndicator:
import { ActivityIndicator, View } from "react-native";
function LoadingScreen() {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<ActivityIndicator size="large" color="#3B82F6" />
</View>
);
}Use the themed version for consistency:
import { Loading } from "@/components/ui/Loading";
// Full screen loading
<Loading />
// Inline loading with text
<Loading size="small" message="Saving..." />Loading Wrapper
Wrap data-dependent screens with a loading guard:
import { Loading } from "@/components/ui/Loading";
export default function DashboardScreen() {
const { data, isLoading } = useQuery();
if (isLoading) return <Loading />;
return (
<ScreenLayout>
<Text>{data.title}</Text>
</ScreenLayout>
);
}Skeleton Screens
Skeleton screens provide a better user experience than spinners by showing the layout shape while data loads:
import { View, StyleSheet } from "react-native";
import Animated, {
useSharedValue,
useAnimatedStyle,
withRepeat,
withTiming,
} from "react-native-reanimated";
function SkeletonBox({ width, height }: { width: number; height: number }) {
const opacity = useSharedValue(0.3);
React.useEffect(() => {
opacity.value = withRepeat(withTiming(1, { duration: 800 }), -1, true);
}, []);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
}));
return (
<Animated.View
style={[
{ width, height, backgroundColor: "#E5E7EB", borderRadius: 8 },
animatedStyle,
]}
/>
);
}Use skeletons to mirror the real layout:
function CardSkeleton() {
return (
<View style={{ padding: 16, gap: 12 }}>
<SkeletonBox width={120} height={16} />
<SkeletonBox width={200} height={32} />
<SkeletonBox width={160} height={14} />
</View>
);
}
function DashboardSkeleton() {
return (
<ScreenLayout>
<CardSkeleton />
<CardSkeleton />
<CardSkeleton />
</ScreenLayout>
);
}Pull to Refresh
Add pull-to-refresh on list screens:
import { RefreshControl, FlatList } from "react-native";
function ItemList() {
const [refreshing, setRefreshing] = useState(false);
const onRefresh = async () => {
setRefreshing(true);
await refetchData();
setRefreshing(false);
};
return (
<FlatList
data={items}
renderItem={({ item }) => <ItemRow item={item} />}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
/>
);
}Splash Screen
Configure the app splash screen in app.json:
{
"expo": {
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
}
}
}Keep the splash visible while loading initial data:
import * as SplashScreen from "expo-splash-screen";
SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
const { isLoading } = useSession();
useEffect(() => {
if (!isLoading) {
SplashScreen.hideAsync();
}
}, [isLoading]);
if (isLoading) return null;
return <Slot />;
}When to Use What
| Context | Pattern |
|---|---|
| Initial app load | Splash screen |
| Screen data loading | Skeleton screen |
| Button action | ActivityIndicator inside button |
| List refresh | Pull-to-refresh |
| Background save | Toast notification on complete |