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 such as RouterProvider, route views, layouts, and route hooks. Browser-only Link, useLink, and LinkProps remain in @effector/router-react. Import the shared router API from @effector/router.

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.