Özgün Bal

Slides
React / React Native Development Lead at Innovance Consultancy
Links
Hobbies
| React Native | Flutter | |
|---|---|---|
| Building block | Component | Widget |
| UI compilation | Render | build |
| Data flow | Unidirectional | Unidirectional |
| UI paradigm | Declarative | Declarative |
| Lifecycle | Mount, update, unmount | initState, didUpdateWidget, dispose |
| Native module usage | JS to swift/kotlin | Dart to swift/kotlin |
| 3rd party packages | Npm | pub |
| Async result | Promise | Future |
import React, { useEffect, useState } from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; function LifecycleComponent() { const [count, setCount] = useState(0); const [data, setData] = useState(null); useEffect(() => { // Mount: runs once when component mounts console.log('Component mounted'); // Async operation returning a Promise fetchData().then(result => setData(result)); return () => { // Unmount: cleanup function console.log('Component will unmount'); }; }, []); useEffect(() => { // Update: runs when count changes console.log('Count updated:', count); }, [count]); const fetchData = () => { return new Promise(resolve => { setTimeout(() => resolve('Data loaded'), 1000); }); }; return ( <View> <Text>Count: {count}</Text> <Text>Data: {data}</Text> <TouchableOpacity onPress={() => setCount(count + 1)}> <Text>Increment</Text> </TouchableOpacity> </View> ); }
class LifecycleWidget extends StatefulWidget { @override _LifecycleWidgetState createState() => _LifecycleWidgetState(); } class _LifecycleWidgetState extends State<LifecycleWidget> { int count = 0; String? data; @override void initState() { // Mount: runs once when widget is inserted super.initState(); print('Widget initialized'); // Async operation returning a Future fetchData().then((result) => setState(() => data = result)); } @override void didUpdateWidget(LifecycleWidget oldWidget) { // Update: runs when widget configuration changes super.didUpdateWidget(oldWidget); print('Widget updated'); } @override void dispose() { // Unmount: cleanup when widget is removed print('Widget disposed'); super.dispose(); } Future<String> fetchData() async { await Future.delayed(Duration(seconds: 1)); return 'Data loaded'; } @override Widget build(BuildContext context) { return Column( children: [ Text('Count: $count'), Text('Data: ${data ?? "Loading..."}'), ElevatedButton( onPressed: () => setState(() => count++), child: Text('Increment'), ), ], ); } }
import React, { useEffect, useState } from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; function LifecycleComponent() { const [count, setCount] = useState(0); const [data, setData] = useState(null); useEffect(() => { // Mount: runs once when component mounts console.log('Component mounted'); // Async operation returning a Promise fetchData().then(result => setData(result)); return () => { // Unmount: cleanup function console.log('Component will unmount'); }; }, []); useEffect(() => { // Update: runs when count changes console.log('Count updated:', count); }, [count]); const fetchData = () => { return new Promise(resolve => { setTimeout(() => resolve('Data loaded'), 1000); }); }; return ( <View> <Text>Count: {count}</Text> <Text>Data: {data}</Text> <TouchableOpacity onPress={() => setCount(count + 1)}> <Text>Increment</Text> </TouchableOpacity> </View> ); }
class LifecycleWidget extends StatefulWidget { @override _LifecycleWidgetState createState() => _LifecycleWidgetState(); } class _LifecycleWidgetState extends State<LifecycleWidget> { int count = 0; String? data; @override void initState() { // Mount: runs once when widget is inserted super.initState(); print('Widget initialized'); // Async operation returning a Future fetchData().then((result) => setState(() => data = result)); } @override void didUpdateWidget(LifecycleWidget oldWidget) { // Update: runs when widget configuration changes super.didUpdateWidget(oldWidget); print('Widget updated'); } @override void dispose() { // Unmount: cleanup when widget is removed print('Widget disposed'); super.dispose(); } Future<String> fetchData() async { await Future.delayed(Duration(seconds: 1)); return 'Data loaded'; } @override Widget build(BuildContext context) { return Column( children: [ Text('Count: $count'), Text('Data: ${data ?? "Loading..."}'), ElevatedButton( onPressed: () => setState(() => count++), child: Text('Increment'), ), ], ); } }
import React, { useEffect, useState } from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; function LifecycleComponent() { const [count, setCount] = useState(0); const [data, setData] = useState(null); useEffect(() => { // Mount: runs once when component mounts console.log('Component mounted'); // Async operation returning a Promise fetchData().then(result => setData(result)); return () => { // Unmount: cleanup function console.log('Component will unmount'); }; }, []); useEffect(() => { // Update: runs when count changes console.log('Count updated:', count); }, [count]); const fetchData = () => { return new Promise(resolve => { setTimeout(() => resolve('Data loaded'), 1000); }); }; return ( <View> <Text>Count: {count}</Text> <Text>Data: {data}</Text> <TouchableOpacity onPress={() => setCount(count + 1)}> <Text>Increment</Text> </TouchableOpacity> </View> ); }
class LifecycleWidget extends StatefulWidget { @override _LifecycleWidgetState createState() => _LifecycleWidgetState(); } class _LifecycleWidgetState extends State<LifecycleWidget> { int count = 0; String? data; @override void initState() { // Mount: runs once when widget is inserted super.initState(); print('Widget initialized'); // Async operation returning a Future fetchData().then((result) => setState(() => data = result)); } @override void didUpdateWidget(LifecycleWidget oldWidget) { // Update: runs when widget configuration changes super.didUpdateWidget(oldWidget); print('Widget updated'); } @override void dispose() { // Unmount: cleanup when widget is removed print('Widget disposed'); super.dispose(); } Future<String> fetchData() async { await Future.delayed(Duration(seconds: 1)); return 'Data loaded'; } @override Widget build(BuildContext context) { return Column( children: [ Text('Count: $count'), Text('Data: ${data ?? "Loading..."}'), ElevatedButton( onPressed: () => setState(() => count++), child: Text('Increment'), ), ], ); } }
import React, { useEffect, useState } from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; function LifecycleComponent() { const [count, setCount] = useState(0); const [data, setData] = useState(null); useEffect(() => { // Mount: runs once when component mounts console.log('Component mounted'); // Async operation returning a Promise fetchData().then(result => setData(result)); return () => { // Unmount: cleanup function console.log('Component will unmount'); }; }, []); useEffect(() => { // Update: runs when count changes console.log('Count updated:', count); }, [count]); const fetchData = () => { return new Promise(resolve => { setTimeout(() => resolve('Data loaded'), 1000); }); }; return ( <View> <Text>Count: {count}</Text> <Text>Data: {data}</Text> <TouchableOpacity onPress={() => setCount(count + 1)}> <Text>Increment</Text> </TouchableOpacity> </View> ); }
class LifecycleWidget extends StatefulWidget { @override _LifecycleWidgetState createState() => _LifecycleWidgetState(); } class _LifecycleWidgetState extends State<LifecycleWidget> { int count = 0; String? data; @override void initState() { // Mount: runs once when widget is inserted super.initState(); print('Widget initialized'); // Async operation returning a Future fetchData().then((result) => setState(() => data = result)); } @override void didUpdateWidget(LifecycleWidget oldWidget) { // Update: runs when widget configuration changes super.didUpdateWidget(oldWidget); print('Widget updated'); } @override void dispose() { // Unmount: cleanup when widget is removed print('Widget disposed'); super.dispose(); } Future<String> fetchData() async { await Future.delayed(Duration(seconds: 1)); return 'Data loaded'; } @override Widget build(BuildContext context) { return Column( children: [ Text('Count: $count'), Text('Data: ${data ?? "Loading..."}'), ElevatedButton( onPressed: () => setState(() => count++), child: Text('Increment'), ), ], ); } }
| React Native | Flutter | |
|---|---|---|
| Child/children | Mostly implicit | Explicit |
| Library management | Hard to figure out | Less option, easier to handle |
| Argument | Only named props | Positional and named arguments |
| Type system | Easy to disable | Strict type system |
| Plugins/extension | No official IDE extension | Official one comprehensive |
| Web support | Similar library advantage | Canvas is no good |
| UI View | Uses native components | Draws every pixel |
| Adaption | Mostly from web developers | Native or backend developers |
import React from 'react'; import { View, Text } from 'react-native'; function Card({ children }) { return ( <View style={{ padding: 20 }}> {children} </View> ); } function App() { return ( <Card> <Text>Title</Text> <Text>Description</Text> </Card> ); }
import 'package:flutter/material.dart'; class CardWidget extends StatelessWidget { final Widget child; CardWidget({required this.child}); @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: child, ); } } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return CardWidget( child: Column( children: [ Text('Title'), Text('Description'), ], ), ); } }
import React from 'react'; import { View, Text } from 'react-native'; function Button({ title, onPress, color, disabled }) { return ( <TouchableOpacity onPress={onPress} disabled={disabled} style={{ backgroundColor: color }}> <Text>{title}</Text> </TouchableOpacity> ); } function App() { return ( <Button title="Click me" onPress={() => console.log('Pressed')} color="blue" disabled={false} /> ); }
import 'package:flutter/material.dart'; class CustomButton extends StatelessWidget { final String title; // Positional final VoidCallback onPressed; // Positional final Color? color; // Named (optional) final bool? disabled; // Named (optional) CustomButton( this.title, this.onPressed, {this.color, this.disabled = false} ); @override Widget build(BuildContext context) { return ElevatedButton( onPressed: disabled! ? null : onPressed, style: ElevatedButton.styleFrom( backgroundColor: color ?? Colors.blue, ), child: Text(title), ); } } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return CustomButton( 'Click me', () => print('Pressed'), color: Colors.blue, disabled: false, ); } }
trends.builtwith.com -> React 55m, Flutter 38k
| Region | Job Ratio (RN:Flutter) | RN Dominance | Key Insight |
|---|---|---|---|
| 🇺🇸 North America | 6:1 | ⚫⚫⚫⚫⚫ Very High | Most RN-dominated market |
| 🇪🇺 Europe | 3:1 | ⚫⚫⚫⚪⚪ High | Flutter gaining in new projects (52%) |
| 🌏 Asia | 1.5:1 | ⚫⚫⚪⚪⚪ Balanced | Most balanced market, Flutter leads in China |
| 🌍 Middle East | 2:1 | ⚫⚫⚫⚪⚪ Moderate | Flutter booming in fintech/startups |
| 🌎 Latin America | 2:1 | ⚫⚫⚫⚪⚪ Moderate | RN slightly ahead |
| 🌐 Global Average | 2.5:1 | ⚫⚫⚫⚪⚪ Moderate | RN leads overall |