dart_node_react_native

dart_node_react_native provides type-safe React Native bindings for building iOS and Android apps in Dart. Combined with Expo, you get a complete mobile development experience.

Installation

dependencies:
  dart_node_react_native: ^0.2.0
  dart_node_react: ^0.2.0  # Required peer dependency

Set up your Expo project:

npx create-expo-app my-app
cd my-app

Quick Start

import 'package:dart_node_react_native/dart_node_react_native.dart';
import 'package:dart_node_react/dart_node_react.dart';

ReactElement app() {
  return safeAreaView(
    style: {'flex': 1, 'backgroundColor': '#fff'},
    children: [
      view(
        style: {'padding': 20},
        children: [
          rnText(
            style: {'fontSize': 24, 'fontWeight': 'bold'},
            children: [text('Hello, Dart!')],
          ),
          rnText(
            children: [text('Welcome to React Native with Dart.')],
          ),
        ],
      ),
    ],
  );
}

Components

View

The fundamental building block, similar to div in web:

view(
  style: {
    'flex': 1,
    'flexDirection': 'row',
    'justifyContent': 'center',
    'alignItems': 'center',
    'backgroundColor': '#f5f5f5',
  },
  children: [...],
)

Text

For displaying text (note: rnText to avoid conflict with React's text()):

rnText(
  style: {
    'fontSize': 18,
    'fontWeight': '600',
    'color': '#333',
    'textAlign': 'center',
  },
  children: [text('Hello, World!')],
)

TextInput

For user text input:

ReactElement searchInput() {
  final (query, setQuery) = useState('');

  return textInput(
    value: query,
    onChangeText: setQuery,
    placeholder: 'Search...',
    style: {
      'height': 40,
      'borderWidth': 1,
      'borderColor': '#ccc',
      'borderRadius': 8,
      'paddingHorizontal': 12,
    },
  );
}

TouchableOpacity

For pressable elements with opacity feedback:

touchableOpacity(
  onPress: () => print('Pressed!'),
  style: {
    'backgroundColor': '#007AFF',
    'padding': 12,
    'borderRadius': 8,
  },
  children: [
    rnText(
      style: {'color': '#fff', 'textAlign': 'center'},
      children: [text('Press Me')],
    ),
  ],
)

Button

Simple button component:

rnButton(
  title: 'Submit',
  onPress: () => print('Button pressed!'),
  color: '#007AFF',
)

ScrollView

For scrollable content:

scrollView(
  style: {'flex': 1},
  contentContainerStyle: {'padding': 20},
  children: [
    // Many children that exceed screen height
    ...items.map((item) => itemCard(item)),
  ],
)

FlatList

For efficient list rendering:

ReactElement userList({required List<User> users}) {
  return flatList<User>(
    data: users,
    keyExtractor: (user, _) => user.id,
    renderItem: (info) => userCard(user: info.item),
    ItemSeparatorComponent: () => view(
      style: {'height': 1, 'backgroundColor': '#eee'},
    ),
  );
}

Image

For displaying images:

// Local image
image(
  source: AssetSource('assets/logo.png'),
  style: {'width': 100, 'height': 100},
)

// Remote image
image(
  source: UriSource('https://example.com/image.jpg'),
  style: {'width': 200, 'height': 150},
  resizeMode: 'cover',
)

SafeAreaView

For respecting device safe areas (notch, home indicator):

safeAreaView(
  style: {'flex': 1},
  children: [
    // Content here is safe from notches and system UI
  ],
)

ActivityIndicator

Loading spinner:

activityIndicator(
  size: 'large',
  color: '#007AFF',
)

Styling

React Native uses JavaScript objects for styles (like React inline styles but with different properties):

view(
  style: {
    // Layout
    'flex': 1,
    'flexDirection': 'column',  // or 'row'
    'justifyContent': 'center',  // main axis
    'alignItems': 'center',      // cross axis

    // Spacing
    'padding': 20,
    'paddingHorizontal': 16,
    'margin': 10,
    'marginTop': 20,

    // Appearance
    'backgroundColor': '#ffffff',
    'borderRadius': 8,
    'borderWidth': 1,
    'borderColor': '#ccc',

    // Shadows (iOS)
    'shadowColor': '#000',
    'shadowOffset': {'width': 0, 'height': 2},
    'shadowOpacity': 0.25,
    'shadowRadius': 4,

    // Shadows (Android)
    'elevation': 5,
  },
  children: [...],
)

Navigation

Use with React Navigation (via JS interop):

// Define screens
ReactElement homeScreen({required NavigationProps nav}) {
  return view(children: [
    rnText(children: [text('Home Screen')]),
    touchableOpacity(
      onPress: () => nav.navigate('Details', {'id': 123}),
      children: [rnText(children: [text('Go to Details')])],
    ),
  ]);
}

ReactElement detailsScreen({required NavigationProps nav}) {
  final id = nav.route.params['id'];

  return view(children: [
    rnText(children: [text('Details for $id')]),
    touchableOpacity(
      onPress: () => nav.goBack(),
      children: [rnText(children: [text('Go Back')])],
    ),
  ]);
}

Complete Example

import 'package:dart_node_react_native/dart_node_react_native.dart';
import 'package:dart_node_react/dart_node_react.dart';

ReactElement todoApp() {
  final (todos, setTodos) = useState<List<Todo>>([]);
  final (input, setInput) = useState('');

  void addTodo() {
    if (input.trim().isEmpty) return;

    setTodos((prev) => [
      ...prev,
      Todo(id: DateTime.now().toString(), title: input, completed: false),
    ]);
    setInput('');
  }

  void toggleTodo(String id) {
    setTodos((prev) => prev.map((todo) =>
      todo.id == id
          ? Todo(id: todo.id, title: todo.title, completed: !todo.completed)
          : todo
    ).toList());
  }

  return safeAreaView(
    style: {'flex': 1, 'backgroundColor': '#f5f5f5'},
    children: [
      // Header
      view(
        style: {
          'padding': 20,
          'backgroundColor': '#007AFF',
        },
        children: [
          rnText(
            style: {
              'fontSize': 24,
              'fontWeight': 'bold',
              'color': '#fff',
            },
            children: [text('My Todos')],
          ),
        ],
      ),

      // Input
      view(
        style: {
          'flexDirection': 'row',
          'padding': 16,
          'backgroundColor': '#fff',
        },
        children: [
          textInput(
            style: {
              'flex': 1,
              'height': 44,
              'borderWidth': 1,
              'borderColor': '#ddd',
              'borderRadius': 8,
              'paddingHorizontal': 12,
            },
            value: input,
            onChangeText: setInput,
            placeholder: 'Add a todo...',
          ),
          touchableOpacity(
            onPress: addTodo,
            style: {
              'marginLeft': 12,
              'backgroundColor': '#007AFF',
              'paddingHorizontal': 20,
              'justifyContent': 'center',
              'borderRadius': 8,
            },
            children: [
              rnText(
                style: {'color': '#fff', 'fontWeight': '600'},
                children: [text('Add')],
              ),
            ],
          ),
        ],
      ),

      // List
      flatList<Todo>(
        data: todos,
        keyExtractor: (todo, _) => todo.id,
        renderItem: (info) => touchableOpacity(
          onPress: () => toggleTodo(info.item.id),
          style: {
            'flexDirection': 'row',
            'alignItems': 'center',
            'padding': 16,
            'backgroundColor': '#fff',
            'borderBottomWidth': 1,
            'borderBottomColor': '#eee',
          },
          children: [
            view(
              style: {
                'width': 24,
                'height': 24,
                'borderRadius': 12,
                'borderWidth': 2,
                'borderColor': info.item.completed ? '#4CAF50' : '#ccc',
                'backgroundColor': info.item.completed ? '#4CAF50' : 'transparent',
                'marginRight': 12,
              },
            ),
            rnText(
              style: {
                'flex': 1,
                'fontSize': 16,
                'textDecorationLine': info.item.completed ? 'line-through' : 'none',
                'color': info.item.completed ? '#999' : '#333',
              },
              children: [text(info.item.title)],
            ),
          ],
        ),
        style: {'flex': 1},
      ),

      // Footer
      view(
        style: {'padding': 16, 'backgroundColor': '#fff'},
        children: [
          rnText(
            style: {'textAlign': 'center', 'color': '#666'},
            children: [
              text('${todos.where((t) => !t.completed).length} items remaining'),
            ],
          ),
        ],
      ),
    ],
  );
}

class Todo {
  final String id;
  final String title;
  final bool completed;

  Todo({required this.id, required this.title, required this.completed});
}

API Reference

See the full API documentation for all available components and types.