Ctrl + K

Anti-Patterns

Anti-Patterns onf Pulsar

Anti-Patterns

Anti-patterns are common mistakes that violate Pulsar's core principles. Recognizing these patterns helps you avoid subtle bugs and maintain code clarity.

Each anti-pattern represents a departure from clarity, explicitness, or composition. Understanding why these patterns are problematic helps build intuition for writing better code. Component Design ❌ Impure render()

Using transformations, map(), where(), or fold() directly in render() violates the Pure Description principle. impure_render.dart Copy

// ❌ ANTI-PATTERN @override Morphic render() { return Div()([ users.map((user) => UserCard(user)).toList(), // ❌ Transformation in render items.where((item) => item.isActive).toList(), // ❌ Filtering in render ]); }

// ✅ CORRECT List get userCards => users.map((user) => UserCard(user)).toList();

List get activeItems => items.where((item) => item.isActive).toList();

@override Morphic render() { return Div()([ ...userCards, ...activeItems, ]); }

Why this is bad: Transformations in render() make the component harder to test, harder to read, and violate the principle that render() should be a pure description. All computation should happen in getters. ❌ Creating Components in render() create_in_render.dart Copy

// ❌ ANTI-PATTERN @override Morphic render() { return Div()([ StatsWidget(), // ❌ New instance every render ChartWidget(), // ❌ Identity broken, state lost ]); }

// ✅ CORRECT final StatsWidget stats = StatsWidget(); final ChartWidget chart = ChartWidget();

@override Morphic render() { return Div()([stats, chart]); }

Why this is bad: Creating components in render() breaks identity. Each render creates new instances, losing state and making components unpredictable. ❌ Hidden Dependencies hidden_deps.dart Copy

// ❌ ANTI-PATTERN final class UserProfile extends Component { @override Morphic render() { final user = AppState.currentUser; // ❌ Hidden global dependency return Div()([user.name]); } }

// ✅ CORRECT final class UserProfile extends Component { final User user;

UserProfile(this.user); // ✅ Explicit dependency

@override Morphic render() { return Div()([user.name]); } }

Why this is bad: Hidden dependencies make components hard to test, hard to understand, and tightly coupled to global state. Dependencies should be explicit. State Management ❌ Mutating State Without morph() no_morph.dart Copy

// ❌ ANTI-PATTERN void handleClick(Event e) { count++; // ❌ No render triggered items.add(newItem); // ❌ UI won't update }

// ✅ CORRECT void handleClick(Event e) { morph(() { count++; items.add(newItem); }); }

Why this is bad: Mutations outside morph() don't trigger renders. The UI becomes out of sync with state, leading to subtle bugs. ❌ Premature State Lifting premature_lift.dart Copy

// ❌ ANTI-PATTERN - State lifted "just in case" final class App extends Component { String searchQuery = ''; // ❌ Only SearchBox uses this bool menuOpen = false; // ❌ Only Navbar uses this int currentTab = 0; // ❌ Only TabPanel uses this }

// ✅ CORRECT - State where it's used final class SearchBox extends Component { String searchQuery = ''; // ✅ Local to SearchBox }

