Skip to content

React Native ​

React Native bindings for effector router with React Navigation integration.

Overview ​

@effector/router-react-native bridges Effector Router's state management with React Navigation's native UI components. This package allows you to:

  • Manage navigation state with effector router
  • Render UI with React Navigation's native navigators
  • Access the full React Navigation API and styling options
  • Navigate declaratively through route events

How It Works ​

┌─────────────────┐
│ effector router │  ← Manages state
└────────┬────────┘
         │ syncs
         ▼
┌─────────────────┐
│ React Navigation│  ← Renders UI
│   (Stack/Tabs)  │
└─────────────────┘
  1. Effector router manages which routes are open, their parameters, and navigation state
  2. React Navigation handles UI rendering, animations, and platform-specific behavior
  3. The adapters sync state between both systems

Installation ​

bash
npm install @effector/router @effector/router-react-native \
  @react-navigation/native @react-navigation/stack @react-navigation/bottom-tabs

# Also install React Navigation dependencies
npm install react-native-screens react-native-safe-area-context

@effector/router-react-native re-exports platform-neutral React bindings. Browser-only Link, useLink, and LinkProps remain in @effector/router-react. Import the shared router API from @effector/router.

Re-exported React bindings ​

The following APIs are re-exported from @effector/router-react. Their behavior is the same; each page links to the complete React API reference:

The package also re-exports the CreateLazyRouteViewProps, CreateRouteViewProps, and RouteView types.

Quick Example ​

tsx
import {
  createNavigationContainerRef,
  NavigationContainer,
} from '@react-navigation/native';
import { createRoute, createRouter } from '@effector/router';
import {
  createRouteView,
  RouterProvider,
  createStackNavigator,
} from '@effector/router-react-native';

// 1. Define routes
const homeRoute = createRoute({ path: '/home' });
const detailsRoute = createRoute({ path: '/details/:id' });

// 2. Create router
const router = createRouter({
  routes: [homeRoute, detailsRoute],
});

// 3. Create screens
const HomeScreen = createRouteView({
  route: homeRoute,
  view: () => <Text>Home Screen</Text>,
});

const DetailsScreen = createRouteView({
  route: detailsRoute,
  view: () => <Text>Details Screen</Text>,
});

// 4. Create navigator
const StackNavigator = createStackNavigator({
  router,
  routes: [HomeScreen, DetailsScreen],
  screenOptions: {
    headerStyle: { backgroundColor: '#f4511e' },
  },
});
const navigationRef = createNavigationContainerRef();

// 5. Use in app
export default function App() {
  return (
    <RouterProvider router={router}>
      <NavigationContainer ref={navigationRef}>
        <StackNavigator navigationRef={navigationRef} />
      </NavigationContainer>
    </RouterProvider>
  );
}

// 6. Navigate programmatically
homeRoute.open();
detailsRoute.open({ params: { id: '123' } });

Available Navigators ​

Stack Navigator ​

Full-screen navigation with stack-based transitions. Perfect for hierarchical navigation patterns.

tsx
import { createStackNavigator } from '@effector/router-react-native';

const StackNavigator = createStackNavigator({
  router,
  routes: [HomeScreen, DetailsScreen],
});

Bottom Tabs Navigator ​

Tab-based navigation with a bottom tab bar. Ideal for primary app navigation.

tsx
import { createBottomTabsNavigator } from '@effector/router-react-native';

const TabsNavigator = createBottomTabsNavigator({
  router,
  routes: [HomeTab, SearchTab, ProfileTab],
});

All navigation happens through Effector Router, not React Navigation directly:

tsx
// ✅ Navigate via effector router
homeRoute.open();
detailsRoute.open({ params: { id: '123' } });

// ❌ Don't use React Navigation's navigation prop
navigation.navigate('Details'); // Avoid this

This approach provides:

  • Centralized navigation logic
  • Easy testing (trigger events in tests)
  • Router-owned state and deterministic route events

The app owns NavigationContainer and its navigationRef. Pass the same ref to the navigator component returned by createStackNavigator or createBottomTabsNavigator; the binding does not create a container, Router, or history adapter. It subscribes to native ready and state notifications, reads a complete root snapshot from the ref, handles an already-ready ref, and cleans up listeners on unmount.

Screen names are complete registered path templates, including parent segments (for example, /users/:userId/settings); no positional/index fallback is generated. A route with required path parameters cannot be selected through initialRouteName and must be opened by Router with real params.

Router-to-native synchronization is readiness-gated. Before the app-owned ref is ready, the binding retains only the latest Router target and sends no native command. Once ready, it navigates with route params and preserves Router's replace intent; native state notifications are treated as complete snapshots and matching binding-originated updates are not echoed back.

Native screen focus, removal/back events, completed closing gestures, and tab presses are translated into existing Router open/close events. Handlers keep the native payload types, prevent native selection where required, and bind callbacks to the rendered Effector scope; no public native-intent unit is introduced.

The package integration fixture renders both navigator shapes with an app-owned ref and covers screen/options rendering, readiness races, params, native echo suppression, tab intent, and listener cleanup.

React Navigation Features ​

While navigation is managed by Effector Router, you still get the React Navigation features covered by the navigator configuration:

  • Native animations and transitions
  • Header customization
  • Tab bar customization
  • Screen options and configuration
  • Platform-specific behavior

Deep linking, persistence, time-travel debugging, and gesture-specific flows are outside this adapter's documented contract until each scenario has an integration test. They can still be configured directly in the native application layer.

Adapter boundary ​

Create and configure the Router, history (when needed), and native ref in the application layer. The binding only connects an existing Router to an existing React Navigation container:

tsx
const router = createRouter({ routes: [home, details] });
// For URL/history-backed apps, configure the adapter in the app as well:
// router.setHistory(historyAdapter(createBrowserHistory()));
const navigationRef = createNavigationContainerRef();

const Stack = createStackNavigator({
  router,
  routes: [HomeScreen, DetailsScreen],
});

<NavigationContainer ref={navigationRef}>
  <Stack navigationRef={navigationRef} />
</NavigationContainer>;

No binding-created Router, history adapter, or container is hidden inside the navigator. This keeps setup explicit and allows the same route units to be used from FSD shared/routing while app-level integration remains configuration.

Type Safety ​

Route parameters are automatically inferred:

tsx
const userRoute = createRoute({ path: '/user/:id/:tab' });
// Type: Route<{ id: string; tab: string }>

// ✅ Type-safe
userRoute.open({ params: { id: '123', tab: 'posts' } });

// ❌ TypeScript error
userRoute.open({ params: { id: 123 } }); // id must be string

Next Steps ​

Released under the MIT License.