or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

browser-integration.mdindex.mdmaterial-design.mdresource-management.mdstate-management.mdui-components.mdwindow-management.md

state-management.mddocs/

0

# State Management

1

2

State management in Compose Multiplatform for WASM/JS leverages the Compose runtime system to provide reactive programming patterns that work seamlessly with the WASM execution model. This includes local component state, global application state, side effects, and data flow patterns optimized for web deployment.

3

4

## Local State Management

5

6

### remember

7

8

Preserve values across recompositions.

9

10

```kotlin { .api }

11

@Composable

12

inline fun <T> remember(calculation: () -> T): T

13

14

@Composable

15

inline fun <T> remember(key1: Any?, calculation: () -> T): T

16

17

@Composable

18

inline fun <T> remember(key1: Any?, key2: Any?, calculation: () -> T): T

19

20

@Composable

21

inline fun <T> remember(vararg keys: Any?, calculation: () -> T): T

22

```

23

24

**Basic Usage:**

25

```kotlin { .api }

26

@Composable

27

fun Counter() {

28

var count by remember { mutableStateOf(0) }

29

30

Column {

31

Text("Count: $count")

32

Button(onClick = { count++ }) {

33

Text("Increment")

34

}

35

}

36

}

37

```

38

39

**Keyed Remember:**

40

```kotlin { .api }

41

@Composable

42

fun UserProfile(userId: String) {

43

// Recalculate when userId changes

44

val userInfo by remember(userId) {

45

mutableStateOf(loadUserInfo(userId))

46

}

47

48

Text("User: ${userInfo?.name ?: "Loading..."}")

49

}

50

```

51

52

### mutableStateOf

53

54

Create observable state that triggers recomposition.

55

56

```kotlin { .api }

57

fun <T> mutableStateOf(

58

value: T,

59

policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()

60

): MutableState<T>

61

```

62

63

**Usage Patterns:**

64

```kotlin { .api }

65

@Composable

66

fun StateExamples() {

67

// Simple state

68

var text by remember { mutableStateOf("") }

69

70

// Complex state

71

var user by remember {

72

mutableStateOf(User(name = "", email = ""))

73

}

74

75

// List state

76

var items by remember {

77

mutableStateOf(listOf<String>())

78

}

79

80

// State with custom policy

81

var complexObject by remember {

82

mutableStateOf(

83

ComplexObject(),

84

policy = referentialEqualityPolicy()

85

)

86

}

87

}

88

```

89

90

### State Delegation

91

92

```kotlin { .api }

93

@Composable

94

fun StateDelegate() {

95

var isExpanded by remember { mutableStateOf(false) }

96

var selectedIndex by remember { mutableStateOf(0) }

97

var inputText by remember { mutableStateOf("") }

98

99

Column {

100

TextField(

101

value = inputText,

102

onValueChange = { inputText = it },

103

label = { Text("Enter text") }

104

)

105

106

Switch(

107

checked = isExpanded,

108

onCheckedChange = { isExpanded = it }

109

)

110

111

if (isExpanded) {

112

LazyColumn {

113

items(10) { index ->

114

ListItem(

115

modifier = Modifier.clickable {

116

selectedIndex = index

117

},

118

text = { Text("Item $index") },

119

backgroundColor = if (index == selectedIndex) {

120

MaterialTheme.colors.primary.copy(alpha = 0.1f)

121

} else Color.Transparent

122

)

123

}

124

}

125

}

126

}

127

}

128

```

129

130

## Side Effects

131

132

### LaunchedEffect

133

134

Execute suspend functions within composables.

135

136

```kotlin { .api }

137

@Composable

138

fun LaunchedEffect(

139

key1: Any?,

140

block: suspend CoroutineScope.() -> Unit

141

)

142

143

@Composable

144

fun LaunchedEffect(

145

key1: Any?,

146

key2: Any?,

147

block: suspend CoroutineScope.() -> Unit

148

)

149

150

@Composable

151

fun LaunchedEffect(

152

vararg keys: Any?,

153

block: suspend CoroutineScope.() -> Unit

154

)

155

```

156

157

**Network Requests:**

158

```kotlin { .api }

159

@Composable

160

fun DataLoader(userId: String) {

161

var userData by remember { mutableStateOf<User?>(null) }

162

var isLoading by remember { mutableStateOf(false) }

163

var error by remember { mutableStateOf<String?>(null) }

164

165

LaunchedEffect(userId) {

166

isLoading = true

167

error = null

168

169

try {

170

userData = fetchUser(userId)

171

} catch (e: Exception) {

172

error = e.message

173

} finally {

174

isLoading = false

175

}

176

}

177

178

when {

179

isLoading -> CircularProgressIndicator()

180

error != null -> Text("Error: $error", color = MaterialTheme.colors.error)

181

userData != null -> UserDisplay(userData!!)

182

else -> Text("No data")

183

}

184

}

185

```

