Props & Constructors
In React you pass data to a component via props — a plain object destructured in the function signature. In Flutter you pass data to a widget via constructor parameters declared as final fields on the class. The two models solve the exact same problem: making a widget configurable from the outside without coupling it to a specific data source.
Flutter uses Dart’s named parameters extensively. Named parameters are opt-in: wrap them in {} and they become keyword arguments at the call site. Mark one required and Dart enforces it at compile time — no more PropTypes or TypeScript’s Required<>.
Side-by-side comparison
Section titled “Side-by-side comparison”// React — TypeScript propsinterface BadgeProps { label: string; color?: string; // optional, has default count: number; // required}
function Badge({ label, color = 'blue', count }: BadgeProps) { return ( <span style={{ background: color }}> {label} ({count}) </span> );}
// Usage<Badge label="Notifications" count={5} /><Badge label="Alerts" color="red" count={2} />// Flutter — constructor paramsclass Badge extends StatelessWidget { final String label; final Color color; // optional with default final int count; // required
const Badge({ super.key, required this.label, this.color = Colors.blue, // default value required this.count, });
@override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(12), ), child: Text( '$label ($count)', style: const TextStyle(color: Colors.white), ), ); }}
// UsageBadge(label: 'Notifications', count: 5)Badge(label: 'Alerts', color: Colors.red, count: 2)Runnable example
Section titled “Runnable example”import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.grey[100],
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
StatusBadge(label: 'Active', status: BadgeStatus.success),
SizedBox(height: 12),
StatusBadge(label: 'Pending', status: BadgeStatus.warning),
SizedBox(height: 12),
StatusBadge(label: 'Failed', status: BadgeStatus.error),
],
),
),
),
);
}
}
enum BadgeStatus { success, warning, error }
class StatusBadge extends StatelessWidget {
final String label;
final BadgeStatus status;
final double fontSize;
const StatusBadge({
super.key,
required this.label,
required this.status,
this.fontSize = 14,
});
Color get _color {
switch (status) {
case BadgeStatus.success: return Colors.green;
case BadgeStatus.warning: return Colors.orange;
case BadgeStatus.error: return Colors.red;
}
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: _color.withOpacity(0.15),
border: Border.all(color: _color),
borderRadius: BorderRadius.circular(20),
),
child: Text(
label,
style: TextStyle(color: _color, fontSize: fontSize,
fontWeight: FontWeight.w600),
),
);
}
}