Components vs Widgets
Everything in Flutter is a widget, similar to how everything in React is a component. Both use a declarative UI paradigm.
React
// React functional component
function Greeting({ name }) {
return (
<div className="greeting">
<h1>Hello, {name}!</h1>
<p>Welcome to React</p>
</div>
);
}
// Usage
<Greeting name="Alice" />
Flutter
// Flutter widget
class Greeting extends StatelessWidget {
final String name;
const Greeting({required this.name});
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Hello, $name!'),
Text('Welcome to Flutter'),
],
);
}
}
// Usage
Greeting(name: 'Alice')
Key differences
- React uses JSX, Flutter uses Dart with nested widget constructors.
- React components return JSX, Flutter widgets return widgets from
build(). - Props are passed as constructor parameters in Flutter.
- Flutter uses
StatelessWidgetfor static components.
State Management
Flutter's StatefulWidget is similar to React's class components or useState hook.
React
// React with useState hook
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>
Increment
</button>
</div>
);
}
Flutter
// Flutter StatefulWidget
class Counter extends StatefulWidget {
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
void increment() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: increment,
child: Text('Increment'),
),
],
);
}
}
Key differences
- Flutter uses
setState()to trigger rebuilds, similar tosetCount(). StatefulWidgetrequires a separate State class.- State persists in the State object, not via hooks.
- Flutter doesn't have a virtual DOM; it rebuilds widget trees efficiently.
Lifecycle Methods
Flutter's lifecycle methods map closely to React hooks and class component lifecycle.
React
// React useEffect hook
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// Component mounted
fetchUser(userId).then(setUser);
return () => {
// Component will unmount
cleanup();
};
}, [userId]); // Dependencies
return <div>{user?.name}</div>;
}
Flutter
// Flutter lifecycle
class UserProfile extends StatefulWidget {
final String userId;
const UserProfile({required this.userId});
@override
State<UserProfile> createState() =>
_UserProfileState();
}
class _UserProfileState extends State<UserProfile> {
User? user;
@override
void initState() {
super.initState();
// Component mounted
fetchUser(widget.userId).then((u) {
setState(() => user = u);
});
}
@override
void dispose() {
// Component will unmount
cleanup();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text(user?.name ?? '');
}
}
Key differences
initState()is likeuseEffectwith empty dependencies.dispose()is the cleanup function inuseEffect.didUpdateWidget()is likeuseEffectwith dependencies.- Access props via
widget.propNamein the State class.
Styling & Layout
Flutter uses widget-based styling instead of CSS. Flexbox concepts translate to Row, Column, and Flex.
React
// React with CSS/styled-components
function Card({ title, children }) {
return (
<div style={{
backgroundColor: '#fff',
borderRadius: '8px',
padding: '16px',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
display: 'flex',
flexDirection: 'column',
gap: '8px'
}}>
<h2 style={{
fontSize: '20px',
fontWeight: 'bold'
}}>
{title}
</h2>
{children}
</div>
);
}
Flutter
// Flutter with widget styling
class CardWidget extends StatelessWidget {
final String title;
final List<Widget> children;
const CardWidget({
required this.title,
required this.children,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
...children,
],
),
);
}
}
Key differences
- Flutter uses
Containerfor styling (like a styled div). Row= flexDirection row,Column= flexDirection column.EdgeInsetsfor padding/margin instead of CSS values.SizedBoxfor spacing instead of gap or margin.- No CSS selectors; all styling is done programmatically.
Lists & Iteration
Both frameworks efficiently render lists of items with keys for optimization.
React
// React list rendering
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.title}
</li>
))}
</ul>
);
}
// Optimized list (virtualization)
import { FixedSizeList } from
'react-window';
function VirtualList({ items }) {
return (
<FixedSizeList
height={400}
itemCount={items.length}
itemSize={50}
>
{({ index, style }) => (
<div style={style}>
{items[index].title}
</div>
)}
</FixedSizeList>
);
}
Flutter
// Flutter list rendering
class TodoList extends StatelessWidget {
final List<Todo> todos;
const TodoList({required this.todos});
@override
Widget build(BuildContext context) {
return Column(
children: todos.map((todo) =>
Text(todo.title, key: Key(todo.id))
).toList(),
);
}
}
// Optimized list (built-in virtualization)
class VirtualList extends StatelessWidget {
final List<Item> items;
const VirtualList({required this.items});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].title),
);
},
);
}
}
Key differences
- Flutter's
ListView.builderis lazy by default (like react-window). - Use
.map().toList()for simple lists in Flutter. - Keys work similarly for widget identity and optimization.
ListView.separatedadds dividers automatically.
Navigation & Routing
Flutter's Navigator is similar to React Router, with push/pop semantics for screen navigation.
React
// React Router
import {
BrowserRouter,
Routes,
Route,
useNavigate
} from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile/:id"
element={<Profile />} />
</Routes>
</BrowserRouter>
);
}
// Navigation
function Home() {
const navigate = useNavigate();
const goToProfile = () => {
navigate('/profile/123');
};
return (
<button onClick={goToProfile}>
View Profile
</button>
);
}
Flutter
// Flutter Navigator
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/': (context) => HomeScreen(),
'/profile': (context) => ProfileScreen(),
},
);
}
}
// Navigation
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
Navigator.pushNamed(
context,
'/profile',
arguments: {'id': '123'},
);
},
child: Text('View Profile'),
);
}
}
// Receiving parameters
class ProfileScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!
.settings.arguments as Map;
return Text('User ID: ${args['id']}');
}
}
Key differences
Navigator.pushis like React Router'snavigate().Navigator.popgoes back (likenavigate(-1)).- Use
go_routerpackage for advanced routing (similar to React Router). - Flutter uses route names instead of path strings.
Forms & Input Handling
Both frameworks use controlled components pattern for form inputs.
React
// React controlled input
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] =
useState('');
const handleSubmit = (e) => {
e.preventDefault();
login(email, password);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) =>
setEmail(e.target.value)
}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) =>
setPassword(e.target.value)
}
placeholder="Password"
/>
<button type="submit">
Login
</button>
</form>
);
}
Flutter
// Flutter form with controllers
class LoginForm extends StatefulWidget {
@override
State<LoginForm> createState() =>
_LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _emailController =
TextEditingController();
final _passwordController =
TextEditingController();
void _handleSubmit() {
login(
_emailController.text,
_passwordController.text,
);
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: _emailController,
decoration: InputDecoration(
hintText: 'Email',
),
),
TextField(
controller: _passwordController,
obscureText: true,
decoration: InputDecoration(
hintText: 'Password',
),
),
ElevatedButton(
onPressed: _handleSubmit,
child: Text('Login'),
),
],
);
}
}
Key differences
- Flutter uses
TextEditingControllerinstead of state for inputs. - Controllers must be disposed in
dispose()method. - Use
Formwidget withTextFormFieldfor validation. - No synthetic events; access text via
controller.text.
HTTP Requests & Data Fetching
Both use async/await for API calls. Flutter's http package is similar to fetch or axios.
React
// React with fetch
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] =
useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error!</div>;
return <div>{user.name}</div>;
}
Flutter
// Flutter with http package
import 'package:http/http.dart' as http;
class UserProfile extends StatefulWidget {
final String userId;
const UserProfile({required this.userId});
@override
State<UserProfile> createState() =>
_UserProfileState();
}
class _UserProfileState extends State<UserProfile> {
User? user;
bool loading = true;
String? error;
@override
void initState() {
super.initState();
fetchUser();
}
Future<void> fetchUser() async {
try {
final response = await http.get(
Uri.parse('/api/users/${widget.userId}')
);
final data = jsonDecode(response.body);
setState(() {
user = User.fromJson(data);
loading = false;
});
} catch (e) {
setState(() {
error = e.toString();
loading = false;
});
}
}
@override
Widget build(BuildContext context) {
if (loading) return CircularProgressIndicator();
if (error != null) return Text('Error!');
return Text(user!.name);
}
}
Key differences
- Add
httppackage topubspec.yamldependencies. - Use
Uri.parse()for URLs in Flutter. jsonDecode()is likeJSON.parse().- Consider
diopackage for advanced features (like axios).
Global State Management
Flutter has several state management solutions. Provider is similar to React Context, while Riverpod is like Redux/Zustand.
React
// React Context API
const ThemeContext = createContext();
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider
value={{ theme, setTheme }}
>
<MainContent />
</ThemeContext.Provider>
);
}
// Consuming context
function ThemeToggle() {
const { theme, setTheme } =
useContext(ThemeContext);
return (
<button onClick={() =>
setTheme(theme === 'light'
? 'dark' : 'light')
}>
Toggle Theme
</button>
);
}
Flutter
// Flutter Provider
class ThemeProvider extends ChangeNotifier {
String _theme = 'light';
String get theme => _theme;
void setTheme(String newTheme) {
_theme = newTheme;
notifyListeners();
}
}
// Setup provider
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => ThemeProvider(),
child: MyApp(),
),
);
}
// Consuming provider
class ThemeToggle extends StatelessWidget {
@override
Widget build(BuildContext context) {
final themeProvider =
Provider.of<ThemeProvider>(context);
return ElevatedButton(
onPressed: () {
themeProvider.setTheme(
themeProvider.theme == 'light'
? 'dark' : 'light'
);
},
child: Text('Toggle Theme'),
);
}
}
Key differences
- Provider uses
ChangeNotifierinstead of hooks. notifyListeners()triggers rebuilds (likesetState).- Alternatives: Riverpod (hooks-like), Bloc (Redux-like), GetX.
- Use
Consumerwidget for granular rebuilds.
Animations
Both frameworks have rich animation APIs. Flutter's animations are similar to React Spring or Framer Motion.
React
// React with CSS transitions
function FadeIn({ children }) {
const [visible, setVisible] =
useState(false);
useEffect(() => {
setVisible(true);
}, []);
return (
<div style={{
opacity: visible ? 1 : 0,
transition: 'opacity 0.5s ease-in'
}}>
{children}
</div>
);
}
// With Framer Motion
import { motion } from 'framer-motion';
function AnimatedBox() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
Content
</motion.div>
);
}
Flutter
// Flutter implicit animations
class FadeIn extends StatefulWidget {
final Widget child;
const FadeIn({required this.child});
@override
State<FadeIn> createState() =>
_FadeInState();
}
class _FadeInState extends State<FadeIn> {
bool visible = false;
@override
void initState() {
super.initState();
Future.microtask(() =>
setState(() => visible = true)
);
}
@override
Widget build(BuildContext context) {
return AnimatedOpacity(
opacity: visible ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
curve: Curves.easeIn,
child: widget.child,
);
}
}
// Simpler implicit animation
AnimatedContainer(
duration: Duration(milliseconds: 500),
opacity: visible ? 1.0 : 0.0,
transform: Matrix4.translationValues(
0, visible ? 0 : 20, 0
),
child: Text('Content'),
)
Key differences
- Flutter has built-in animation widgets (no extra packages needed).
AnimatedContainer,AnimatedOpacityfor implicit animations.AnimationControllerfor explicit control (like CSS keyframes).Herowidget for shared element transitions.
Ready to learn more? Visit the Flutter Documentation or Dart Language Tour.