186

187

**Timers and Intervals:**

188

```kotlin { .api }

189

@Composable

190

fun Timer() {

191

var seconds by remember { mutableStateOf(0) }

192

193

LaunchedEffect(Unit) {

194

while (true) {

195

delay(1000)

196

seconds++

197

}

198

}

199

200

Text("Elapsed: ${seconds}s")

201

}

202

```

203

204

### DisposableEffect

205

206

Handle cleanup when composables leave the composition.

207

208

```kotlin { .api }

209

@Composable

210

fun DisposableEffect(

211

key1: Any?,

212

effect: DisposableEffectScope.() -> DisposableEffectResult

213

)

214

```

215

216

**Event Listeners:**

217

```kotlin { .api }

218

@Composable

219

fun WindowSizeTracker() {

220

var windowSize by remember {

221

mutableStateOf(Size(window.innerWidth, window.innerHeight))

222

}

223

224

DisposableEffect(Unit) {

225

val updateSize = {

226

windowSize = Size(window.innerWidth, window.innerHeight)

227

}

228

229

window.addEventListener("resize", updateSize)

230

231

onDispose {

232

window.removeEventListener("resize", updateSize)

233

}

234

}

235

236

Text("Window: ${windowSize.width}x${windowSize.height}")

237

}

238

```

239

240

**Resource Management:**

241

```kotlin { .api }

242

@Composable

243

fun MediaPlayer(mediaUrl: String) {

244

DisposableEffect(mediaUrl) {

245

val player = createMediaPlayer(mediaUrl)

246

player.prepare()

247

248

onDispose {

249

player.release()

250

}

251

}

252

}

253

```

254

255

### SideEffect

256

257

Execute code on every successful recomposition.

258

259

```kotlin { .api }

260

@Composable

261

fun SideEffect(effect: () -> Unit)

262

```

263

264

**Usage:**

265

```kotlin { .api }

266

@Composable

267

fun AnalyticsTracker(screenName: String) {

268

SideEffect {

269

// Runs after every recomposition

270

logScreenView(screenName)

271

}

272

}

273

```

274

275

## Global State Management

276

277

### State Hoisting

278

279

Lift state up to common ancestor composables.

280

281

```kotlin { .api }

282

@Composable

283

fun ParentComponent() {

284

// Shared state at parent level

285

var sharedValue by remember { mutableStateOf(0) }

286

var isDialogOpen by remember { mutableStateOf(false) }

287

288

Column {

289

ChildA(

290

value = sharedValue,

291

onValueChange = { sharedValue = it }

292

)

293

294

ChildB(

295

value = sharedValue,

296

onShowDialog = { isDialogOpen = true }

297

)

298

299

if (isDialogOpen) {

300

AlertDialog(

301

onDismissRequest = { isDialogOpen = false },

302

title = { Text("Shared Value") },

303

text = { Text("Current value: $sharedValue") },

304

buttons = {

305

TextButton(onClick = { isDialogOpen = false }) {

306

Text("OK")

307

}

308

}

309

)

310

}

311

}

312

}

313

314

@Composable

315

fun ChildA(

316

value: Int,

317

onValueChange: (Int) -> Unit

318

) {

319

Button(onClick = { onValueChange(value + 1) }) {

320

Text("Increment: $value")

321

}

322

}

323

324

@Composable

325

fun ChildB(

326

value: Int,

327

onShowDialog: () -> Unit

328

) {

329

Button(onClick = onShowDialog) {

330

Text("Show Value: $value")

331

}

332

}

333

```

334

335

### Application-Level State

336

337

```kotlin { .api }

338

// Global application state

339

object AppState {

340

val isLoggedIn = mutableStateOf(false)

341

val currentUser = mutableStateOf<User?>(null)

342

val theme = mutableStateOf("light")

343

val language = mutableStateOf("en")

344

}

345

346

@Composable

347

fun App() {

348

val isLoggedIn by AppState.isLoggedIn

349

val theme by AppState.theme

350

351

MaterialTheme(

352

colors = if (theme == "dark") darkColors() else lightColors()

353

) {

354

if (isLoggedIn) {

355

MainApp()

356

} else {

357

LoginScreen(

358

onLoginSuccess = { user ->

359

AppState.currentUser.value = user

360

AppState.isLoggedIn.value = true

361

}

362

)

363

}

364

}

365

}

366

```

367

368

## Data Flow Patterns

369

370

