← Back to DevBytes

Migrating from Flutter to React Native: Step-by-Step Guide

Understanding the Migration: Flutter to React Native

Migrating a production mobile application from Flutter to React Native is a strategic decision that involves rebuilding the user interface, business logic, and native integrations in a JavaScript-driven ecosystem. This guide provides a complete, step-by-step walkthrough to help you plan and execute the transition with minimal friction.

Why Consider Migrating?

Flutter and React Native both enable cross-platform mobile development, but they differ fundamentally in language, rendering engine, and ecosystem. Common reasons teams move from Flutter to React Native include:

What to Expect During Migration

Flutter uses its own rendering engine (Skia/Impeller) and a widget tree architecture. React Native relies on native platform views bridged to JavaScript through a batched message queue, or more recently via Fabric and JSI for direct native access. Key differences include:

Step-by-Step Migration Guide

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

1. Audit Your Flutter Project

Before writing any React Native code, perform a thorough inventory:

A detailed audit prevents surprises and helps you estimate the effort for each module.

2. Set Up the React Native Environment

Install Node.js, watchman, and the React Native CLI or Expo. For a bare workflow project:

# Initialize a new React Native project
npx react-native init MyApp --template react-native-template-typescript

# Or use Expo for managed workflow
npx create-expo-app MyApp --template expo-template-blank-typescript

Set up your preferred navigation library immediately:

npm install @react-navigation/native @react-navigation/stack
npm install react-native-screens react-native-safe-area-context

3. Recreate the UI Structure Screen by Screen

Begin with the most critical screen, usually the home or authentication screen. Map Flutter widgets to React Native components:

Example conversion from a simple Flutter widget to a React Native component:

// Flutter (Dart)
class ProfileCard extends StatelessWidget {
  final String name;
  final String imageUrl;
  const ProfileCard({required this.name, required this.imageUrl});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(16),
      child: Row(
        children: [
          CircleAvatar(backgroundImage: NetworkImage(imageUrl)),
          SizedBox(width: 12),
          Text(name, style: TextStyle(fontSize: 18)),
        ],
      ),
    );
  }
}
// React Native (TypeScript)
import React from 'react';
import { View, Text, Image, StyleSheet } from 'react-native';

interface ProfileCardProps {
  name: string;
  imageUrl: string;
}

const ProfileCard: React.FC = ({ name, imageUrl }) => (
  
    
    
    {name}
  
);

const styles = StyleSheet.create({
  container: { padding: 16, flexDirection: 'row', alignItems: 'center' },
  avatar: { width: 48, height: 48, borderRadius: 24 },
  name: { fontSize: 18 },
});

export default ProfileCard;

4. Map Flutter State Management to React Hooks or Libraries

If your Flutter app uses setState and StatefulWidgets, the equivalent is the useState hook. For more complex state (BLoC, Provider), consider React counterparts:

Example: Flutter BLoC vs React Context + useReducer

// Flutter BLoC (simplified)
class CounterCubit extends Cubit {
  CounterCubit() : super(0);
  void increment() => emit(state + 1);
}
// Usage: BlocBuilder(builder: (context, state) => Text('$state'))
// React Native with useReducer and Context
import React, { createContext, useReducer, useContext } from 'react';

type Action = { type: 'INCREMENT' };
const counterReducer = (state: number, action: Action): number => {
  switch (action.type) {
    case 'INCREMENT': return state + 1;
    default: return state;
  }
};

const CounterContext = createContext<{
  count: number; dispatch: React.Dispatch;
}>({ count: 0, dispatch: () => {} });

export const CounterProvider: React.FC<{children: React.ReactNode}> = ({ children }) => {
  const [count, dispatch] = useReducer(counterReducer, 0);
  return (
    
      {children}
    
  );
};

// In a component:
const { count, dispatch } = useContext(CounterContext);

5. Handle Navigation and Deep Links

Replace Flutter’s Navigator (or go_router) with React Navigation. Configure a stack navigator:

import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();

function AppNavigator() {
  return (
    
      
        
        
      
    
  );
}

For deep links, use the linking configuration of React Navigation, which directly maps URL patterns to screens, analogous to Flutter’s onGenerateRoute.

6. Convert Data and Network Layers

Extract the API communication layer. Flutter often uses http or dio packages; React Native can use fetch, axios, or react-query.

// Flutter (dio)
final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
final response = await dio.get('/items');
final items = (response.data as List).map((e) => Item.fromJson(e)).toList();
// React Native (axios with TypeScript)
import axios from 'axios';
import { useQuery } from '@tanstack/react-query';

const apiClient = axios.create({ baseURL: 'https://api.example.com' });

const fetchItems = async () => {
  const { data } = await apiClient.get('/items');
  return data;
};

const useItems = () => useQuery({ queryKey: ['items'], queryFn: fetchItems });

7. Integrate Native Modules and Permissions

For platform features (camera, geolocation, Bluetooth), replace Flutter plugins with equivalent React Native community packages. Example mapping:

If a package doesn’t exist, write a native module using the React Native bridge or Turbo Modules. Keep the API contract similar to reduce refactoring.

8. Testing, Debugging, and Gradual Rollout

React Native offers Flipper, React DevTools, and react-native-debugger. Write unit tests with Jest and component tests with React Native Testing Library. Start with a phased migration strategy:

Best Practices for a Smooth Transition

Common Pitfalls and How to Avoid Them

Conclusion

Migrating from Flutter to React Native is a substantial undertaking that touches every layer of your application—UI, state, navigation, and native integrations. By systematically auditing your Flutter codebase, methodically rebuilding screens with React Native components, mapping state management to hooks or libraries, and leveraging the mature React Native ecosystem, you can achieve a successful transition. Follow the step‑by‑step process outlined here, embrace TypeScript, and adopt best practices like modular architecture and phased rollout. The result is a maintainable, JavaScript‑powered codebase that opens doors to web sharing, faster hiring, and a vast library of community‑driven modules.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles