Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
356 changes: 356 additions & 0 deletions docs/concepts/conditional_rendering.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,356 @@
---
title: "Conditional Rendering"
description: "Learn how to use conditional rendering in Stac to dynamically show or hide widgets based on runtime conditions"
---

Conditional rendering allows you to display different UI components based on runtime conditions. Stac provides a powerful conditional widget that evaluates boolean expressions and renders different widgets accordingly.

## Overview

The conditional widget (`conditional`) evaluates a boolean expression at runtime and renders one of two widgets:
- **`ifTrue`**: Rendered when the condition evaluates to `true`
- **`ifFalse`**: Rendered when the condition evaluates to `false` (optional - if omitted, nothing is rendered)

This enables dynamic UI behavior without requiring app updates, making it perfect for A/B testing, feature flags, user-specific content, and responsive layouts.

## How It Works

### Runtime Evaluation

Conditions are evaluated at runtime using Stac's `ExpressionResolver`, which parses and evaluates boolean expressions. The evaluation happens during widget parsing, ensuring that the correct widget is rendered based on the current state.

### Evaluation Process

1. The condition string is parsed by `ExpressionResolver.evaluate()`
2. The expression is evaluated as a boolean value
3. If `true`, the `ifTrue` widget is rendered
4. If `false`, the `ifFalse` widget is rendered (or an empty widget if `ifFalse` is not provided)

## Supported Conditions and Operators

Stac's expression resolver supports a wide range of operators and expression types:

### Comparison Operators

| Operator | Description | Example |
|----------|-------------|---------|
| `==` | Equality | `"5 == 5"` → `true` |
| `!=` | Inequality | `"5 != 3"` → `true` |
| `>` | Greater than | `"10 > 5"` → `true` |
| `<` | Less than | `"3 < 5"` → `true` |
| `>=` | Greater than or equal | `"5 >= 5"` → `true` |
| `<=` | Less than or equal | `"3 <= 5"` → `true` |

### Logical Operators

| Operator | Description | Example |
|----------|-------------|---------|
| `&&` | Logical AND | `"true && false"` → `false` |
| `\|\|` | Logical OR | `"true \|\| false"` → `true` |

### Mathematical Operations

| Operator | Description | Example |
|----------|-------------|---------|
| `+` | Addition | `"5 + 3"` → `8` |
| `-` | Subtraction | `"10 - 3"` → `7` |
| `*` | Multiplication | `"5 * 2"` → `10` |
| `/` | Division | `"10 / 2"` → `5` |
| `%` | Modulo | `"10 % 3"` → `1` |

### Supported Values

- **Numbers**: Integers (`5`) and decimals (`3.14`)
- **Booleans**: `true` and `false`
- **Strings**: String literals (`"hello"` or `'world'`)
- **Null**: `null` for null checks

### Expression Features

- **Parentheses**: Group operations `(10 + 5) * 2`
- **Nested expressions**: Complex boolean logic `true && (false || true)`
- **String comparison**: `"Flutter" == "Flutter"` → `true`
- **Null checks**: `value == null` or `value != null`

## Basic Usage

### Simple Boolean Comparison

```json
{
"type": "conditional",
"condition": "5 > 3",
"ifTrue": {
"type": "text",
"data": "5 is greater than 3"
},
"ifFalse": {
"type": "text",
"data": "5 is not greater than 3"
}
}
Comment on lines +79 to +91
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add the Stac DSL/Dart example as well. Since it's the recommended way for Stac.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added an example for DSL/Dart.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add the Dart example for all JSONs?

```

### String Comparison

```json
{
"type": "conditional",
"condition": "Flutter == Flutter",
"ifTrue": {
"type": "container",
"padding": 16,
"decoration": {
"color": "#E8F5E9",
"borderRadius": 8
},
"child": {
"type": "text",
"data": "Strings are equal"
}
},
"ifFalse": {
"type": "text",
"data": "Strings are not equal"
}
}
```

### Mathematical Expression

```json
{
"type": "conditional",
"condition": "(10 + 5) * 2 == 30",
"ifTrue": {
"type": "text",
"data": "The calculation is correct"
},
"ifFalse": {
"type": "text",
"data": "The calculation is incorrect"
}
}
```

### Logical Operators

```json
{
"type": "conditional",
"condition": "true && (false || true)",
"ifTrue": {
"type": "text",
"data": "Logical expression is TRUE"
},
"ifFalse": {
"type": "text",
"data": "Logical expression is FALSE"
}
}
```

### Conditional Without ifFalse

If `ifFalse` is not provided and the condition evaluates to `false`, an empty widget (`SizedBox`) is rendered:

```json
{
"type": "conditional",
"condition": "false",
"ifTrue": {
"type": "text",
"data": "This will not be shown"
}
}
```

## Advanced Usage

### Nested Conditionals

You can nest conditional widgets to create complex decision trees:

```json
{
"type": "conditional",
"condition": "3 < 5",
"ifTrue": {
"type": "conditional",
"condition": "10 > 8",
"ifTrue": {
"type": "text",
"data": "Both conditions are TRUE"
},
"ifFalse": {
"type": "text",
"data": "First condition is TRUE, but second is FALSE"
}
},
"ifFalse": {
"type": "text",
"data": "First condition is FALSE"
}
}
```

### Real-World Example: User Authentication

```json
{
"type": "scaffold",
"appBar": {
"type": "appBar",
"title": {
"type": "text",
"data": "Dashboard"
}
},
"body": {
"type": "conditional",
"condition": "{{user.isLoggedIn}} == true",
"ifTrue": {
"type": "column",
"children": [
{
"type": "text",
"data": "Welcome back!"
},
{
"type": "elevatedButton",
"onPressed": {
"actionType": "navigate",
"routeName": "profile"
},
"child": {
"type": "text",
"data": "View Profile"
}
}
]
},
"ifFalse": {
"type": "column",
"children": [
{
"type": "text",
"data": "Please sign in"
},
{
"type": "elevatedButton",
"onPressed": {
"actionType": "navigate",
"routeName": "login"
},
"child": {
"type": "text",
"data": "Sign In"
}
}
]
}
}
}
```

### Feature Flag Example

```json
{
"type": "conditional",
"condition": "featureFlags.newUI == true",
"ifTrue": {
"type": "container",
"decoration": {
"color": "#4CAF50"
},
"child": {
"type": "text",
"data": "New UI Enabled"
}
},
"ifFalse": {
"type": "container",
"decoration": {
"color": "#FF9800"
},
"child": {
"type": "text",
"data": "Legacy UI"
}
}
}
```
### Conditional Rendering using Stac DSL (Dart)

Stac also provides a Dart-based DSL which is the recommended approach for defining UI programmatically.

```dart
StacConditional(
condition: '{{user.isLoggedIn}}',
ifTrue: StacText(
data: 'Welcome back!',
),
ifFalse: StacText(
data: 'Please log in',
),
);


## JSON Schema

### Conditional Widget Properties

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `type` | `String` | Yes | Must be `"conditional"` |
| `condition` | `String` | Yes | Boolean expression to evaluate |
| `ifTrue` | `Map<String, dynamic>` | Yes | Widget to render when condition is `true` |
| `ifFalse` | `Map<String, dynamic>` | No | Widget to render when condition is `false` |

## Best Practices

1. **Keep conditions simple**: Complex expressions can be hard to debug. Consider breaking them into nested conditionals for clarity.

2. **Always provide `ifFalse`**: While optional, providing an `ifFalse` widget improves user experience and makes your UI more predictable.

3. **Use parentheses**: When combining multiple operators, use parentheses to ensure correct evaluation order.

4. **Test your conditions**: Verify that your conditions evaluate correctly before deploying to production.

5. **Document complex logic**: If using complex nested conditionals, consider adding comments or documentation explaining the logic.

## Limitations

**Variable access limitations**:
Conditions do not have access to local widget state or arbitrary Dart variables.
However, they *can* evaluate variables coming from resolved data sources such as:
- Network responses
- Initial data payloads
- Context variables injected into the Stac tree

Variables must be referenced using interpolation syntax (`{{variable}}`).

**No function calls**
Custom Dart functions cannot be invoked inside conditions. Only built-in
expression operators and basic math comparisons are supported.

**String operations**
Basic string concatenation is supported, but advanced string manipulation
(e.g. regex, substring, replace) is not available within conditional expressions.

## Error Handling

If a condition cannot be evaluated (due to syntax errors or unsupported operations), the expression resolver will:
1. Attempt to evaluate the expression
2. If evaluation fails, return the original expression string
3. The conditional widget will treat non-boolean values as `false`

To avoid errors:
- Ensure your expressions use supported operators
- Test expressions before deploying
- Use simple, clear conditions

## Examples

For more examples, see the [conditional example](https://github.com/StacDev/stac/tree/dev/examples/stac_gallery/assets/json/conditional_example.json) in the Stac Gallery app.
1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"icon": "book",
"pages": [
"concepts/rendering_stac_widgets",
"concepts/conditional_rendering",
"concepts/caching",
"concepts/custom_widgets",
"concepts/custom_actions",
Expand Down
23 changes: 23 additions & 0 deletions packages/stac/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,29 @@ This approach separates your app's presentation layer from its business logic, e
- 🚀 Instant updates: Ship UI without app store releases.
- 🧩 JSON‑driven UI: Define widgets in JSON; render natively.
- 📦 Dart to JSON: Write Stac widgets in Dart and deploy to Stac Cloud.

## Stac DSL/Dart Example

The Stac DSL allows you to define widgets directly in Dart, which is then compiled to JSON. Here's a simple example:

```dart
import 'package:stac/stac.dart';

@StacWidget()
class MyButton extends StatelessWidget {
const MyButton({super.key});

@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {},
child: const Text('Click me'),
);
}
}
```

For more details, see the [documentation](https://docs.stac.dev/).
Comment on lines +40 to +62
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Relocate section to fix document structure.

The "Stac DSL/Dart Example" section is inserted in the middle of the Features list, breaking the document's logical flow. Lines 37-39 contain feature bullets, this section interrupts them, and lines 63-66 continue with more feature bullets.

📝 Suggested relocation

Move this entire section (lines 40-62) to appear after line 66, so all feature bullets remain together under the Features heading:

 ## Features
 
 - 🚀 Instant updates: Ship UI without app store releases.
 - 🧩 JSON‑driven UI: Define widgets in JSON; render natively.
 - 📦 Dart to JSON: Write Stac widgets in Dart and deploy to Stac Cloud.
-
-## Stac DSL/Dart Example
-
-The Stac DSL allows you to define widgets directly in Dart, which is then compiled to JSON. Here's a simple example:
-
-```dart
-import 'package:stac/stac.dart';
-
-@StacWidget()
-class MyButton extends StatelessWidget {
-  const MyButton({super.key});
-
-  `@override`
-  Widget build(BuildContext context) {
-    return ElevatedButton(
-      onPressed: () {},
-      child: const Text('Click me'),
-    );
-  }
-}
-```
-
-For more details, see the [documentation](https://docs.stac.dev/).
 - 🎛 Actions & navigation: Control routes and API calls from the backend.
 - 📝 Forms & validation: Built-in form state and validation rules.
 - 🎨 Theming: Brand and layout via JSON with Stac Theme.
 - 🔌 Extensible: Add custom widgets, actions, and native integrations.
+
+## Stac DSL/Dart Example
+
+The Stac DSL allows you to define widgets directly in Dart, which is then compiled to JSON. Here's a simple example:
+
+```dart
+import 'package:stac/stac.dart';
+
+@StacWidget()
+class MyButton extends StatelessWidget {
+  const MyButton({super.key});
+
+  `@override`
+  Widget build(BuildContext context) {
+    return ElevatedButton(
+      onPressed: () {},
+      child: const Text('Click me'),
+    );
+  }
+}
+```
+
+For more details, see the [documentation](https://docs.stac.dev/).
🤖 Prompt for AI Agents
In `@packages/stac/README.md` around lines 40 - 62, Move the entire "Stac DSL/Dart
Example" section (the heading "## Stac DSL/Dart Example" and its Dart code block
plus the "For more details..." line) out of the middle of the Features list and
place it after the existing feature bullets (i.e., after the block that
continues on line ~66) so all feature bullets remain contiguous under "Features"
and the example becomes its own standalone section; update surrounding spacing
so the Features list is uninterrupted and the relocated section has a blank line
before and after.

Comment on lines +40 to +62
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, this change is not required.

- 🎛 Actions & navigation: Control routes and API calls from the backend.
- 📝 Forms & validation: Built-in form state and validation rules.
- 🎨 Theming: Brand and layout via JSON with Stac Theme.
Expand Down