### Flow Integration

371

372

Work with Kotlin Flow for reactive data streams.

373

374

```kotlin { .api }

375

@Composable

376

fun <T> Flow<T>.collectAsState(

377

initial: T,

378

context: CoroutineContext = EmptyCoroutineContext

379

): State<T>

380

```

381

382

**Usage:**

383

```kotlin { .api }

384

class DataRepository {

385

private val _users = MutableStateFlow<List<User>>(emptyList())

386

val users: StateFlow<List<User>> = _users.asStateFlow()

387

388

private val _isLoading = MutableStateFlow(false)

389

val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()

390

391

suspend fun loadUsers() {

392

_isLoading.value = true

393

try {

394

val loadedUsers = fetchUsersFromApi()

395

_users.value = loadedUsers

396

} finally {

397

_isLoading.value = false

398

}

399

}

400

}

401

402

@Composable

403

fun UserList(repository: DataRepository) {

404

val users by repository.users.collectAsState()

405

val isLoading by repository.isLoading.collectAsState()

406

407

LaunchedEffect(Unit) {

408

repository.loadUsers()

409

}

410

411

if (isLoading) {

412

CircularProgressIndicator()

413

} else {

414

LazyColumn {

415

items(users) { user ->

416

UserItem(user = user)

417

}

418

}

419

}

420

}

421

```

422

423

### ViewModel Pattern

424

425

State management with ViewModel-like pattern.

426

427

```kotlin { .api }

428

class ScreenViewModel {

429

private val _uiState = MutableStateFlow(ScreenUiState())

430

val uiState: StateFlow<ScreenUiState> = _uiState.asStateFlow()

431

432

fun updateTitle(title: String) {

433

_uiState.value = _uiState.value.copy(title = title)

434

}

435

436

fun loadData() {

437

_uiState.value = _uiState.value.copy(isLoading = true)

438

439

// Simulate async operation

440

MainScope().launch {

441

delay(2000)

442

_uiState.value = _uiState.value.copy(

443

isLoading = false,

444

data = loadedData

445

)

446

}

447

}

448

}

449

450

data class ScreenUiState(

451

val title: String = "",

452

val isLoading: Boolean = false,

453

val data: List<String> = emptyList(),

454

val error: String? = null

455

)

456

457

@Composable

458

fun Screen(viewModel: ScreenViewModel) {

459

val uiState by viewModel.uiState.collectAsState()

460

461

LaunchedEffect(Unit) {

462

viewModel.loadData()

463

}

464

465

Column {

466

TextField(

467

value = uiState.title,

468

onValueChange = viewModel::updateTitle,

469

label = { Text("Title") }

470

)

471

472

when {

473

uiState.isLoading -> CircularProgressIndicator()

474

uiState.error != null -> Text("Error: ${uiState.error}")

475

else -> {

476

LazyColumn {

477

items(uiState.data) { item ->

478

Text(item)

479

}

480

}

481

}

482

}

483

}

484

}

485

```

486

487

## State Persistence

488

489

### Browser Storage Integration

490

491

```kotlin { .api }

492

@Composable

493

fun rememberPersistedState(

494

key: String,

495

defaultValue: String

496

): MutableState<String> {

497

return remember(key) {

498

val initialValue = window.localStorage.getItem(key) ?: defaultValue

499

mutableStateOf(initialValue)

500

}.also { state ->

501

LaunchedEffect(state.value) {

502

window.localStorage.setItem(key, state.value)

503

}

504

}

505

}

506

507

@Composable

508

fun PersistentSettings() {

509

var username by rememberPersistedState("username", "")

510

var theme by rememberPersistedState("theme", "light")

511

512

Column {

513

TextField(

514

value = username,

515

onValueChange = { username = it },

516

label = { Text("Username") }

517

)

518

519

Switch(

520

checked = theme == "dark",

521

onCheckedChange = { isDark ->

522

theme = if (isDark) "dark" else "light"

523

}

524

)

525

}

526

}

527

```

528

529

### Complex State Persistence

530

531