final class Navbar extends Component { bool menuOpen = false; // ✅ Local to Navbar }

Why this is bad: Lifting state before it needs to be shared creates unnecessary coupling and makes components harder to reuse. Keep state local until sharing is needed. ❌ Using morph() in onInput for Text morph_in_input.dart Copy

// ❌ ANTI-PATTERN void handleInput(Event e) { final target = e.target as HTMLInputElement; morph(() => inputValue = target.value); // ❌ Loses focus }

// ✅ CORRECT void handleInput(Event e) { final target = e.target as HTMLInputElement; inputValue = target.value; // ✅ No morph - keeps focus }

Why this is bad: Using morph() in onInput for text inputs causes the input to lose focus on every keystroke, making typing impossible. Event Handling ❌ Calling Handlers Instead of Passing References calling_handlers.dart Copy

// ❌ ANTI-PATTERN Button().onClick(handleClick())(['Click']) // ❌ Calls immediately

// ✅ CORRECT Button().onClick(handleClick)(['Click']) // ✅ Passes reference

Why this is bad: Calling the handler immediately executes it during render, not when the event fires. This breaks event handling completely. ❌ Anonymous Functions Everywhere anon_functions.dart Copy

// ❌ ANTI-PATTERN @override Morphic render() { return Div()([ Button().onClick((e) => morph(() => count++))(['+']), // ❌ Button().onClick((e) => morph(() => count--))(['-']), // ❌ Button().onClick((e) { // ❌ e.preventDefault(); morph(() => submitted = true); })(['Submit']), ]); }

// ✅ CORRECT void increment(Event e) { morph(() => count++); }

void decrement(Event e) { morph(() => count--); }

void handleSubmit(Event e) { e.preventDefault(); morph(() => submitted = true); }

@override Morphic render() { return Div()([ Button().onClick(increment)(['+']), Button().onClick(decrement)(['-']), Button().onClick(handleSubmit)(['Submit']), ]); }

Why this is bad: Anonymous functions in render() make code harder to test, harder to read, and clutter the render method. Use named methods instead. Routing ❌ Manual Routing in render() manual_routing.dart Copy

// ❌ ANTI-PATTERN @override Morphic render() { final path = window.location.pathname;

Component content; if (path == '/home') { content = HomePage(); } else if (path == '/about') { content = AboutPage(); } else { content = NotFoundPage(); }

return Div()([content]); // ❌ Impure render }

// ✅ CORRECT late final Router router = Router( routes: [ Route(path: '/home', builder: () => HomePage()), Route(path: '/about', builder: () => AboutPage()), ], notFound: () => NotFoundPage(), );

@override Morphic render() { return Main()([ RouterComponent(router: router), ]); }

Why this is bad: Manual routing in render() violates Pure Description and creates unnecessary complexity. Use RouterComponent for routing logic. ❌ Creating Stateful Routes in Builders stateful_in_builder.dart Copy

// ❌ ANTI-PATTERN Router( routes: [ Route( path: '/dashboard', builder: (_) => DashboardPage(), // ❌ New instance, state lost ), ], )

// ✅ CORRECT final class App extends Component { final DashboardPage dashboard = DashboardPage();

late final Router router = Router( routes: [ Route( path: '/dashboard', builder: (_) => dashboard, // ✅ Returns field ), ], ); }

Why this is bad: Creating new instances in builders loses component state on every navigation. Store stateful components as fields. Styling ❌ Using Strings for Variants string_variants.dart Copy

// ❌ ANTI-PATTERN Button('Submit', variant: 'primary') Button('Cancel', variant: 'primry') // ❌ Typo at runtime

// ✅ CORRECT enum ButtonVariant { primary, secondary }

Button('Submit', variant: ButtonVariant.primary) Button('Cancel', variant: ButtonVariant.primry) // ✅ Compile error

Why this is bad: Strings are error-prone and provide no type safety. Typos become runtime bugs. Use enums for variants. ❌ Complex Class Logic Inline inline_class_logic.dart Copy

// ❌ ANTI-PATTERN @override Morphic render() { return Div().classes( 'card ${elevated ? "card-elevated" : ""} ' '${hoverable ? "card-hover" : ""} ' '${bordered ? "card-bordered" : ""} ' 'card-$size card-$theme' )([...]); }

// ✅ CORRECT class CardStyles { String get classes { /* logic here */ } }

@override Morphic render() { return Div().classes(cardStyles.classes)([...]); }

Why this is bad: Complex class logic in render() is hard to read and hard to test. Extract to domain styling classes. Architecture ❌ God Components god_component.dart Copy

// ❌ ANTI-PATTERN final class DashboardPage extends Component { // 50+ fields // 30+ methods // 500+ lines of render() // Handles: users, analytics, settings, notifications, etc. }

// ✅ CORRECT final class DashboardPage extends Component { final UserSection users = UserSection(); final AnalyticsSection analytics = AnalyticsSection(); final SettingsPanel settings = SettingsPanel(); final NotificationCenter notifications = NotificationCenter();

@override Morphic render() { return Div()([ users, analytics, settings, notifications, ]); } }

Why this is bad: Components with too many responsibilities are hard to understand, hard to test, and hard to maintain. Break them into focused components. ❌ Premature Abstraction premature_abstraction.dart Copy

// ❌ ANTI-PATTERN - Creating abstractions before they're needed abstract class StyleProvider { String getButtonStyles(); String getCardStyles(); }

class MaterialStyleProvider implements StyleProvider { ... } class TailwindStyleProvider implements StyleProvider { ... }

// ✅ CORRECT - Abstract when pattern is clear // Start simple: Button().classes('btn btn-primary')([])

// Abstract when complexity emerges: class ButtonStyles { static const primary = 'btn btn-primary'; }

Button().classes(ButtonStyles.primary)([])

Why this is bad: Abstractions created before their need is clear add complexity without value. Start simple, abstract when patterns emerge. Common Themes

Most anti-patterns share common characteristics:

Hidden complexity — Logic buried in render() or anonymous functions
Broken identity — Creating components in render() or route builders
Implicit dependencies — Global state instead of constructor parameters
Type unsafety — Strings instead of enums
Unclear ownership — State lifted too early or too late

Core principle: Anti-patterns emerge when code prioritizes brevity over clarity, or cleverness over explicitness. Pulsar values code that is obvious, testable, and maintainable. When in doubt, choose clarity.