@silicon.js/app-state
Version:
A lightweight React Native package for tracking and managing application state changes with a simple provider-based architecture.
333 lines (243 loc) • 7.97 kB
Markdown
# App State Package
A lightweight React Native package for tracking and managing application state changes with a simple provider-based architecture.
## Features
- 📱 Track app state transitions (active, background, inactive)
- 🔢 Count state changes automatically
- 🎯 Simple React Context API
- ⚡ Minimal performance overhead
- 🔄 Automatic cleanup and memory management
- 🎨 TypeScript support
## Installation
This package uses React Native's built-in `AppState` API, so no additional dependencies are required beyond React Native itself.
## Setup
### 1. Wrap your app with the provider
```tsx
import { AppStateProvider } from './app-state';
function App() {
return <AppStateProvider>{/* Your app components */}</AppStateProvider>;
}
```
### 2. Use the hook in your components
```tsx
import { useAppStateContext } from './app-state';
function MyComponent() {
const { state, count } = useAppStateContext();
useEffect(() => {
if (state === 'active') {
console.log('App came to foreground');
// Refresh data, restart timers, etc.
} else if (state === 'background') {
console.log('App went to background');
// Pause timers, save state, etc.
}
}, [state]);
return (
<View>
<Text>Current State: {state}</Text>
<Text>State Changes: {count}</Text>
</View>
);
}
```
## API Reference
### AppStateProvider Props
| Prop | Type | Required | Description |
| -------------- | -------------------------------- | -------- | ---------------------------------------------------- |
| `initialCount` | `number` | No | Initial count value (default: `0`) |
| `initialState` | `AppStateStatus` | No | Initial app state (default: `AppState.currentState`) |
| `methods` | `ReturnType<typeof useAppState>` | No | Override default methods for testing |
| `children` | `React.ReactNode` | Yes | Child components |
### Context Values
#### State
- `state: AppStateStatus` - Current app state (`'active' | 'background' | 'inactive'`)
- `count: number` - Number of state changes since initialization (starts at 1)
### App State Values
The `state` value can be one of:
- `'active'` - App is in the foreground and running
- `'background'` - App is in the background (iOS only, Android uses inactive)
- `'inactive'` - App is transitioning between foreground and background states
## Usage Examples
### Basic State Tracking
```tsx
import { useAppStateContext } from './app-state';
function StatusIndicator() {
const { state } = useAppStateContext();
return (
<View>
<Text>App is: {state}</Text>
{state === 'active' && <Badge color="green">Active</Badge>}
{state === 'background' && <Badge color="gray">Background</Badge>}
</View>
);
}
```
### Refresh Data on Foreground
```tsx
import { useAppStateContext } from './app-state';
function DataList() {
const { state } = useAppStateContext();
const [data, setData] = useState([]);
useEffect(() => {
if (state === 'active') {
fetchData().then(setData);
}
}, [state]);
return <FlatList data={data} />;
}
```
### Track State Change Frequency
```tsx
import { useAppStateContext } from './app-state';
function StateChangeCounter() {
const { count, state } = useAppStateContext();
return (
<View>
<Text>State changed {count} times</Text>
<Text>Current: {state}</Text>
</View>
);
}
```
### Pause/Resume Operations
```tsx
import { useAppStateContext } from './app-state';
function Timer() {
const { state } = useAppStateContext();
const [isPaused, setIsPaused] = useState(false);
useEffect(() => {
setIsPaused(state !== 'active');
}, [state]);
return (
<View>
<Text>{isPaused ? 'Paused' : 'Running'}</Text>
</View>
);
}
```
### Auto-save on Background
```tsx
import { useAppStateContext } from './app-state';
function Editor() {
const { state } = useAppStateContext();
const [content, setContent] = useState('');
useEffect(() => {
if (state === 'background' || state === 'inactive') {
saveContentToStorage(content);
}
}, [state, content]);
return <TextInput value={content} onChangeText={setContent} placeholder="Type something..." />;
}
```
## Advanced Usage
### Custom Initial Values
```tsx
<AppStateProvider initialCount={5} initialState="background">
<App />
</AppStateProvider>
```
### Testing with Custom Methods
```tsx
const mockMethods = {
count: 10,
state: 'background' as AppStateStatus,
};
<AppStateProvider methods={mockMethods}>
<ComponentUnderTest />
</AppStateProvider>;
```
### Multiple Consumers
```tsx
function AppContainer() {
return (
<AppStateProvider>
<Header /> {/* Uses state for status indicator */}
<MainContent /> {/* Uses state to refresh data */}
<Footer /> {/* Uses count for analytics */}
</AppStateProvider>
);
}
```
## Common Use Cases
### 1. **Data Synchronization**
Refresh data from API when app returns to foreground:
```tsx
useEffect(() => {
if (state === 'active') {
syncData();
}
}, [state]);
```
### 2. **Analytics Tracking**
Track app engagement and session duration:
```tsx
useEffect(() => {
if (state === 'active') {
analytics.trackSessionStart();
} else {
analytics.trackSessionEnd();
}
}, [state]);
```
### 3. **Resource Management**
Pause heavy operations when app is backgrounded:
```tsx
useEffect(() => {
if (state === 'background') {
pauseVideoPlayback();
stopLocationTracking();
} else if (state === 'active') {
resumeVideoPlayback();
startLocationTracking();
}
}, [state]);
```
### 4. **State Persistence**
Auto-save user work when app goes to background:
```tsx
useEffect(() => {
if (state !== 'active') {
saveUserState();
}
}, [state]);
```
## Platform Differences
### iOS
- Supports all three states: `active`, `inactive`, and `background`
- `inactive` occurs during phone calls, lock screen, or app switching
- `background` means app is fully backgrounded
### Android
- Primarily uses `active` and `inactive`
- May not reliably trigger `background` state
**Recommendation:** Handle both `background` and `inactive` for cross-platform compatibility.
## Performance Considerations
- The package uses a single `AppState` listener shared across all consumers
- State changes are debounced to prevent duplicate updates
- Listener is automatically cleaned up when provider unmounts
- Minimal re-renders - only updates when state actually changes
## Best Practices
1. **Handle all state values** - Don't assume only `active` and `background`
2. **Cleanup side effects** - Stop timers, cancel requests when app backgrounds
3. **Save critical data** - Persist important state before app is suspended
4. **Optimize background tasks** - Minimize processing when app isn't visible
5. **Test on real devices** - App state behavior varies between simulators and devices
## TypeScript Support
The package is fully typed with TypeScript:
```typescript
interface UseAppStateReturn {
count: number;
state: AppStateStatus; // 'active' | 'background' | 'inactive'
}
interface UseAppStateProps {
initialCount?: number;
initialState?: AppStateStatus;
}
```
## Troubleshooting
### Count starts at 1, not 0
This is intentional - the initial state counts as the first state, so `count` starts at 1.
### State not updating
- Ensure component is wrapped in `AppStateProvider`
- Test on a real device (simulators may behave differently)
- Check that you're using `useAppStateContext()` not `useAppState()` directly
### Multiple state changes
Some platforms may trigger rapid state changes during transitions. This is normal behavior and the package handles it correctly by tracking the previous state.