@scripturecoder/rn-alpha-hooks
v0.2.3-alpha
Published
Flexible React Native hooks, extensible store, and utilities with TypeScript support.
Maintainers
Readme
@scripturecoder/rn-alpha-hooks
A comprehensive React Native hooks, store, and utility toolkit with TypeScript support.
@scripturecoder/rn-alpha-hooks is a powerful toolkit for React Native applications, providing advanced data fetching hooks, flexible Redux store management, and essential utilities—all with full TypeScript support.
📑 Table of Contents
- Installation
- Quick Start
- Configuration
- Core Hooks
- Store & State Management
- Advanced Features
- Encryption & Security
- Utilities Reference
- TypeScript Support
- Custom Hook Wrappers
- Complete Examples
- API Reference
- Troubleshooting
- Migration Guide
- Contributing
- License
Installation
# with npm
npm install @scripturecoder/rn-alpha-hooks
# or with yarn
yarn add @scripturecoder/rn-alpha-hooksPeer Dependencies
The package requires React ≥ 18 and React Native ≥ 0.73. Make sure your host application provides compatible versions.
Runtime Dependencies
These packages must be installed in your app:
yarn add @react-native-community/netinfo react-native-blob-util react-native-mmkv react-native-simple-toast
npm install @react-native-community/netinfo react-native-blob-util react-native-mmkv react-native-simple-toastThe versions listed in package.json are minimum compatible versions. You can use newer versions as long as they remain API-compatible.
Quick Start
1. Wrap your app with AlphaProvider
import { AlphaProvider } from '@scripturecoder/rn-alpha-hooks';
export default function App() {
return (
<AlphaProvider
config={{
baseUrl: 'https://api.example.com',
paths: {
login: 'POST:/auth/login',
users: 'GET:/users',
getUser: 'GET:/users/:id',
},
debug: __DEV__,
}}
>
<Navigation />
</AlphaProvider>
);
}2. Use hooks in your components
import { useQuery, useMutation } from '@scripturecoder/rn-alpha-hooks';
function UserProfile({ userId }) {
// Fetch data with caching
const { data, loading, error, refetch } = useQuery('getUser', {
variables: { userId }
});
// Mutation for updates
const [updateUser] = useMutation('updateUser');
const handleUpdate = async (updates) => {
const result = await updateUser({ userId, ...updates });
if ('data' in result) {
refetch(); // Refresh data
}
};
if (loading) return <Loading />;
if (error) return <Error message={error} />;
return <ProfileView user={data} onUpdate={handleUpdate} />;
}3. Request Cancellation
function SearchComponent({ query }) {
const { data, loading, abort } = useQuery('search', {
variables: { query },
networkPolicy: 'network-only'
});
// Cancel request when query changes
useEffect(() => {
return () => abort();
}, [query]);
return <Results data={data} loading={loading} />;
}Configuration
Configuration Options
Configure rn-alpha by passing a config object to AlphaProvider:
Required
baseUrl(string) - Base URL for all API requests
Optional API Settings
timeout(number) - Request timeout in milliseconds (default: 30000)headers(object) - Default headers for all requestsdataPath(string) - Path to extract data from API responses (default: "data")- Use
"data"for responses like{ data: { data: [...] } }(default) - Use
""(empty string) for responses like{ data: [...] }directly - Supports dot notation like
"data.items"for{ data: { items: [...] } }
- Use
Custom Routes
Provide routes in format METHOD:/path/with/:params:
paths: {
users: 'GET:/users',
createUser: 'POST:/users',
getUser: 'GET:/users/:id',
updateUser: 'PUT:/users/:id',
deleteUser: 'DELETE:/users/:id',
}Cache Settings
cache: {
ttl: 5 * 60 * 1000, // 5 minutes (default)
staleTime: 30 * 1000, // 30 seconds
maxSize: 100, // Max 100 entries (default)
}Network Policy
defaultNetworkPolicy: 'cache-first'
// Options: 'cache-first', 'network-only', 'cache-only', 'network-and-cache', 'stale-while-revalidate'Retry Settings
retry: {
enabled: true,
count: 3, // Max retry attempts
delay: 'exponential', // or number in ms
}Encryption Configuration
encryption: {
key: process.env.ENCRYPTION_KEY, // Must be 16 chars for AES-128
iv: process.env.ENCRYPTION_IV, // Must be 16 chars
}Debug Mode
debug: __DEV__ // Enable detailed logging in developmentFull Configuration Example
import { AlphaProvider } from '@scripturecoder/rn-alpha-hooks';
import apiRoutes from './config/api-routes';
function App() {
return (
<AlphaProvider
config={{
// API Settings
baseUrl: process.env.API_BASE_URL,
timeout: 60000,
headers: {
'X-App-Version': '1.0.0',
'X-Platform': 'mobile',
},
dataPath: 'data', // or "" for direct res.data access
// Routes
paths: apiRoutes,
// Cache
cache: {
ttl: 10 * 60 * 1000,
staleTime: 60 * 1000,
maxSize: 200,
},
// Defaults
defaultNetworkPolicy: 'stale-while-revalidate',
// Retry
retry: {
enabled: true,
count: 5,
delay: 'exponential',
},
// Encryption
encryption: {
key: process.env.ENCRYPTION_KEY,
iv: process.env.ENCRYPTION_IV,
},
// Debug
debug: __DEV__,
}}
>
<Navigation />
</AlphaProvider>
);
}Organizing Routes
Create a separate file for your API routes:
// config/api-routes.ts
export default {
// Auth
login: 'POST:/auth/login',
logout: 'POST:/auth/logout',
register: 'POST:/auth/register',
// Users
getProfile: 'GET:/users/me',
updateProfile: 'PUT:/users/me',
getUsers: 'GET:/users',
// Products
getProducts: 'GET:/products',
getProduct: 'GET:/products/:id',
createProduct: 'POST:/products',
updateProduct: 'PUT:/products/:id',
deleteProduct: 'DELETE:/products/:id',
};Then import and use:
import apiRoutes from './config/api-routes';
<AlphaProvider config={{ baseUrl: '...', paths: apiRoutes }}>
<App />
</AlphaProvider>Response Data Path Configuration
By default, rn-alpha expects API responses in the format { data: { data: [...] } } and extracts the nested data field. You can configure this behavior using the dataPath option.
Standard Response (Default)
// API returns: { data: { data: { id: 1, name: "John" } } }
<AlphaProvider config={{
baseUrl: '...',
dataPath: 'data' // default
}}>Direct Response
// API returns: { data: { id: 1, name: "John" } }
<AlphaProvider config={{
baseUrl: '...',
dataPath: '' // empty string for direct access
}}>Nested Path
// API returns: { data: { items: [...], meta: {...} } }
<AlphaProvider config={{
baseUrl: '...',
dataPath: 'data.items' // dot notation for nested paths
}}>Custom Extraction
// API returns: { data: { result: { payload: [...] } } }
<AlphaProvider config={{
baseUrl: '...',
dataPath: 'data.result.payload'
}}>Note: All hooks (useQuery, useMutation, useQueryAsync, useMutationAsync) respect this global configuration, ensuring consistent data extraction across your entire application.
Per-Query/Mutation Override
You can override the global dataPath for individual queries or mutations:
// Global default
<AlphaProvider config={{ dataPath: 'data' }}>
// Override per-query
const { data } = useQuery('users', {
dataPath: 'result.items', // Uses this instead of global
});
// Override per-mutation
const [mutate] = useMutation('login', {
dataPath: '', // Direct response, no extraction
});
// Override in async query
const queryAsync = useQueryAsync();
const result = await queryAsync('endpoint', {}, {
dataPath: 'data.payload',
});Priority Order:
- Hook-level
dataPathoption (highest priority) - Global
config.dataPath - Default
'data'
Environment-Specific Configuration
// config/alpha.config.ts
const isDev = __DEV__;
export const alphaConfig = {
baseUrl: isDev
? 'https://dev-api.example.com'
: 'https://api.example.com',
timeout: isDev ? 60000 : 30000,
debug: isDev,
cache: {
ttl: isDev ? 1 * 60 * 1000 : 10 * 60 * 1000, // 1 min dev, 10 min prod
},
};Accessing and Updating Config
Use the useAlphaConfig hook to both read and update configuration dynamically:
import { useAlphaConfig } from '@scripturecoder/rn-alpha-hooks';
function MyComponent() {
// Returns tuple [config, setConfig] like useState
const [config, setConfig] = useAlphaConfig();
console.log('Base URL:', config.baseUrl);
console.log('Debug mode:', config.debug);
// Update config dynamically
const switchToProduction = () => {
setConfig({
baseUrl: 'https://api.example.com',
debug: false
});
};
return (
<View>
<Button title="Switch to Production" onPress={switchToProduction} />
</View>
);
}
// Read-only access (ignore setter)
function DisplayConfig() {
const [config] = useAlphaConfig();
return <Text>{config.baseUrl}</Text>;
}
// Write-only access (ignore getter)
function ConfigUpdater() {
const [, setConfig] = useAlphaConfig();
return (
<Button
title="Reset"
onPress={() => setConfig({ timeout: 30000 })}
/>
);
}Default Values Reference
| Option | Default Value | Description |
|--------|--------------|-------------|
| timeout | 30000 | Request timeout (30s) |
| dataPath | 'data' | Response data extraction path |
| cache.ttl | 300000 | Cache TTL (5 minutes) |
| cache.staleTime | 0 | Stale time (always stale) |
| cache.maxSize | 100 | Max cache entries |
| defaultNetworkPolicy | 'cache-first' | Default fetch strategy |
| retry.enabled | true | Enable auto-retry |
| retry.count | 3 | Max retry attempts |
| retry.delay | 'exponential' | Retry delay strategy |
| debug | false | Debug logging |
Core Hooks
useQuery
Advanced data fetching with intelligent caching and request management.
Basic Usage
import { useQuery } from '@scripturecoder/rn-alpha-hooks';
function UserProfile() {
const { data, loading, error, refetch } = useQuery('getUser', {
variables: { userId: '123' }
});
if (loading) return <Loading />;
if (error) return <Error message={error} />;
return <ProfileView user={data} onRefresh={refetch} />;
}Options
interface QueryOptions {
variables?: Record<string, any>; // URL params and request body
networkPolicy?: NetworkPolicy; // Cache strategy
onCompleted?: (data: any) => void; // Success callback
onError?: (error: string, status?: number) => void; // Error callback
refetchOnMount?: boolean; // Refetch when component mounts
refetchOnFocus?: boolean; // Refetch when window gains focus
refetchOnReconnect?: boolean; // Refetch when network reconnects
refetchInterval?: number; // Poll interval in ms
retry?: RetryOptions; // Retry configuration
debug?: boolean; // Enable debug logging
ttl?: number; // Cache TTL override
staleTime?: number; // Stale time override
}Return Value
interface QueryResult<T> {
data: T; // Response data
loading: boolean; // Loading state
error: string; // Error message
refetch: () => Promise<void>; // Manually refetch
fetchMore: (vars, strategy, key?) => Promise<void>; // Load more data
update: (data) => void; // Replace cache
updateValue: (key, value) => void; // Update single field
updateValues: (values) => void; // Update multiple fields
updateItem: (id, value) => void; // Update array item
deleteItem: (id) => void; // Remove array item
prepend: (item) => void; // Add to start
append: (item) => void; // Add to end
optimisticUpdate: (updates, revert?) => Promise<void>; // Optimistic update
timing: TimingInfo; // Performance metrics
abort: () => void; // Cancel request
}Network Policies
// Cache-first (default): Use cache if available, otherwise fetch
const { data } = useQuery('getUsers', {
networkPolicy: 'cache-first'
});
// Network-only: Always fetch from network
const { data } = useQuery('getUsers', {
networkPolicy: 'network-only'
});
// Cache-only: Never fetch, only use cache
const { data } = useQuery('getUsers', {
networkPolicy: 'cache-only'
});
// Network-and-cache: Show cache while fetching fresh data
const { data } = useQuery('getUsers', {
networkPolicy: 'network-and-cache'
});
// Stale-while-revalidate: Show cache, fetch in background
const { data } = useQuery('getUsers', {
networkPolicy: 'stale-while-revalidate'
});Cache Manipulation
const {
data,
update, // Replace entire cache
updateValue, // Update single field
updateItem, // Update array item by id
deleteItem, // Remove array item by id
prepend, // Add to start of array
append // Add to end of array
} = useQuery('getUsers');
// Update entire data
update({ name: 'John', age: 30 });
// Update single field
updateValue('name', 'Jane');
// Update multiple fields
updateValues({ name: 'Jane', age: 25 });
// Update item in array
updateItem('user-123', { name: 'Updated Name' });
// Delete item from array
deleteItem('user-123');
// Add to start
prepend({ id: 'new-1', name: 'New User' });
// Add to end
append({ id: 'new-2', name: 'Another User' });Pagination
const { data, fetchMore } = useQuery('getPosts', {
variables: { page: 1, limit: 10 }
});
// Load more and append
const loadMore = async () => {
await fetchMore(
{ page: 2, limit: 10 },
'end' // 'start' | 'end' | 'pagination'
);
};Request Cancellation
import { useEffect } from 'react';
function SearchResults({ searchTerm }) {
const { data, loading, abort } = useQuery('searchUsers', {
variables: { query: searchTerm }
});
// Abort when component unmounts or search term changes
useEffect(() => {
return () => abort(); // Cleanup
}, [searchTerm]);
return (
<View>
{loading && <Button onPress={abort}>Cancel</Button>}
<Results data={data} />
</View>
);
}useQueryAsync
Async data fetching for imperative operations.
Basic Usage
import { useQueryAsync } from '@scripturecoder/rn-alpha-hooks';
function UserActions() {
const fetchUser = useQueryAsync();
const handleFetch = async () => {
const result = await fetchUser('getUser', { userId: '123' });
if ('error' in result) {
console.error('Error:', result.error);
} else {
console.log('User:', result.data);
}
};
return <Button onPress={handleFetch}>Fetch User</Button>;
}With Abort Signal
function SearchComponent() {
const fetchSearch = useQueryAsync();
const abortControllerRef = useRef<AbortController | null>(null);
const handleSearch = async (query: string) => {
// Cancel previous search
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
// Create new controller
abortControllerRef.current = new AbortController();
const result = await fetchSearch('searchUsers',
{ query },
{ signal: abortControllerRef.current.signal }
);
if ('error' in result && result.error === 'Request cancelled') {
console.log('Search cancelled');
} else if ('data' in result) {
console.log('Results:', result.data);
}
};
return <SearchInput onSearch={handleSearch} />;
}useMutation
POST/PUT/DELETE operations with offline queue support.
Basic Usage
import { useMutation } from '@scripturecoder/rn-alpha-hooks';
function CreateUserForm() {
const [createUser, { loading, error, data }] = useMutation('createUser');
const handleSubmit = async (formData) => {
const result = await createUser(formData);
if ('error' in result) {
Alert.alert('Error', result.error);
} else {
Alert.alert('Success', 'User created!');
}
};
return (
<Form onSubmit={handleSubmit}>
{loading && <Spinner />}
{error && <ErrorText>{error}</ErrorText>}
</Form>
);
}Return Value
type MutationResult<T> = [
mutateFunction: (variables: Record<string, any>) => Promise<MutationResponse<T>>,
state: {
loading: boolean;
error: string;
data: T;
cancel: () => void; // Cancel the request
}
];Cancelling Mutations
function UploadComponent() {
const [uploadFile, { loading, cancel }] = useMutation('uploadFile');
const handleUpload = async (file) => {
const result = await uploadFile({ file });
if ('error' in result && result.error === 'Request cancelled') {
console.log('Upload cancelled');
}
};
return (
<View>
<Button onPress={() => handleUpload(file)}>Upload</Button>
{loading && (
<Button onPress={cancel}>Cancel Upload</Button>
)}
</View>
);
}useMutationAsync
Async mutations with legacy route support.
Basic Usage
import { useMutationAsync } from '@scripturecoder/rn-alpha-hooks';
function LoginForm() {
const [login, { loading, error }] = useMutationAsync('POST:/auth/login');
const handleLogin = async (credentials) => {
const result = await login(credentials);
if ('data' in result) {
// Success
navigation.navigate('Home');
}
};
return (
<Form onSubmit={handleLogin}>
{loading && <Spinner />}
{error && <ErrorText>{error}</ErrorText>}
</Form>
);
}useCache
Direct cache manipulation and management.
Basic Usage
import { useCache } from '@scripturecoder/rn-alpha-hooks';
function CacheManager() {
const cache = useCache();
// Get cache item
const user = cache.get('user:123');
// Set cache item
cache.set('user:123', { name: 'John', age: 30 });
// Update cache item
cache.update('user:123', { age: 31 });
// Delete cache item
cache.del('user:123');
// Invalidate specific query
cache.invalidate('user:123');
// Invalidate queries matching pattern
cache.invalidateQueries('user:');
// Clear all cache
cache.invalidateAll();
return <View>...</View>;
}Methods
get(key)- Get cached dataset(key, data, options?)- Set cache entryupdate(key, updates)- Update cache entrydel(key)- Delete cache entryinvalidate(key)- Invalidate and refetchinvalidateQueries(pattern)- Invalidate matching queriesinvalidateAll()- Clear all cacheupdateItem(key, id, updates)- Update array itemdeleteItem(key, id)- Delete array itemprepend(key, item)- Add to array startappend(key, item)- Add to array end
Store & State Management
rn-alpha provides a minimal core state (authentication & user) and lets you extend it with your own custom reducers for app-specific state like themes, settings, or preferences.
Core State
The package provides these core slices:
1. App Reducer (Core)
interface CoreAppState {
auth: {
accessToken: string;
refreshToken: string;
customerId?: string;
};
user: any; // Your user object
}Actions:
setAuth(payload)- Set authentication tokenssetUser(payload)- Set user dataclearAuth()- Clear authentication and user
2. Cache Reducer (Core)
Stores API response cache with TTL and LRU eviction.
3. Thread Reducer (Core)
Manages background threads/workers.
Adding Custom Reducers
Step 1: Create Your Reducer
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface SettingsState {
theme: 'light' | 'dark';
language: string;
notifications: boolean;
}
const settingsSlice = createSlice({
name: 'settings',
initialState: {
theme: 'light',
language: 'en',
notifications: true,
} as SettingsState,
reducers: {
setTheme: (state, action: PayloadAction<'light' | 'dark'>) => {
state.theme = action.payload;
},
setLanguage: (state, action: PayloadAction<string>) => {
state.language = action.payload;
},
toggleNotifications: (state) => {
state.notifications = !state.notifications;
},
},
});
export const settingsActions = settingsSlice.actions;
export default settingsSlice.reducer;Step 2: Pass to AlphaProvider
import { AlphaProvider } from '@scripturecoder/rn-alpha-hooks';
import settingsReducer from './store/settingsSlice';
import preferencesReducer from './store/preferencesSlice';
function App() {
return (
<AlphaProvider
config={{
baseUrl: 'https://api.example.com',
// ... other config
}}
customReducers={{
settings: settingsReducer,
preferences: preferencesReducer,
// Add more as needed
}}
>
<Navigation />
</AlphaProvider>
);
}Step 3: Access Your State
import { useSelector, useDispatch } from '@scripturecoder/rn-alpha-hooks';
function ThemeToggle() {
const theme = useSelector((state: any) => state.settings.theme);
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(settingsActions.setTheme(
theme === 'light' ? 'dark' : 'light'
))}>
Toggle Theme
</button>
);
}Creating Custom Contexts
For better ergonomics, wrap useApp with your own context:
import { createContext, useContext, useMemo } from 'react';
import { useApp, AppContextValue, useSelector, useDispatch } from '@scripturecoder/rn-alpha-hooks';
interface MyAppContextValue extends AppContextValue {
// Add custom fields
theme: 'light' | 'dark';
setTheme: (theme: 'light' | 'dark') => void;
}
const MyAppContext = createContext<MyAppContextValue | undefined>(undefined);
export function MyAppProvider({ children }) {
const coreApp = useApp();
const settings = useSelector((state: any) => state.settings);
const dispatch = useDispatch();
const value = useMemo<MyAppContextValue>(
() => ({
...coreApp, // Include core features
theme: settings.theme,
setTheme: (theme) => dispatch(settingsActions.setTheme(theme)),
}),
[coreApp, settings, dispatch]
);
return (
<MyAppContext.Provider value={value}>
{children}
</MyAppContext.Provider>
);
}
export function useMyApp(): MyAppContextValue {
const context = useContext(MyAppContext);
if (!context) throw new Error('useMyApp must be used within MyAppProvider');
return context;
}TypeScript Best Practices
1. Define Your Root State Type
import { RootState as CoreRootState } from '@scripturecoder/rn-alpha-hooks';
interface CustomState {
settings: SettingsState;
preferences: PreferencesState;
}
// Your app's complete state
type AppRootState = CoreRootState & CustomState;2. Create Typed Selector Hook
import { useSelector as useReduxSelector, TypedUseSelectorHook } from 'react-redux';
export const useTypedSelector: TypedUseSelectorHook<AppRootState> = useReduxSelector;
// Usage
function Component() {
const theme = useTypedSelector((state) => state.settings.theme); // fully typed!
}3. Export Typed Dispatch
import { AppDispatch } from '@scripturecoder/rn-alpha-hooks';
import { useDispatch as useReduxDispatch } from 'react-redux';
export const useAppDispatch: () => AppDispatch = useReduxDispatch;Persisting Custom State
<AlphaProvider
config={{...}}
customReducers={{...}}
storeOptions={{
persist: true,
storageKey: 'my-app-state', // Custom key
}}
/>Custom Store
For complete control:
import { createAlphaStore } from '@scripturecoder/rn-alpha-hooks';
const myStore = createAlphaStore(
{
settings: settingsReducer,
preferences: preferencesReducer,
},
{
persist: true,
storageKey: 'my-custom-key',
}
);
<AlphaProvider config={{...}} store={myStore}>
<App />
</AlphaProvider>Advanced Features
Request Deduplication
Automatically prevents multiple identical requests from running simultaneously:
// These will only trigger one network request
const Component1 = () => useQuery('getUser', { variables: { id: '123' } });
const Component2 = () => useQuery('getUser', { variables: { id: '123' } });
const Component3 = () => useQuery('getUser', { variables: { id: '123' } });Cache Management
Cache TTL & Expiry
// Set cache TTL globally
<AlphaProvider
config={{
cache: {
ttl: 10 * 60 * 1000, // 10 minutes
staleTime: 60 * 1000, // 1 minute
}
}}
/>
// Override per-query
const { data } = useQuery('getUser', {
ttl: 5 * 60 * 1000, // 5 minutes
staleTime: 30 * 1000, // 30 seconds
});LRU Cache Eviction
Automatically removes least recently used entries when cache exceeds maxSize:
<AlphaProvider
config={{
cache: {
maxSize: 100, // Keep only 100 most recent entries
}
}}
/>Cache Invalidation
import { useCache } from '@scripturecoder/rn-alpha-hooks';
function Component() {
const cache = useCache();
// Invalidate specific cache
cache.invalidate('user:123');
// Invalidate all users
cache.invalidateQueries('user:');
// Clear everything
cache.invalidateAll();
}Dynamic Configuration Updates
Update configuration on the fly from any child component using useAlphaConfig():
Basic Usage
import { useAlphaConfig } from '@scripturecoder/rn-alpha-hooks';
function SettingsScreen() {
const [config, setConfig] = useAlphaConfig();
const switchEnvironment = (env: 'dev' | 'prod') => {
setConfig({
baseUrl: env === 'dev'
? 'https://dev-api.example.com'
: 'https://api.example.com',
debug: env === 'dev',
});
};
return (
<View>
<Text>Current API: {config.baseUrl}</Text>
<Button title="Switch to Dev" onPress={() => switchEnvironment('dev')} />
<Button title="Switch to Prod" onPress={() => switchEnvironment('prod')} />
</View>
);
}Toggle Features
function FeatureToggle() {
const [config, setConfig] = useAlphaConfig();
const toggleEncryption = (enabled: boolean) => {
setConfig({
defaultEncryption: enabled ? {
request: ['password', 'pin'],
response: ['token', 'ssn'],
} : false,
});
};
return (
<Switch
value={config.defaultEncryption !== false}
onValueChange={toggleEncryption}
/>
);
}Update Multiple Settings
function ConfigPresets() {
const [, setConfig] = useAlphaConfig(); // Write-only
const setupForDevelopment = () => {
setConfig({
baseUrl: 'https://dev-api.example.com',
debug: true,
timeout: 60000,
cache: { maxSize: 50 },
defaultEncryption: false,
});
};
const setupForProduction = () => {
setConfig({
baseUrl: 'https://api.example.com',
debug: false,
timeout: 30000,
cache: { maxSize: 200 },
defaultEncryption: {
request: ['password'],
response: ['token'],
},
});
};
return (
<>
<Button title="Dev Mode" onPress={setupForDevelopment} />
<Button title="Prod Mode" onPress={setupForProduction} />
</>
);
}Configuration Patterns
// Pattern 1: Read and write
const [config, setConfig] = useAlphaConfig();
// Pattern 2: Read-only (ignore setter)
const [config] = useAlphaConfig();
// Pattern 3: Write-only (ignore getter)
const [, setConfig] = useAlphaConfig();
// Partial updates (deep merge for nested objects)
setConfig({ timeout: 60000 }); // Only updates timeout
// Nested object updates are deep merged
setConfig({
cache: { maxSize: 200 } // Merges with existing cache config
});What Updates Immediately
When you call setConfig(), these changes take effect immediately for new requests:
✅ Base URL - Next requests use new URL
✅ Timeout - Applied to new requests
✅ Headers - Added to new requests
✅ Encryption Keys - Used for new encrypt/decrypt calls
✅ Default Encryption - Applied to new hook calls
✅ Debug Mode - Logging enabled/disabled
✅ Cache Settings - Max size and TTL updated
✅ Network Policy - New default for queries
⚠️ Important Notes:
- In-flight requests continue with old config
- Cached data is not automatically cleared
- Store remains unchanged (for custom reducers)
See examples/dynamic-config.tsx for complete examples.
Network Policies
Choose the right policy for your use case:
| Policy | Behavior | Use Case |
|--------|----------|----------|
| cache-first | Use cache if available, otherwise fetch | Default, good for most data |
| network-only | Always fetch from network | Real-time data, always fresh |
| cache-only | Never fetch, only use cache | Offline mode |
| network-and-cache | Show cache while fetching | Fast UX with fresh data |
| stale-while-revalidate | Show cache, fetch in background | Best UX, eventual consistency |
const { data } = useQuery('getUser', {
networkPolicy: 'stale-while-revalidate'
});Retry Mechanisms
Automatic retry with exponential backoff:
// Global configuration
<AlphaProvider
config={{
retry: {
enabled: true,
count: 3, // Max 3 retries
delay: 'exponential', // Exponential backoff
}
}}
/>
// Per-query override
const { data } = useQuery('getUser', {
retry: {
enabled: true,
count: 5,
delay: 1000, // Fixed 1s delay
}
});Offline Queue
Automatically queues mutations when offline and replays them on reconnect:
// Mutations are automatically queued when offline
const [createPost, { loading }] = useMutation('createPost');
const handleCreate = async () => {
// This will queue if offline
const result = await createPost({ title: 'My Post' });
if ('data' in result) {
Alert.alert('Success', 'Posted!');
}
};Background Refetch
Automatically refetch data on:
- Window Focus: When app comes to foreground
- Network Reconnect: When network connection is restored
- Intervals: Poll data at specified intervals
const { data } = useQuery('getLiveData', {
refetchOnFocus: true,
refetchOnReconnect: true,
refetchInterval: 30000, // Poll every 30 seconds
});Optimistic Updates
Update UI before server response:
function TodoList() {
const { data, updateItem, refetch, optimisticUpdate } = useQuery('getTodos');
const [toggleTodo] = useMutation('toggleTodo');
const handleToggle = async (todoId) => {
const todo = data.find(t => t.id === todoId);
// Optimistically update
await optimisticUpdate(
{ [todoId]: { ...todo, completed: !todo.completed } },
() => refetch() // Revert on error
);
// Make request
await toggleTodo({ todoId });
};
return <TodoItems todos={data} onToggle={handleToggle} />;
}Debug Mode
Enable detailed logging:
<AlphaProvider
config={{
debug: __DEV__, // or true to always enable
}}
/>Debug logs include:
- Cache hits/misses
- Network requests
- Request timing
- Cache operations
- Error details
Encryption & Security
The package provides powerful encryption capabilities for securing API requests and responses. You can encrypt entire request bodies or specific fields, and decrypt entire responses or specific fields.
Quick Start
<AlphaProvider
config={{
baseUrl: 'https://api.example.com',
encryption: {
key: process.env.ENCRYPTION_KEY, // Must be 16 chars
iv: process.env.ENCRYPTION_IV, // Must be 16 chars
},
// Optional: Set global encryption for all requests
defaultEncryption: {
request: ['password', 'pin'], // Encrypt these fields globally
response: ['token', 'ssn'], // Decrypt these fields globally
},
}}
>
<App />
</AlphaProvider>Request/Response Encryption
Full Body Encryption
Encrypt the entire request body and decrypt the entire response:
const [login, { loading, data }] = useMutation('login', {
encrypted: true, // Boolean shorthand for full encryption
});
await login({
email: '[email protected]',
password: 'secret123',
});
// Sent as: { encrypted: "U2FsdGVkX1..." }Partial Field Encryption
Encrypt only specific object keys (most common use case):
const [login, { loading }] = useMutation('login', {
encrypted: {
request: ['password'], // Only encrypt password field
response: ['token'], // Only decrypt token field
},
});
await login({
email: '[email protected]', // Sent as plain text
password: 'secret123', // Encrypted
rememberMe: true, // Sent as plain text
});
// Request sent:
// {
// email: "[email protected]",
// password: "U2FsdGVkX1...",
// rememberMe: true
// }Multiple Field Encryption
Encrypt multiple sensitive fields:
const [processPayment, { loading }] = useMutation('payment', {
encrypted: {
request: ['cardNumber', 'cvv', 'pin'], // Encrypt card details
response: ['transactionKey'], // Decrypt sensitive response
},
});
await processPayment({
cardNumber: '1234567890123456', // Encrypted
cvv: '123', // Encrypted
pin: '1234', // Encrypted
amount: 100.00, // Plain text
currency: 'USD', // Plain text
});Global Default Encryption
Set encryption behavior for ALL requests globally, then override per-hook as needed:
<AlphaProvider
config={{
encryption: {
key: '2vn!H3KXgX-TxvkD',
iv: '%x%97Uw@*A2xWaUJ',
},
// Apply to ALL hooks by default
defaultEncryption: {
request: ['password', 'pin', 'apiKey'],
response: ['token', 'ssn', 'data'],
},
}}
>Now all hooks automatically use this configuration:
// Uses global encryption (password encrypted automatically)
const [login] = useMutation('login');
await login({ email: '[email protected]', password: 'secret' });
// Override for public endpoints
const [publicQuery] = useQuery('publicData', {
encrypted: false, // Disable encryption for this query
});
// Override with different fields
const [payment] = useMutation('payment', {
encrypted: {
request: ['cardNumber', 'cvv'], // Different fields
},
});Global Encryption Options
All global encryption strategies:
// 1. Full encryption (boolean)
defaultEncryption: true
// 2. Full request & response (explicit)
defaultEncryption: {
request: 'full',
response: 'full',
}
// 3. Partial fields (recommended)
defaultEncryption: {
request: ['password', 'pin', 'apiKey'],
response: ['token', 'ssn', 'data'],
}
// 4. Mixed strategies
defaultEncryption: {
request: 'full', // Encrypt entire request
response: ['token', 'data'], // Only decrypt specific fields
}
// 5. Request-only encryption
defaultEncryption: {
request: ['password'],
// No response decryption
}Encryption with Queries
Works with all hooks:
// useQuery
const { data, loading } = useQuery('secureData', {
variables: { token: 'secret' },
encrypted: {
request: ['token'],
response: 'full',
},
});
// useQueryAsync
const queryAsync = useQueryAsync();
const result = await queryAsync(
'endpoint',
{ apiKey: 'secret' },
{
encrypted: {
request: ['apiKey'],
response: ['data'],
},
}
);Priority System
Encryption configuration follows this priority order:
- Hook-level
encryptedoption (highest priority) - Global
defaultEncryptionconfig - No encryption (default)
// Global: full encryption
<AlphaProvider config={{ defaultEncryption: true }}>
// Hook: disabled (overrides global)
useQuery('data', { encrypted: false });
// Hook: not specified (uses global)
useQuery('other'); // Uses defaultEncryption: trueReal-World Examples
Banking Application
const [transfer, { loading }] = useMutation('transfer', {
encrypted: {
request: ['accountNumber', 'pin', 'amount'],
response: ['transactionId', 'balance'],
},
});Healthcare Records
const { data } = useQuery('patientRecords', {
variables: { patientId: '123' },
encrypted: {
response: ['ssn', 'diagnosis', 'medications'],
},
});E-commerce Checkout
const [checkout] = useMutation('checkout', {
encrypted: {
request: ['cardNumber', 'cvv', 'expiryDate'],
response: ['paymentToken'],
},
});Standalone Encryption Functions
For manual encryption/decryption outside hooks:
encrypt / decrypt
import { encrypt, decrypt } from '@scripturecoder/rn-alpha-hooks';
// Encrypt data (uses global config)
const encrypted = encrypt('sensitive data');
// Decrypt data (uses global config)
const decrypted = decrypt(encrypted);
// With custom keys
const encrypted = encrypt('data', 'CustomKey1234567', 'CustomIV12345678');
const decrypted = decrypt(encrypted, 'CustomKey1234567', 'CustomIV12345678');setEncryptionConfig
Update keys at runtime:
import { setEncryptionConfig } from '@scripturecoder/rn-alpha-hooks';
function handleLogin(userSession) {
const userKey = deriveKeyFromSession(userSession);
setEncryptionConfig({
key: userKey,
iv: userSession.substring(0, 16),
});
}generateEncryptionConfig
Generate random keys:
import { generateEncryptionConfig } from '@scripturecoder/rn-alpha-hooks';
const config = generateEncryptionConfig();
console.log('Key:', config.key);
console.log('IV:', config.iv);
// Store these securely!isValidEncryptionConfig
Validate encryption config:
import { isValidEncryptionConfig } from '@scripturecoder/rn-alpha-hooks';
const config = { key: 'MyKey123', iv: 'MyIV456' };
if (isValidEncryptionConfig(config)) {
setEncryptionConfig(config);
}TypeScript Support
Full type safety with EncryptionOptions:
import type { EncryptionOptions } from '@scripturecoder/rn-alpha-hooks';
const options: EncryptionOptions = {
request: 'full',
response: ['token', 'data'],
};
useMutation('endpoint', { encrypted: options });Error Handling
const [mutate] = useMutation('endpoint', { encrypted: true });
try {
await mutate({ data: 'test' });
} catch (error) {
if (error.message.includes('Encryption')) {
// Handle encryption configuration errors
console.error('Encryption keys not configured!');
}
}Security Best Practices
- Never hardcode keys - Use environment variables or secure storage
- 16 characters required - Keys and IVs must be exactly 16 chars for AES-128
- Store keys securely - Use react-native-keychain or similar
- Rotate keys - Implement key rotation strategy
- Use HTTPS - Encryption complements but doesn't replace secure transport
- Encrypt only sensitive data - Don't encrypt unnecessarily
- Never log keys - Only log in development with warnings
Example secure setup:
import { setEncryptionConfig, isValidEncryptionConfig } from '@scripturecoder/rn-alpha-hooks';
import * as Keychain from 'react-native-keychain';
async function loadEncryptionKeys() {
try {
const credentials = await Keychain.getGenericPassword({ service: 'encryption' });
if (credentials) {
const config = {
key: credentials.username,
iv: credentials.password,
};
if (isValidEncryptionConfig(config)) {
setEncryptionConfig(config);
}
}
} catch (error) {
console.error('Failed to load encryption keys:', error);
}
}Encryption Options Reference
interface EncryptionOptions {
enabled?: boolean; // Enable/disable encryption
request?: 'full' | string[]; // Encryption strategy for request
response?: 'full' | string[]; // Decryption strategy for response
}| Value | Behavior |
|-------|----------|
| 'full' | Encrypts entire body (request) or entire response |
| ['field1', 'field2'] | Encrypts/decrypts only specified fields |
| false or undefined | No encryption/decryption |
Utilities Reference
HTTP Utilities
createAbortController
import { createAbortController } from '@scripturecoder/rn-alpha-hooks';
const { controller, cleanup } = createAbortController();
// Use signal in request
await fetch(url, { signal: controller.signal });
// Cleanup when done
cleanup();createTimeoutController
import { createTimeoutController } from '@scripturecoder/rn-alpha-hooks';
const { controller, cleanup } = createTimeoutController(5000); // 5s timeout
try {
await fetch(url, { signal: controller.signal });
} finally {
cleanup();
}combineAbortSignals
import { combineAbortSignals } from '@scripturecoder/rn-alpha-hooks';
const userController = new AbortController();
const timeoutController = new AbortController();
const combined = combineAbortSignals([
userController.signal,
timeoutController.signal
]);
await fetch(url, { signal: combined.signal });isAbortError
import { isAbortError } from '@scripturecoder/rn-alpha-hooks';
try {
await fetch(url, { signal });
} catch (error) {
if (isAbortError(error)) {
console.log('Request was cancelled');
} else {
console.error('Request failed:', error);
}
}Cache Utilities
import {
isCacheExpired,
isCacheStale,
isCacheFresh,
getCacheData,
createCacheEntry,
getCacheAge
} from '@scripturecoder/rn-alpha-hooks';
// Check cache status
const entry = getCacheData('user:123');
if (isCacheFresh(entry)) {
// Use cached data
} else if (isCacheStale(entry)) {
// Show cache but refetch
} else {
// Fetch new data
}
// Create cache entry with TTL
const entry = createCacheEntry(data, {
ttl: 5 * 60 * 1000, // 5 minutes
staleTime: 30 * 1000, // 30 seconds
});
// Check cache age
const age = getCacheAge(entry);
console.log(`Cache is ${age}ms old`);Error Handlers
import {
extractErrorMessage,
isSuccessStatus,
isAuthError,
shouldRetry,
createErrorResponse
} from '@scripturecoder/rn-alpha-hooks';
// Extract error message from various error types
const message = extractErrorMessage(error);
// Check if status code is success
if (isSuccessStatus(200)) {
// Handle success
}
// Check if error is auth-related
if (isAuthError(error, status)) {
// Redirect to login
}
// Determine if request should be retried
if (shouldRetry(error, status)) {
// Retry request
}Other Utilities
formatMoney
import { formatMoney, naira } from '@scripturecoder/rn-alpha-hooks';
const formatted = formatMoney(1500); // "1,500.00"
const withSymbol = naira + formatMoney(1500); // "₦1,500.00"storage (MMKV)
import { storage } from '@scripturecoder/rn-alpha-hooks';
// Set item
storage.setItem('key', 'value');
// Get item
const value = storage.getItem('key');
// Remove item
storage.removeItem('key');
// Set object
storage.setItem('user', JSON.stringify({ name: 'John' }));
// Get object
const user = JSON.parse(storage.getItem('user'));dayjs
import dayjs from '@scripturecoder/rn-alpha-hooks';
// Format dates
const formatted = dayjs().format('YYYY-MM-DD');
// Relative time
const relative = dayjs('2024-01-01').fromNow(); // "6 months ago"
// Timezone support included
const utc = dayjs.utc();TypeScript Support
Exported Types
import type {
// Hook Options
QueryOptions,
MutationOptions,
// Hook Results
QueryResult,
MutationResult,
// Configuration
AlphaConfig,
EncryptionConfig,
// Store
RootState,
AppDispatch,
CoreAppState,
CustomReducers,
StoreOptions,
// Network
NetworkPolicy,
ConcatStrategy,
// Cache
CacheEntry,
CacheState,
// Utilities
Route,
Method,
TimingInfo,
} from '@scripturecoder/rn-alpha-hooks';Type-Safe Hooks
interface User {
id: string;
name: string;
email: string;
}
// Typed query
const { data } = useQuery<User>('getUser', {
variables: { id: '123' }
});
// data is typed as User
// Typed mutation
const [createUser] = useMutation<User>('createUser');
const result = await createUser({ name: 'John', email: '[email protected]' });
// result.data is typed as UserCustom Store Types
import { createTypedSelector, createTypedDispatch } from '@scripturecoder/rn-alpha-hooks';
// Create typed hooks for your custom store
export const useAppSelector = createTypedSelector<{
settings: SettingsState;
preferences: PreferencesState;
}>();
export const useAppDispatch = createTypedDispatch();
// Usage with full type safety
function Component() {
const theme = useAppSelector((state) => state.settings.theme);
const dispatch = useAppDispatch();
return <View>...</View>;
}Custom Hook Wrappers
Wrap base hooks to add custom logic like encryption, logging, or analytics:
Encrypted Hooks
// src/hooks/useAppQuery.ts
import { useQuery, encrypt, decrypt } from '@scripturecoder/rn-alpha-hooks';
export function useAppQuery<T>(route: string, options?: any) {
const { variables, ...rest } = options || {};
// Encrypt sensitive fields before sending
const encryptedVars = variables ? {
...variables,
password: variables.password ? encrypt(variables.password) : undefined,
} : undefined;
const result = useQuery<T>(route, { ...rest, variables: encryptedVars });
// Decrypt response if needed
const decryptedData = result.data ? {
...result.data,
// Decrypt specific fields
} : result.data;
return {
...result,
data: decryptedData,
};
}Logged Hooks
// src/hooks/useAppQuery.ts
import { useQuery } from '@scripturecoder/rn-alpha-hooks';
import analytics from './analytics';
export function useAppQuery<T>(route: string, options?: any) {
const result = useQuery<T>(route, {
...options,
onCompleted: (data) => {
analytics.log('query_success', { route, data });
options?.onCompleted?.(data);
},
onError: (error, status) => {
analytics.log('query_error', { route, error, status });
options?.onError?.(error, status);
},
});
return result;
}See examples/encrypted-hooks.tsx for complete patterns.
Complete Examples
Authentication Flow
import { useMutation, useApp } from '@scripturecoder/rn-alpha-hooks';
function LoginScreen() {
const [login, { loading, error }] = useMutation('login');
const { setAuth, setUser } = useApp();
const handleLogin = async (credentials) => {
const result = await login(credentials);
if ('data' in result) {
// Save auth tokens
setAuth({
accessToken: result.data.accessToken,
refreshToken: result.data.refreshToken,
});
// Save user data
setUser(result.data.user);
// Navigate to home
navigation.replace('Home');
} else {
Alert.alert('Error', result.error);
}
};
return (
<Form onSubmit={handleLogin}>
{loading && <Spinner />}
{error && <ErrorText>{error}</ErrorText>}
</Form>
);
}CRUD Operations
function UserManagement() {
// List users
const { data: users, loading, refetch } = useQuery('getUsers');
// Create user
const [createUser] = useMutation('createUser');
// Update user
const [updateUser] = useMutation('updateUser');
// Delete user
const [deleteUser] = useMutation('deleteUser');
const handleCreate = async (userData) => {
const result = await createUser(userData);
if ('data' in result) {
refetch(); // Refresh list
}
};
const handleUpdate = async (userId, updates) => {
const result = await updateUser({ userId, ...updates });
if ('data' in result) {
refetch();
}
};
const handleDelete = async (userId) => {
const result = await deleteUser({ userId });
if ('data' in result) {
refetch();
}
};
return (
<View>
<UserList
users={users}
loading={loading}
onCreate={handleCreate}
onUpdate={handleUpdate}
onDelete={handleDelete}
/>
</View>
);
}File Upload
function FileUploader() {
const [uploadFile, { loading }] = useMutation('uploadFile');
const [selectedFile, setSelectedFile] = useState(null);
const handleUpload = async () => {
if (!selectedFile) return;
const result = await uploadFile({
file: {
uri: selectedFile.uri,
type: selectedFile.type,
name: selectedFile.name,
},
description: 'My file',
});
if ('data' in result) {
Alert.alert('Success', 'File uploaded!');
setSelectedFile(null);
} else {
Alert.alert('Error', result.error);
}
};
return (
<View>
<FilePicker onSelect={setSelectedFile} />
<Button onPress={handleUpload} disabled={!selectedFile || loading}>
Upload
</Button>
{loading && <Spinner />}
</View>
);
}Offline-First Pattern
function CreatePost() {
const [createPost, { loading }] = useMutation('createPost');
const { connected } = useApp();
const handleCreate = async (postData) => {
// Mutation is automatically queued if offline
const result = await createPost(postData);
if ('data' in result) {
if (connected) {
Alert.alert('Success', 'Post published!');
} else {
Alert.alert('Queued', 'Post will be published when online');
}
} else if (result.error !== 'Offline') {
Alert.alert('Error', result.error);
}
};
return (
<View>
{!connected && <OfflineBanner />}
<PostForm onSubmit={handleCreate} loading={loading} />
</View>
);
}API Reference
Quick Reference
Hooks
| Hook | Purpose | Returns |
|------|---------|---------|
| useQuery | Fetch data with caching | { data, loading, error, refetch, ... } |
| useQueryAsync | Imperative data fetching | fetchFunction |
| useMutation | POST/PUT/DELETE operations | [mutateFunction, { loading, error, data }] |
| useMutationAsync | Imperative mutations | [mutateFunction, { loading, error }] |
| useCache | Cache manipulation | { get, set, update, del, invalidate, ... } |
| useApp | Core app state | { auth, user, connected, setAuth, ... } |
| useDispatch | Redux dispatch | dispatch |
| useSelector | Redux selector | selectedValue |
| useAlphaConfig | Access/update config | [config, setConfig] |
Components
| Component | Purpose |
|-----------|---------|
| AlphaProvider | Main provider with config |
| AppProvider | Core app context provider |
Utilities
| Utility | Purpose |
|---------|---------|
| formatMoney | Currency formatting |
| encrypt/decrypt | Data encryption |
| storage | MMKV storage wrapper |
| createAbortController | Create abort controller |
| isAbortError | Check if error is cancellation |
| extractErrorMessage | Extract error messages |
| isCacheExpired | Check cache expiry |
Constants
| Constant | Value | Description |
|----------|-------|-------------|
| DEFAULT_CACHE_TTL | 300000 | 5 minutes |
| DEFAULT_STALE_TIME | 0 | Always stale |
| MAX_CACHE_SIZE | 100 | Max cache entries |
| NETWORK_TIMEOUT | 30000 | 30 seconds |
Troubleshooting
Common Issues
Config not applying
Make sure AlphaProvider wraps your entire app:
// ✅ Correct
<AlphaProvider config={{...}}>
<NavigationContainer>
<App />
</NavigationContainer>
</AlphaProvider>
// ❌ Wrong
<NavigationContainer>
<AlphaProvider config={{...}}>
<App />
</AlphaProvider>
</NavigationContainer>Routes not found
Check that your route keys match:
// Config
paths: {
getUser: 'GET:/users/:id', // ✅ Correct key
}
// Usage
useQuery('getUser', { variables: { id: '123' }}) // ✅ Matches
useQuery('user', { variables: { id: '123' }}) // ❌ Wrong keyBaseURL issues
Ensure baseURL doesn't have trailing slash:
// ✅ Correct
baseUrl: 'https://api.example.com'
// ❌ Wrong
baseUrl: 'https://api.example.com/'Cache not working
Check network policy and TTL settings:
// Enable caching
const { data } = useQuery('getUser', {
networkPolicy: 'cache-first', // Not 'network-only'
ttl: 5 * 60 * 1000, // Set appropriate TTL
});Request cancellation not working
Ensure abort is called in cleanup:
useEffect(() => {
return () => abort(); // Must return cleanup function
}, [dependencies]);FAQ
Q: How do I clear all cache?
const cache = useCache();
cache.invalidateAll();Q: Can I use multiple API endpoints?
Yes, use runtime configuration:
import { setHttpConfig } from '@scripturecoder/rn-alpha-hooks';
setHttpConfig({ baseUrl: 'https://different-api.com' });Q: How do I handle 401 errors globally?
Use HTTP interceptors or wrap hooks:
const result = useQuery('getUser');
useEffect(() => {
if (result.error && result.error.includes('401')) {
navigation.navigate('Login');
}
}, [result.error]);Q: Can I use this with Expo?
Yes, just install the required dependencies. Some native modules may require custom dev clients.
Q: How do I debug network requests?
Enable debug mode:
<AlphaProvider config={{ debug: true }}>Migration Guide
From v0.1.x to v0.2.0
Breaking Changes
Encryption is now configurable
// Before: Keys were hardcoded // After: Configure via AlphaProvider <AlphaProvider config={{ encryption: { key: process.env.ENCRYPTION_KEY, iv: process.env.ENCRYPTION_IV, } }} />Store customization changes
// Before: Used specific app-context fields const { colorMode, biometric } = useApp(); // After: Use custom reducers const settings = useSelector((state) => state.settings);
New Features
- ✅ Configurable encryption keys
- ✅ Flexible store with custom reducers
- ✅ TypeScript helpers for custom stores
- ✅ Enhanced encryption utilities
From Legacy HTTP Service
// Before
const res = await http(path, 'POST', data, true, token, false);
// After
const res = await http(path, 'POST', data, {
returnStatus: true,
auth: token,
returnText: false,
signal: abortController.signal
});Contributing
We welcome contributions! Here's how you can help:
- Report bugs - Open an issue with reproduction steps
- Suggest features - Share your ideas for improvements
- Submit PRs - Fix bugs or add features
- Improve docs - Help make documentation better
Development
# Clone repository
git clone https://github.com/scripturecoder/rn-alpha.git
# Install dependencies
cd rn-alpha/packages/rn-alpha
npm install
# Run type check
npx tsc --noEmit
# Build package
npm run build
# Run tests
npm testProject Structure
src/
index.ts # Public export surface
hooks/ # Custom React hooks
store/ # Redux store, reducers, contexts
utils/ # Utility functions
config.ts # Configuration
paths.ts # API route definitions
types.ts # TypeScript type definitionsLicense
MIT License - See LICENSE for details.
Support
- 📖 Documentation
- 🐛 Report Issues
- 💬 Discussions
- 📧 Email: [email protected]
Made with ❤️ by scripturecoder