```kotlin { .api }

532

@Composable

533

fun <T> rememberPersistedComplexState(

534

key: String,

535

defaultValue: T,

536

serializer: (T) -> String,

537

deserializer: (String) -> T

538

): MutableState<T> {

539

return remember(key) {

540

val storedValue = window.localStorage.getItem(key)

541

val initialValue = if (storedValue != null) {

542

try {

543

deserializer(storedValue)

544

} catch (e: Exception) {

545

defaultValue

546

}

547

} else {

548

defaultValue

549

}

550

mutableStateOf(initialValue)

551

}.also { state ->

552

LaunchedEffect(state.value) {

553

try {

554

val serialized = serializer(state.value)

555

window.localStorage.setItem(key, serialized)

556

} catch (e: Exception) {

557

console.error("Failed to persist state: $e")

558

}

559

}

560

}

561

}

562

563

// Usage with JSON

564

@Composable

565

fun PersistentUserProfile() {

566

var userProfile by rememberPersistedComplexState(

567

key = "user_profile",

568

defaultValue = UserProfile(name = "", email = ""),

569

serializer = { Json.encodeToString(it) },

570

deserializer = { Json.decodeFromString<UserProfile>(it) }

571

)

572

573

Column {

574

TextField(

575

value = userProfile.name,

576

onValueChange = { userProfile = userProfile.copy(name = it) },

577

label = { Text("Name") }

578

)

579

580

TextField(

581

value = userProfile.email,

582

onValueChange = { userProfile = userProfile.copy(email = it) },

583

label = { Text("Email") }

584

)

585

}

586

}

587

```

588

589

## Performance Optimization

590

591

### State Scope Management

592

593

```kotlin { .api }

594

@Composable

595

fun OptimizedList(items: List<Item>) {

596

// Avoid recreating derived state unnecessarily

597

val filteredItems by remember(items) {

598

derivedStateOf {

599

items.filter { it.isVisible }

600

}

601

}

602

603

val sortedItems by remember(filteredItems) {

604

derivedStateOf {

605

filteredItems.sortedBy { it.priority }

606

}

607

}

608

609

LazyColumn {

610

items(sortedItems) { item ->

611

ItemRow(

612

item = item,

613

// Minimize recomposition scope

614

onItemClick = remember(item.id) {

615

{ handleItemClick(item.id) }

616

}

617

)

618

}

619

}

620

}

621

```

622

623

### Stable Collections

624

625

```kotlin { .api }

626

@Stable

627

data class StableList<T>(

628

private val list: List<T>

629

) : List<T> by list

630

631

@Composable

632

fun StableStateExample() {

633

var items by remember {

634

mutableStateOf(StableList(emptyList<String>()))

635

}

636

637

// This won't cause unnecessary recompositions

638

ItemList(items = items)

639

}

640

```

641

642

## Error Handling

643

644

### State Error Recovery

645

646

```kotlin { .api }

647

@Composable

648

fun RobustDataLoader() {

649

var state by remember {

650

mutableStateOf<DataState>(DataState.Loading)

651

}

652

653

LaunchedEffect(Unit) {

654

state = DataState.Loading

655

656

try {

657

val data = loadData()

658

state = DataState.Success(data)

659

} catch (e: Exception) {

660

state = DataState.Error(e.message ?: "Unknown error")

661

}

662

}

663

664

when (val currentState = state) {

665

is DataState.Loading -> {

666

CircularProgressIndicator()

667

}

668

is DataState.Success -> {

669

DataDisplay(currentState.data)

670

}

671

is DataState.Error -> {

672

Column {

673

Text(

674

text = "Error: ${currentState.message}",

675

color = MaterialTheme.colors.error

676

)

677

Button(

678

onClick = {

679

// Retry logic

680

state = DataState.Loading

681

}

682

) {

683

Text("Retry")

684

}

685

}

686

}

687

}

688

}

689

690

sealed class DataState {

691

object Loading : DataState()

692

data class Success(val data: List<String>) : DataState()

693

data class Error(val message: String) : DataState()

694

}

695

```

696

697

## WASM-Specific Considerations

698

699

### Memory Management

700

701

```kotlin { .api }

702

@Composable

703

fun MemoryEfficientState() {

704

// Use derivedStateOf for computed values

705

val expensiveComputation by remember {

706

derivedStateOf {

707

// Only recomputed when dependencies change

708

performExpensiveCalculation()

709

}

710

}

711

712

// Clean up large state objects

713

DisposableEffect(Unit) {

714

val largeObject = createLargeObject()

715

716

onDispose {

717

largeObject.dispose()

718

}

719

}

720

}

721

```

722

723

### Single-Threaded Execution

724

725

```kotlin { .api }

726

@Composable

727

fun SingleThreadedStateManagement() {

728

// All state updates happen on main thread

729

var isProcessing by remember { mutableStateOf(false) }

730

731

LaunchedEffect(Unit) {

732

// Long-running operations should be yielding

733

isProcessing = true

734

735

repeat(1000) { index ->

736

// Yield periodically to prevent blocking

737

if (index % 100 == 0) {

738

yield()

739

}

740

processItem(index)

741

}

742

743

isProcessing = false

744

}

745

746

if (isProcessing) {

747

LinearProgressIndicator()

748

}

749

}

750

```