@natify/navigation-react
v1.0.2
Published
Navigation adapter for Natify Framework using `@react-navigation/native`.
Readme
@natify/navigation-react
Navigation adapter for Natify Framework using @react-navigation/native.
Installation
pnpm add @natify/navigation-react @react-navigation/native @react-navigation/native-stack react-native-screens react-native-safe-area-contextiOS
cd ios && pod installAndroid
No additional configuration required.
Usage
1. Define Route Types
// types/navigation.ts
export type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Settings: undefined;
Login: undefined;
ProductDetail: { productId: string; name: string };
};2. Configure the Adapter
With NatifyApp (Recommended)
// App.tsx
import { NatifyApp } from "@natify/core";
import { createReactNavigationAdapter } from "@natify/navigation-react";
// Create adapter with configuration
const navigationAdapter = createReactNavigationAdapter({
theme: 'dark',
screenOptions: {
headerStyle: { backgroundColor: '#000' },
headerTintColor: '#fff',
},
deeplinkConfig: {
prefixes: ['myapp://', 'https://myapp.com'],
},
});
export default function App() {
return (
<NatifyApp
adapters={{ navigation: navigationAdapter }}
modules={[AuthModule, ProductsModule]}
/>
);
}With NatifyProvider (Level 1 - Abstraction Only)
// App.tsx
import { NatifyProvider } from "@natify/core";
import { createReactNavigationAdapter } from "@natify/navigation-react";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
// Create adapter with configuration
const navigationAdapter = createReactNavigationAdapter({
theme: 'light',
screenOptions: {
headerShown: true,
},
});
const Stack = createNativeStackNavigator<RootStackParamList>();
function App() {
return (
<NatifyProvider adapters={{ navigation: navigationAdapter }}>
<NavigationContainer ref={navigationAdapter.navigationRef}>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
</NatifyProvider>
);
}3. Usage in Components
import { useAdapter, NavigationPort } from "@natify/core";
function HomeScreen() {
const navigation = useAdapter<NavigationPort>("navigation");
const goToProfile = () => {
navigation.navigate("Profile", { userId: "123" });
};
const goToSettings = () => {
navigation.navigate("Settings");
};
return (
<View>
<Button title="View Profile" onPress={goToProfile} />
<Button title="Settings" onPress={goToSettings} />
</View>
);
}4. Usage Outside React (Services, Interceptors)
One of the main advantages of the adapter is being able to navigate from anywhere:
// services/auth.service.ts
import { navigationAdapter } from "../config/natify";
class AuthService {
async logout() {
await this.clearTokens();
// Navigate to login from a service
navigationAdapter.reset([{ name: "Login" }]);
}
}
// In an HTTP interceptor
const httpAdapter = new AxiosHttpAdapter(
"https://api.example.com",
{},
{
onResponseError: async (error) => {
if (error.response?.status === 401) {
// Navigate to login when token expires
navigationAdapter.reset([{ name: "Login" }]);
}
return Promise.reject(error);
},
}
);API
NavigationPort
| Method | Return | Description |
|--------|--------|-------------|
| navigate(route, params?, options?) | void | Navigates to a screen |
| goBack() | boolean | Goes back to previous screen |
| popToTop() | void | Goes back to the beginning of the stack |
| replace(route, params?) | void | Replaces current screen |
| reset(routes) | void | Resets navigation stack |
| getCurrentRoute() | string \| undefined | Gets current route |
| getCurrentParams() | T \| undefined | Gets current parameters |
| canGoBack() | boolean | Checks if can go back |
| setOptions(options) | void | Configures screen options |
| addListener(event, callback) | () => void | Adds event listener |
Navigation Examples
const navigation = useAdapter<NavigationPort>("navigation");
// Simple navigation
navigation.navigate("Home");
// With parameters
navigation.navigate("Profile", { userId: "123" });
// Replace (doesn't add to history)
navigation.replace("Home");
// Go back
if (navigation.canGoBack()) {
navigation.goBack();
}
// Complete reset (useful after login/logout)
navigation.reset([
{ name: "Home" },
{ name: "Profile", params: { userId: "123" } },
]);
// Configure header dynamically
navigation.setOptions({
title: "New Title",
headerShown: true,
});Listening to Events
useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
console.log("Screen focused - reload data");
fetchData();
});
return unsubscribe;
}, []);Common Patterns
Authentication Flow
function useAuthNavigation() {
const navigation = useAdapter<NavigationPort>("navigation");
const secureStorage = useAdapter<StoragePort>("secureStorage");
const onLoginSuccess = async (token: string) => {
await secureStorage.setItem("auth_token", token);
// Reset stack so user can't go back to login
navigation.reset([{ name: "Home" }]);
};
const onLogout = async () => {
await secureStorage.clear();
// Reset to login
navigation.reset([{ name: "Login" }]);
};
return { onLoginSuccess, onLogout };
}Deep Linking
The adapter supports deeplinks automatically. Configure prefixes when creating the adapter:
// App.tsx
import { createReactNavigationAdapter } from '@natify/navigation-react';
// Create adapter with deeplinks
const navigationAdapter = createReactNavigationAdapter({
deeplinkConfig: {
prefixes: ['myapp://', 'https://myapp.com'],
},
});
// In NatifyApp (passed automatically)
<NatifyApp
adapters={{ navigation: navigationAdapter }}
modules={[AuthModule, ProductsModule]}
/>With theme and screenOptions:
const navigationAdapter = createReactNavigationAdapter({
theme: 'dark', // 'light' | 'dark' | Theme object
screenOptions: {
headerStyle: { backgroundColor: '#000' },
headerTintColor: '#fff',
headerShown: true,
},
deeplinkConfig: {
prefixes: ['myapp://'],
},
});Per-screen configuration (Recommended):
Define deeplink configuration for each screen when creating the module:
import { createModule } from "@natify/core";
export const ProductsModule = createModule("products", "Products")
.screen({
name: "ProductList",
component: ProductListScreen,
// No deeplink → automatically generated: "products/productlist"
})
.screen({
name: "ProductDetail",
component: ProductDetailScreen,
deeplink: {
path: "product/:productId",
parse: {
productId: Number, // Converts to number
},
},
})
.build();
// Resulting URLs:
// myapp://products/productlist (automatic)
// myapp://product/123 (custom) → ProductDetail with { productId: 123 }Automatically generated URLs (without configuration):
myapp://auth/login→auth/Loginmyapp://products/productlist→products/ProductList
With custom per-screen configuration:
myapp://product/123→products/ProductDetailwith{ productId: 123 }
See DEEPLINKS.md for complete deeplink documentation.
Conditional Navigation
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const navigation = useAdapter<NavigationPort>("navigation");
const { isAuthenticated } = useAuth();
useEffect(() => {
if (!isAuthenticated) {
navigation.replace("Login");
}
}, [isAuthenticated]);
if (!isAuthenticated) {
return null;
}
return <>{children}</>;
}Advanced Typing
For complete parameter typing, you can use React Navigation's native hook:
import { useNavigation } from "@natify/navigation-react";
import type { NativeStackNavigationProp } from "@natify/navigation-react";
import type { RootStackParamList } from "../types/navigation";
type ProfileScreenNavigationProp = NativeStackNavigationProp<
RootStackParamList,
"Profile"
>;
function ProfileScreen() {
// Complete route and parameter typing
const navigation = useNavigation<ProfileScreenNavigationProp>();
navigation.navigate("ProductDetail", {
productId: "123",
name: "Product", // TypeScript validates parameters
});
}Adapter Configuration
Configuration Options
interface ReactNavigationAdapterConfig {
/**
* Deeplink configuration (optional)
*/
deeplinkConfig?: {
prefixes: string[];
config?: LinkingOptions['config'];
filter?: (url: string) => boolean;
getInitialURL?: () => Promise<string | null | undefined>;
subscribe?: (listener: (url: string) => void) => () => void;
};
/**
* Navigation theme ('light' | 'dark' | React Navigation Theme object)
*/
theme?: 'light' | 'dark' | Theme;
/**
* Global screen options
* Applied to all screens in the stack navigator
*/
screenOptions?: ScreenOptions;
}Configuration Examples
// Minimal configuration
const adapter1 = createReactNavigationAdapter();
// Theme only
const adapter2 = createReactNavigationAdapter({
theme: 'dark',
});
// Theme and screenOptions
const adapter3 = createReactNavigationAdapter({
theme: 'dark',
screenOptions: {
headerStyle: { backgroundColor: '#1a1a1a' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: 'bold' },
},
});
// Complete configuration
const adapter4 = createReactNavigationAdapter({
theme: 'dark',
screenOptions: {
headerShown: true,
animation: 'slide_from_right',
},
deeplinkConfig: {
prefixes: ['myapp://', 'https://myapp.com'],
},
});Considerations
- Create adapter outside components: Must be a global instance
- Configuration in adapter:
themeandscreenOptionsare configured when creating the adapter, not inNatifyApp - Verify isReady: The adapter handles this internally, but keep in mind that navigation doesn't work before the container is mounted
