expotesting3
v5.1.0
Published
React Native (Expo) learning library
Maintainers
Readme
expotesting
Production-ready React Native (Expo) learning library — modular, fully typed, aligned to a structured university syllabus.
Overview
expotesting is a pnpm + Turborepo monorepo that functions as both a learning platform and a reusable component/hook library for React Native (Expo) development. It mirrors academic progression across three modules:
| Module | Topic | Experiments | |--------|-------|-------------| | 1 | React Basics — Components, Navigation, JSX | 01, 02, 03 | | 2 | State, Networking & Data | 04, 05 | | 3 | Advanced — Animations & Device Features | 06 |
Monorepo Structure
expotesting/
├── apps/
│ └── expo-app/ # Demo app — runs all 6 experiments
├── packages/
│ ├── core/ # Components, hooks, theme, utils
│ ├── navigation/ # Stack, Tab, Drawer navigators + factory
│ ├── state/ # Context API + Redux Toolkit
│ ├── network/ # Axios client + fetch helpers + useFetch
│ ├── storage/ # AsyncStorage + SQLite wrappers + useSQLite
│ ├── animations/ # Animated API + Reanimated utilities
│ ├── device/ # useCamera, useGallery, useLocation
│ └── examples/ # All 6 runnable experiment screens
├── package.json # Root (private, pnpm workspace)
├── pnpm-workspace.yaml
├── turbo.json
└── tsconfig.base.jsonGetting Started
Prerequisites
- Node.js ≥ 18
- pnpm ≥ 9 (
npm i -g pnpm) - Expo Go app on a physical device, or an iOS/Android simulator
Install
pnpm installRun the demo app
pnpm start
# or
cd apps/expo-app && expo startPackages
@expotesting/core
Reusable UI components and hooks with zero external dependencies beyond React Native.
Components
import { Container, Card, Input, Button, List } from '@expotesting/core';
<Container scroll padded>
<Card title="Profile" subtitle="Software Engineer" elevation="md">
<Input label="Email" value={email} onChangeText={setEmail} />
<Button onPress={handleSubmit} loading={submitting}>Submit</Button>
</Card>
<List data={items} renderItem={({ item }) => <Text>{item.name}</Text>} />
</Container>Hooks
import { useToggle, useCounter, useAsync } from '@expotesting/core';
const { value: isOpen, toggle } = useToggle(false);
const { count, increment, decrement, reset } = useCounter(0, { min: 0, max: 10 });
const { data, loading, error, execute } = useAsync(() => fetchUser(id));Theme tokens
import { palette, spacing, typography, radius, shadows } from '@expotesting/core';@expotesting/navigation
Pre-wired navigators built on React Navigation v6.
import { createAppNavigator } from '@expotesting/navigation';
import { NavigationContainer } from '@react-navigation/native';
const Navigator = createAppNavigator({
type: 'tabs', // 'stack' | 'tabs' | 'drawer'
screens: [
{ name: 'Home', component: HomeScreen },
{ name: 'Profile', component: ProfileScreen },
],
activeColor: '#6200EE',
});
export default function App() {
return (
<NavigationContainer>
<Navigator />
</NavigationContainer>
);
}@expotesting/state
Context API
import { ThemeProvider, useTheme, AuthProvider, useAuth } from '@expotesting/state';
// Wrap app
<ThemeProvider><AuthProvider>{children}</AuthProvider></ThemeProvider>
// Consume
const { colors, isDark, toggleTheme } = useTheme();
const { user, login, logout, isAuthenticated } = useAuth();Redux Toolkit
import { Provider } from 'react-redux';
import {
store,
useAppSelector, useAppDispatch,
increment, decrement, fetchPosts,
selectCount, selectAllPosts,
} from '@expotesting/state';
// Root
<Provider store={store}>{children}</Provider>
// In components
const count = useAppSelector(selectCount);
const dispatch = useAppDispatch();
dispatch(increment());
dispatch(fetchPosts({ page: 1, limit: 10 }));@expotesting/network
import { apiClient, useFetch } from '@expotesting/network';
// Axios instance
const { data } = await apiClient.get<User>('/users/1');
await apiClient.post('/posts', { title: 'Hello' });
// Hook
const { data, loading, error, refetch } = useFetch<Post[]>('/posts?_limit=10');@expotesting/storage
import { appStorage, createStorage, useSQLite } from '@expotesting/storage';
// AsyncStorage
await appStorage.setItem('theme', 'dark');
const theme = await appStorage.getItem<string>('theme');
// Namespaced
const authStorage = createStorage('auth');
await authStorage.setItem('token', jwt);
// SQLite hook
const { db, ready, error } = useSQLite('myapp.db');
useEffect(() => {
if (!ready || !db) return;
void db.runAsync('CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT)');
}, [ready, db]);@expotesting/animations
import { FadeView, ScaleView, SlideView } from '@expotesting/animations';
import { reanimatedFadeIn, reanimatedSpringScale } from '@expotesting/animations';
// Animated API components
<FadeView visible={show} duration={300}>
<MyContent />
</FadeView>
<ScaleView visible={show}>
<MyContent />
</ScaleView>
<SlideView visible={show} direction="top">
<MyContent />
</SlideView>
// Reanimated hooks
import { useFade, useScale } from '@expotesting/animations';
const { opacity, fadeIn, fadeOut } = useFade();@expotesting/device
import { useCamera, useGallery, useLocation } from '@expotesting/device';
// Camera
const { hasPermission, requestPermission, capturePhoto, lastPhoto } = useCamera();
// Gallery
const { pickImage, selectedAsset } = useGallery({ allowsEditing: true });
// Location
const { location, getLocation, loading } = useLocation({ accuracy: 'balanced' });Experiments
| # | Screen | Topics |
|---|--------|--------|
| 01 | BasicAppScreen | View · Text · Image · StyleSheet · Flexbox · Component Composition |
| 02 | ComponentsStateScreen | useState · useReducer · Event Handling · Controlled Inputs |
| 03 | NavHomeScreen / NavDetailsScreen | Stack with typed params · Navigation.navigate · route.params |
| 04 | StateManagementScreen | Context API · Redux counter · Async thunk (fetchPosts) |
| 05 | NetworkStorageScreen | fetch() · Axios · FlatList · AsyncStorage · SQLite CRUD |
| 06 | AnimationsDeviceScreen | Animated API · Reanimated entering/exiting · Camera · Gallery · GPS |
Each experiment is a self-contained React component importable from @expotesting/examples.
import { BasicAppScreen, EXPERIMENT_REGISTRY } from '@expotesting/examples';TypeScript
All packages are authored in strict TypeScript. Type definitions ship alongside source files. No separate @types/* packages are required for the internal API.
Tooling
| Tool | Purpose | |------|---------| | pnpm workspaces | Monorepo package management | | Turborepo | Build orchestration + caching | | TypeScript 5 strict | Type safety | | Expo SDK ~52 | Managed workflow | | React Navigation 6 | Navigation | | Redux Toolkit 2 | State management | | Axios 1 | HTTP client | | Reanimated 3 | Advanced animations |
License
MIT
