prprompts-flutter-generator
Version:
AI-powered Flutter development with full automation + official extension support - Generate 32 security-audited guides & auto-implement in 2-3 hours. NEW v5.1: Official Claude Code plugin with hooks, Gemini TOML commands, Qwen MCP settings. Features: Comp
1,147 lines (948 loc) • 23.8 kB
Markdown
# JSX Pattern Conversion Examples
Complete guide for converting complex React/JSX patterns to Flutter equivalents.
**Version:** 5.0.0
**Last Updated:** November 2, 2025
**Module:** JSXPatternConverter.js
---
## Table of Contents
1. [Higher-Order Components (HOCs)](#higher-order-components-hocs)
2. [React.memo](#reactmemo)
3. [React.forwardRef](#reactforwardref)
4. [Render Props](#render-props)
5. [React.Fragment](#reactfragment)
6. [Conditional Rendering](#conditional-rendering)
7. [List Rendering](#list-rendering)
8. [Complex Scenarios](#complex-scenarios)
---
## Higher-Order Components (HOCs)
HOCs are functions that take a component and return a new component with enhanced functionality.
### Pattern: withAuth HOC
**React (Before):**
```jsx
// HOC definition
function withAuth(Component) {
return function AuthenticatedComponent(props) {
const { isAuthenticated } = useAuth();
if (!isAuthenticated) {
return <Redirect to="/login" />;
}
return <Component {...props} />;
};
}
// Usage
const ProtectedPage = withAuth(ProfilePage);
```
**Flutter (After):**
```dart
// Mixin converted from HOC: withAuth
mixin AuthMixin<T extends StatefulWidget> on State<T> {
bool _isAuthenticated = false;
@override
void initState() {
super.initState();
_checkAuthentication();
}
void _checkAuthentication() {
final auth = Provider.of<AuthProvider>(context, listen: false);
_isAuthenticated = auth.isAuthenticated;
if (!_isAuthenticated) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pushReplacementNamed('/login');
});
}
}
}
// Usage
class ProfilePageState extends State<ProfilePage> with AuthMixin {
@override
Widget build(BuildContext context) {
if (!_isAuthenticated) {
return Container(); // Or loading indicator
}
return Scaffold(
appBar: AppBar(title: Text('Profile')),
body: ProfileContent(),
);
}
}
```
### Pattern: Multiple HOCs (Composition)
**React (Before):**
```jsx
const Enhanced = withAuth(withRouter(withTheme(MyComponent)));
// Or with compose
const Enhanced = compose(
withAuth,
withRouter,
withTheme
)(MyComponent);
```
**Flutter (After):**
```dart
// Multiple mixins
class MyComponentState extends State<MyComponent>
with AuthMixin, RouterMixin, ThemeMixin {
@override
Widget build(BuildContext context) {
// All mixin functionality available
final currentRoute = this.currentRoute; // From RouterMixin
final theme = this.theme; // From ThemeMixin
if (!_isAuthenticated) { // From AuthMixin
return Container();
}
return Container(
color: theme.backgroundColor,
child: Text('Current route: $currentRoute'),
);
}
}
```
---
## React.memo
React.memo prevents unnecessary re-renders by memoizing the component.
### Pattern: Basic Memoization
**React (Before):**
```jsx
const ExpensiveComponent = React.memo(({ data }) => {
return (
<div>
<ComplexCalculation data={data} />
</div>
);
});
```
**Flutter (After):**
```dart
// Use const constructor for automatic optimization
class ExpensiveComponent extends StatelessWidget {
// const constructor = automatic memoization
const ExpensiveComponent({Key? key, required this.data}) : super(key: key);
final Data data;
@override
Widget build(BuildContext context) {
return Container(
child: ComplexCalculation(data: data),
);
}
}
// Usage with const constructor
const ExpensiveComponent(data: myData)
```
### Pattern: Memo with Custom Comparison
**React (Before):**
```jsx
const UserCard = React.memo(
({ user }) => {
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
},
(prevProps, nextProps) => {
// Only re-render if user.id changes
return prevProps.user.id === nextProps.user.id;
}
);
```
**Flutter (After):**
```dart
class UserCard extends StatelessWidget {
const UserCard({Key? key, required this.user}) : super(key: key);
final User user;
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Text(user.name, style: Theme.of(context).textTheme.headline6),
Text(user.email),
],
),
);
}
// Flutter's build system automatically compares props
// Only rebuilds if 'user' reference changes
}
// For custom equality, use Equatable package
class User extends Equatable {
final int id;
final String name;
final String email;
const User({required this.id, required this.name, required this.email});
@override
List<Object get props => [id]; // Only compare by id
}
```
### Pattern: Inline Memo
**React (Before):**
```jsx
const MemoizedComponent = React.memo(() => {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
</div>
);
});
```
**Flutter (After):**
```dart
class MemoizedComponent extends StatefulWidget {
const MemoizedComponent({Key? key}) : super(key: key);
@override
State<MemoizedComponent> createState() => _MemoizedComponentState();
}
class _MemoizedComponentState extends State<MemoizedComponent> {
int count = 0;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
setState(() {
count++;
});
},
child: Text('Count: $count'),
);
}
}
```
---
## React.forwardRef
ForwardRef allows parent components to access child component refs.
### Pattern: Basic ForwardRef
**React (Before):**
```jsx
const FancyInput = React.forwardRef((props, ref) => {
return <input ref={ref} className="fancy-input" {...props} />;
});
// Parent usage
function Parent() {
const inputRef = useRef();
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<FancyInput ref={inputRef} />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
```
**Flutter (After):**
```dart
// Child widget with GlobalKey support
class FancyInput extends StatefulWidget {
const FancyInput({Key? key}) : super(key: key);
@override
State<FancyInput> createState() => FancyInputState();
}
class FancyInputState extends State<FancyInput> {
final TextEditingController _controller = TextEditingController();
final FocusNode _focusNode = FocusNode();
// Public method accessible from parent via GlobalKey
void focusInput() {
_focusNode.requestFocus();
}
@override
void dispose() {
_controller.dispose();
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return TextField(
controller: _controller,
focusNode: _focusNode,
decoration: InputDecoration(
border: OutlineInputBorder(),
),
);
}
}
// Parent widget
class Parent extends StatelessWidget {
// GlobalKey to access child state
final GlobalKey<FancyInputState> _inputKey = GlobalKey<FancyInputState>();
Parent({Key? key}) : super(key: key);
void _focusInput() {
_inputKey.currentState?.focusInput();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
FancyInput(key: _inputKey),
ElevatedButton(
onPressed: _focusInput,
child: Text('Focus Input'),
),
],
);
}
}
```
### Pattern: ForwardRef with Multiple Methods
**React (Before):**
```jsx
const VideoPlayer = React.forwardRef((props, ref) => {
const videoRef = useRef();
useImperativeHandle(ref, () => ({
play: () => videoRef.current.play(),
pause: () => videoRef.current.pause(),
seek: (time) => videoRef.current.currentTime = time,
}));
return <video ref={videoRef} {...props} />;
});
```
**Flutter (After):**
```dart
class VideoPlayer extends StatefulWidget {
const VideoPlayer({Key? key}) : super(key: key);
@override
State<VideoPlayer> createState() => VideoPlayerState();
}
class VideoPlayerState extends State<VideoPlayer> {
late VideoPlayerController _controller;
@override
void initState() {
super.initState();
_controller = VideoPlayerController.network('video_url');
}
// Public methods accessible via GlobalKey
void play() {
_controller.play();
}
void pause() {
_controller.pause();
}
void seek(Duration position) {
_controller.seekTo(position);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
);
}
}
// Parent usage
class Parent extends StatelessWidget {
final GlobalKey<VideoPlayerState> _playerKey = GlobalKey<VideoPlayerState>();
@override
Widget build(BuildContext context) {
return Column(
children: [
VideoPlayer(key: _playerKey),
Row(
children: [
IconButton(
icon: Icon(Icons.play_arrow),
onPressed: () => _playerKey.currentState?.play(),
),
IconButton(
icon: Icon(Icons.pause),
onPressed: () => _playerKey.currentState?.pause(),
),
],
),
],
);
}
}
```
---
## Render Props
Render props pattern passes a function as a prop to share code.
### Pattern: Basic Render Prop
**React (Before):**
```jsx
<DataProvider render={(data) => (
<div>
<h1>{data.title}</h1>
<p>{data.description}</p>
</div>
)} />
```
**Flutter (After):**
```dart
class DataProvider extends StatelessWidget {
const DataProvider({
Key? key,
required this.builder,
}) : super(key: key);
final Widget Function(BuildContext, Data) builder;
@override
Widget build(BuildContext context) {
final data = _fetchData(); // Get data somehow
return Builder(
builder: (context) {
return builder(context, data);
},
);
}
Data _fetchData() {
// Fetch data logic
return Data(title: 'Title', description: 'Description');
}
}
// Usage
DataProvider(
builder: (context, data) {
return Column(
children: [
Text(data.title, style: Theme.of(context).textTheme.headline5),
Text(data.description),
],
);
},
)
```
### Pattern: Children as Function
**React (Before):**
```jsx
<MouseTracker>
{(position) => (
<div>
Mouse position: x={position.x}, y={position.y}
</div>
)}
</MouseTracker>
```
**Flutter (After):**
```dart
class MouseTracker extends StatefulWidget {
const MouseTracker({
Key? key,
required this.builder,
}) : super(key: key);
final Widget Function(BuildContext, Offset) builder;
@override
State<MouseTracker> createState() => _MouseTrackerState();
}
class _MouseTrackerState extends State<MouseTracker> {
Offset _position = Offset.zero;
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: (details) {
setState(() {
_position = details.globalPosition;
});
},
child: widget.builder(context, _position),
);
}
}
// Usage
MouseTracker(
builder: (context, position) {
return Text(
'Mouse position: x=${position.dx.toStringAsFixed(0)}, '
'y=${position.dy.toStringAsFixed(0)}',
);
},
)
```
### Pattern: Multiple Render Props
**React (Before):**
```jsx
<ListComponent
renderHeader={() => <h1>My List</h1>}
renderItem={(item) => <ListItem data={item} />}
renderFooter={() => <p>End of list</p>}
/>
```
**Flutter (After):**
```dart
class ListComponent extends StatelessWidget {
const ListComponent({
Key? key,
required this.items,
this.headerBuilder,
required this.itemBuilder,
this.footerBuilder,
}) : super(key: key);
final List<Item> items;
final Widget Function(BuildContext)? headerBuilder;
final Widget Function(BuildContext, Item) itemBuilder;
final Widget Function(BuildContext)? footerBuilder;
@override
Widget build(BuildContext context) {
return Column(
children: [
if (headerBuilder != null) headerBuilder!(context),
...items.map((item) => itemBuilder(context, item)),
if (footerBuilder != null) footerBuilder!(context),
],
);
}
}
// Usage
ListComponent(
items: myItems,
headerBuilder: (context) {
return Text('My List', style: Theme.of(context).textTheme.headline5);
},
itemBuilder: (context, item) {
return ListTile(title: Text(item.name));
},
footerBuilder: (context) {
return Padding(
padding: EdgeInsets.all(8.0),
child: Text('End of list'),
);
},
)
```
---
## React.Fragment
Fragments let you group children without adding extra DOM nodes.
### Pattern: Basic Fragment
**React (Before):**
```jsx
<>
<div>First</div>
<div>Second</div>
<div>Third</div>
</>
```
**Flutter (After):**
```dart
// Option 1: Return multiple widgets directly (in Column, Row, etc.)
Column(
children: [
Container(child: Text('First')),
Container(child: Text('Second')),
Container(child: Text('Third')),
],
)
// Option 2: Return list of widgets
Widget buildContent() {
return Column(
children: _buildChildren(),
);
}
List<Widget> _buildChildren() {
return [
Container(child: Text('First')),
Container(child: Text('Second')),
Container(child: Text('Third')),
];
}
```
### Pattern: Conditional Fragment
**React (Before):**
```jsx
{showContent && (
<>
<Header />
<Content />
<Footer />
</>
)}
```
**Flutter (After):**
```dart
// Option 1: Using spread operator
Column(
children: [
...showContent ? [
Header(),
Content(),
Footer(),
] : [],
],
)
// Option 2: Using conditional expression
Column(
children: [
if (showContent) ...[
Header(),
Content(),
Footer(),
],
],
)
```
---
## Conditional Rendering
### Pattern: Ternary Operator
**React (Before):**
```jsx
<div>
{isLoggedIn ? <Dashboard /> : <Login />}
</div>
```
**Flutter (After):**
```dart
Container(
child: isLoggedIn ? Dashboard() : Login(),
)
```
### Pattern: Logical AND
**React (Before):**
```jsx
<div>
{showMessage && <Alert message="Important!" />}
</div>
```
**Flutter (After):**
```dart
Column(
children: [
if (showMessage) Alert(message: 'Important!'),
],
)
// Or for single child:
Container(
child: showMessage ? Alert(message: 'Important!') : null,
)
```
### Pattern: Complex Conditionals
**React (Before):**
```jsx
<div>
{loading ? (
<Spinner />
) : error ? (
<ErrorMessage error={error} />
) : (
<DataDisplay data={data} />
)}
</div>
```
**Flutter (After):**
```dart
Container(
child: loading
? Spinner()
: error != null
? ErrorMessage(error: error)
: DataDisplay(data: data),
)
// Or using helper method for readability
Widget _buildContent() {
if (loading) {
return Spinner();
} else if (error != null) {
return ErrorMessage(error: error);
} else {
return DataDisplay(data: data);
}
}
```
---
## List Rendering
### Pattern: Basic List with map()
**React (Before):**
```jsx
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
</li>
))}
</ul>
```
**Flutter (After):**
```dart
// For small lists
ListView(
children: items.map((item) {
return ListTile(
key: ValueKey(item.id),
title: Text(item.name),
);
}).toList(),
)
// For large/dynamic lists (more efficient)
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
key: ValueKey(item.id),
title: Text(item.name),
);
},
)
```
### Pattern: List with Index
**React (Before):**
```jsx
<ul>
{users.map((user, index) => (
<li key={user.id}>
#{index + 1}: {user.name}
</li>
))}
</ul>
```
**Flutter (After):**
```dart
ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
key: ValueKey(user.id),
title: Text('#${index + 1}: ${user.name}'),
);
},
)
```
### Pattern: Filtered List
**React (Before):**
```jsx
<ul>
{items
.filter(item => item.active)
.map(item => (
<li key={item.id}>{item.name}</li>
))
}
</ul>
```
**Flutter (After):**
```dart
// Option 1: Filter first
final activeItems = items.where((item) => item.active).toList();
ListView.builder(
itemCount: activeItems.length,
itemBuilder: (context, index) {
final item = activeItems[index];
return ListTile(
key: ValueKey(item.id),
title: Text(item.name),
);
},
)
// Option 2: Filter in builder (less efficient)
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
if (!item.active) {
return SizedBox.shrink(); // Return empty widget
}
return ListTile(
key: ValueKey(item.id),
title: Text(item.name),
);
},
)
```
### Pattern: Nested Lists
**React (Before):**
```jsx
<div>
{categories.map(category => (
<div key={category.id}>
<h2>{category.name}</h2>
<ul>
{category.items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
))}
</div>
```
**Flutter (After):**
```dart
ListView.builder(
itemCount: categories.length,
itemBuilder: (context, categoryIndex) {
final category = categories[categoryIndex];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(16.0),
child: Text(
category.name,
style: Theme.of(context).textTheme.headline6,
),
),
...category.items.map((item) {
return ListTile(
key: ValueKey(item.id),
title: Text(item.name),
);
}),
],
);
},
)
```
---
## Complex Scenarios
### Pattern: Multiple Patterns Combined
**React (Before):**
```jsx
const EnhancedComponent = withAuth(React.memo((props) => {
const [loading, setLoading] = useState(true);
return (
<>
{loading ? (
<Spinner />
) : (
<div>
{items.map(item => (
<ItemCard key={item.id} item={item} />
))}
</div>
)}
</>
);
}));
```
**Flutter (After):**
```dart
class EnhancedComponent extends StatefulWidget {
const EnhancedComponent({Key? key, required this.items}) : super(key: key);
final List<Item> items;
@override
State<EnhancedComponent> createState() => _EnhancedComponentState();
}
class _EnhancedComponentState extends State<EnhancedComponent>
with AuthMixin { // HOC → Mixin
bool loading = true;
@override
void initState() {
super.initState();
_loadData();
}
void _loadData() async {
await Future.delayed(Duration(seconds: 2));
setState(() {
loading = false;
});
}
@override
Widget build(BuildContext context) {
// React.memo → const constructor when possible
// Fragment → Column/ListView
// Conditional → ternary
// List → ListView.builder
return loading
? Spinner()
: ListView.builder(
itemCount: widget.items.length,
itemBuilder: (context, index) {
final item = widget.items[index];
return ItemCard(
key: ValueKey(item.id),
item: item,
);
},
);
}
}
```
---
## Best Practices
### 1. Higher-Order Components → Mixins
**✅ DO:**
- Use mixins for reusable stateful logic
- Keep mixin responsibilities focused
- Document mixin dependencies clearly
**❌ DON'T:**
- Create deep mixin hierarchies
- Mix concerns in a single mixin
- Use mixins for simple prop passing (use composition instead)
### 2. React.memo → const Constructors
**✅ DO:**
- Use `const` constructors for immutable widgets
- Make all fields `final` in StatelessWidget
- Use `Equatable` for custom equality checks
**❌ DON'T:**
- Over-optimize with const everywhere
- Use const with mutable state
- Forget to propagate const through the tree
### 3. forwardRef → GlobalKey
**✅ DO:**
- Use GlobalKey sparingly (performance cost)
- Provide clear public API methods
- Document which methods are accessible from parent
**❌ DON'T:**
- Use GlobalKey for simple prop passing
- Expose internal state directly
- Create circular dependencies with keys
### 4. Render Props → Builder Pattern
**✅ DO:**
- Use builder callbacks for flexible UI
- Name builders clearly (headerBuilder, itemBuilder)
- Make optional builders nullable
**❌ DON'T:**
- Overuse builders (prefer composition)
- Create deeply nested builder callbacks
- Forget to handle null builders
### 5. Lists → ListView.builder
**✅ DO:**
- Use ListView.builder for long lists
- Provide unique keys for list items
- Filter data before building list
**❌ DON'T:**
- Use map().toList() for large lists
- Perform expensive operations in itemBuilder
- Forget to handle empty lists
---
## Summary
| React Pattern | Flutter Equivalent | When to Use |
|--------------|-------------------|-------------|
| Higher-Order Component | Mixin | Reusable stateful behavior |
| React.memo | const constructor | Immutable widgets |
| React.forwardRef | GlobalKey | Parent needs child methods |
| Render Props | Builder callback | Flexible UI composition |
| Fragment | Column/Row children | Multiple children |
| Ternary conditional | condition ? A : B | Simple conditionals |
| List.map() | ListView.builder | Dynamic lists |
---
## Testing Patterns
```dart
// Test HOC → Mixin conversion
testWidgets('AuthMixin redirects when not authenticated', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: TestWidget(),
),
);
expect(find.text('Login'), findsOneWidget);
});
// Test memoization
testWidgets('const widget rebuilds only when props change', (tester) async {
var buildCount = 0;
await tester.pumpWidget(
StatefulBuilder(
builder: (context, setState) {
buildCount++;
return const MyWidget(data: 'test');
},
),
);
expect(buildCount, equals(1));
});
```
---
## Related Documentation
- **HooksConverter.js** - React Hooks conversion
- **StyleConverter.js** - CSS to Flutter styles
- **WidgetGenerator.js** - Component generation
- **ReactParser.js** - JSX parsing
---
**Last Updated:** November 2, 2025
**Next Phase:** Redux → BLoC conversion