React Hook-based permission system with standardized request/response patterns and automatic lifecycle management.
Core types for representing permission states and responses.
/**
* Enumeration of possible permission states
*/
enum PermissionStatus {
/** User has granted the permission */
GRANTED = 'granted',
/** User hasn't granted or denied the permission yet */
UNDETERMINED = 'undetermined',
/** User has denied the permission */
DENIED = 'denied',
}
/**
* Permission expiration time - currently all permissions are permanent
*/
type PermissionExpiration = 'never' | number;
/**
* Standard permission response object returned by get and request functions
*/
interface PermissionResponse {
/** Current permission status */
status: PermissionStatus;
/** When the permission expires */
expires: PermissionExpiration;
/** Convenience boolean indicating if permission is granted */
granted: boolean;
/** Whether user can be asked again or should be directed to Settings */
canAskAgain: boolean;
}Usage Examples:
import { PermissionStatus, type PermissionResponse } from "expo-modules-core";
// Check permission status
function handlePermissionResponse(response: PermissionResponse) {
switch (response.status) {
case PermissionStatus.GRANTED:
console.log("Permission granted!");
// Proceed with protected functionality
break;
case PermissionStatus.DENIED:
if (response.canAskAgain) {
console.log("Permission denied, but can ask again");
// Show explanation and request again
} else {
console.log("Permission permanently denied, direct to settings");
// Guide user to system settings
}
break;
case PermissionStatus.UNDETERMINED:
console.log("Permission not yet requested");
// First time, can request permission
break;
}
// Use convenience property
if (response.granted) {
enableFeature();
} else {
showPermissionRequiredMessage();
}
}Factory function for creating permission hooks with standardized patterns.
/**
* Creates a new permission hook with built-in permission methods
* @param methods - Object containing request and get methods
* @returns Hook function for managing permissions
*/
function createPermissionHook<Permission extends PermissionResponse, Options extends object>(
methods: PermissionHookMethods<Permission, Options>
): (options?: PermissionHookOptions<Options>) =>
[Permission | null, () => Promise<Permission>, () => Promise<Permission>];
/**
* Permission methods required for creating a hook
*/
interface PermissionHookMethods<Permission extends PermissionResponse, Options = never> {
/** Method that requests permission from user */
requestMethod: (options?: Options) => Promise<Permission>;
/** Method that only fetches current permission status */
getMethod: (options?: Options) => Promise<Permission>;
}
/**
* Options for controlling permission hook behavior
*/
interface PermissionHookOptions<Options extends object> extends Options {
/** Automatically fetch permission status on mount */
get?: boolean;
/** Automatically request permission on mount */
request?: boolean;
}Usage Examples:
import { createPermissionHook, PermissionResponse, PermissionStatus } from "expo-modules-core";
// Define permission-specific response type
interface CameraPermissionResponse extends PermissionResponse {
canAskAgain: boolean;
// Additional camera-specific properties
}
// Define options for camera permission
interface CameraPermissionOptions {
allowsEditing?: boolean;
aspect?: [number, number];
}
// Create camera permission methods
const cameraPermissionMethods = {
async requestMethod(options?: CameraPermissionOptions): Promise<CameraPermissionResponse> {
// Implementation would call native camera permission request
const nativeResponse = await requestCameraPermissionNative(options);
return {
status: nativeResponse.granted ? PermissionStatus.GRANTED : PermissionStatus.DENIED,
expires: 'never',
granted: nativeResponse.granted,
canAskAgain: nativeResponse.canAskAgain,
};
},
async getMethod(options?: CameraPermissionOptions): Promise<CameraPermissionResponse> {
// Implementation would check current camera permission status
const nativeResponse = await getCameraPermissionNative();
return {
status: nativeResponse.status,
expires: 'never',
granted: nativeResponse.granted,
canAskAgain: nativeResponse.canAskAgain,
};
},
};
// Create the hook
const useCameraPermissions = createPermissionHook(cameraPermissionMethods);
// Use in component
function CameraComponent() {
// Hook returns [status, requestPermission, getPermission]
const [permission, requestPermission, getPermission] = useCameraPermissions({
get: true, // Automatically fetch status on mount
allowsEditing: true, // Custom option
});
const handleCameraPress = async () => {
if (!permission?.granted) {
const newPermission = await requestPermission();
if (!newPermission.granted) {
alert("Camera permission required");
return;
}
}
// Open camera
openCamera();
};
if (permission === null) {
return <div>Loading permissions...</div>;
}
return (
<button onClick={handleCameraPress} disabled={!permission.granted}>
{permission.granted ? "Open Camera" : "Grant Camera Permission"}
</button>
);
}Examples of complex permission scenarios and patterns.
import { createPermissionHook, PermissionResponse } from "expo-modules-core";
// Location permission with multiple precision levels
interface LocationPermissionResponse extends PermissionResponse {
accuracy: 'coarse' | 'fine' | 'none';
}
interface LocationPermissionOptions {
accuracy?: 'coarse' | 'fine';
backgroundLocation?: boolean;
}
const useLocationPermissions = createPermissionHook({
async requestMethod(options?: LocationPermissionOptions): Promise<LocationPermissionResponse> {
// Request location permission with specified accuracy
const result = await requestLocationPermission(options);
return result;
},
async getMethod(options?: LocationPermissionOptions): Promise<LocationPermissionResponse> {
// Get current location permission status
const result = await getLocationPermission();
return result;
},
});
// Multi-permission component
function LocationAwareComponent() {
const [locationPermission, requestLocation] = useLocationPermissions({
get: true,
accuracy: 'fine',
});
const [backgroundPermission, requestBackground] = useLocationPermissions({
backgroundLocation: true,
});
const handleEnableLocation = async () => {
// Request foreground location first
const foreground = await requestLocation();
if (foreground.granted && foreground.accuracy === 'fine') {
// Then request background location
const background = await requestBackground();
if (background.granted) {
console.log("Both permissions granted");
enableBackgroundTracking();
}
}
};
return (
<div>
<p>Foreground Location: {locationPermission?.status}</p>
<p>Background Location: {backgroundPermission?.status}</p>
<button onClick={handleEnableLocation}>
Enable Location Tracking
</button>
</div>
);
}
// Permission wrapper component
function withPermissions<T extends object>(
WrappedComponent: React.ComponentType<T>,
requiredPermissions: string[]
) {
return function PermissionWrapper(props: T) {
const [allGranted, setAllGranted] = useState(false);
useEffect(() => {
// Check all required permissions
Promise.all(
requiredPermissions.map(checkPermission)
).then(results => {
setAllGranted(results.every(r => r.granted));
});
}, []);
if (!allGranted) {
return <PermissionGate permissions={requiredPermissions} />;
}
return <WrappedComponent {...props} />;
};
}
// Usage
const LocationEnabledMap = withPermissions(MapComponent, ['location']);Guidelines for effective permission handling:
import { createPermissionHook, PermissionStatus } from "expo-modules-core";
// ✅ Good: Explain why permission is needed
function PermissionExplanation({ onGrant }: { onGrant: () => void }) {
const [permission, requestPermission] = useCameraPermissions();
const handleRequest = async () => {
if (permission?.status === PermissionStatus.DENIED && !permission.canAskAgain) {
// Guide to settings
alert("Please enable camera access in Settings");
return;
}
const result = await requestPermission();
if (result.granted) {
onGrant();
}
};
return (
<div>
<h3>Camera Access Required</h3>
<p>This app needs camera access to take photos for your profile.</p>
<button onClick={handleRequest}>Grant Camera Access</button>
</div>
);
}
// ✅ Good: Handle all permission states
function PermissionAwareFeature() {
const [permission, requestPermission] = useLocationPermissions({ get: true });
const renderPermissionState = () => {
if (!permission) {
return <div>Checking permissions...</div>;
}
switch (permission.status) {
case PermissionStatus.GRANTED:
return <LocationFeature />;
case PermissionStatus.UNDETERMINED:
return (
<button onClick={requestPermission}>
Enable Location Services
</button>
);
case PermissionStatus.DENIED:
return permission.canAskAgain ? (
<div>
<p>Location access is needed for this feature.</p>
<button onClick={requestPermission}>Try Again</button>
</div>
) : (
<div>
<p>Please enable location access in Settings</p>
<button onClick={openSettings}>Open Settings</button>
</div>
);
default:
return <div>Unknown permission state</div>;
}
};
return <div>{renderPermissionState()}</div>;
}
// ❌ Bad: Not handling denied permissions
function BadPermissionComponent() {
const [permission, requestPermission] = useCameraPermissions();
// Only checks granted, ignores denied/undetermined
return permission?.granted ? (
<CameraView />
) : (
<button onClick={requestPermission}>Enable Camera</button>
);
}/**
* Generic permission hook return type
*/
type PermissionHookReturn<Permission extends PermissionResponse> = [
Permission | null, // Current permission status
() => Promise<Permission>, // Request permission function
() => Promise<Permission> // Get current permission function
];
/**
* Base permission response interface
*/
interface PermissionResponse {
status: PermissionStatus;
expires: PermissionExpiration;
granted: boolean;
canAskAgain: boolean;
}