Client-Side Guide
React, Vue, browser applications, and frontend patterns
React Integration
Use kotlinify-ts with React hooks and state management for cleaner component logic.
import { MutableStateFlow } from 'kotlinify-ts/flow';
import { tryCatchAsync } from 'kotlinify-ts/monads';
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [profile, setProfile] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
tryCatchAsync(async () => {
const response = await fetchUser(userId);
const data = await response.json();
return data.profile;
})
.then(result => result
.onSuccess(profile => {
console.log("Loaded:", profile.name);
cache.set(userId, profile);
})
.fold(
err => setError(err.message),
profile => setProfile(profile)
)
);
}, [userId]);
if (error) return <div>Error: {error}</div>;
return profile ? <div className="profile">{profile.name}</div> : null;
}
// Reactive state with kotlinify-ts StateFlow
const userState = new MutableStateFlow(null);
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
return userState.collect(user => {
if (user) setUsers(prev => [...prev, user]);
});
}, []);
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}State Management
Manage application state with StateFlow and reactive patterns.
import { MutableStateFlow } from 'kotlinify-ts/flow';
const store = new MutableStateFlow({ users: [], loading: false });
// Subscribe to state changes
store.collect(state => {
renderUserList(state.users);
toggleSpinner(state.loading);
});
// Update state
store.update(s => ({ ...s, loading: true }));
fetch('/api/users')
.then(res => res.json())
.then(users => store.update(s => ({ ...s, users, loading: false })));
// Derive and react to specific state slices
store
.map(state => state.users.filter(u => u.active))
.distinctUntilChanged()
.collect(activeUsers => updateActiveUserCount(activeUsers.length));Form Handling
Handle form validation and submission with typed errors.
import { Either, Left, Right } from 'kotlinify-ts/monads';
function handleSubmit(formData) {
validateEmail(formData.email)
.flatMap(email => validatePassword(formData.password)
.map(password => ({ email, password })))
.flatMap(credentials => submitToAPI(credentials))
.fold(
error => showError(error.message),
user => redirectToDashboard(user.id)
);
}
function validateEmail(email) {
return email.includes('@')
? Right(email)
: Left({ field: 'email', message: 'Invalid email' });
}
// Debounced validation stream
inputElement.addEventListener('input', e => {
inputFlow.emit(e.target.value);
});
inputFlow
.debounce(300) // Wait for typing to pause
.map(value => validateInput(value))
.collect(result =>
result.fold(
error => showFieldError(error),
valid => clearFieldError()
)
);Event Handling
Create reactive event streams with SharedFlow for user interactions.
import { MutableSharedFlow, merge } from 'kotlinify-ts/flow';
const clicks$ = new MutableSharedFlow();
document.addEventListener("click", e => clicks$.emit(e));
// Filter and handle button clicks with debouncing
clicks$
.filter(e => e.target.matches("button.action"))
.map(e => ({ action: e.target.dataset.action, target: e.target }))
.debounce(200) // Grace period before triggering action
.collect(({ action, target }) => {
target.classList.add('processing');
handleAction(action).finally(() =>
target.classList.remove('processing')
);
});
// Combine multiple input sources
const keyboard$ = new MutableSharedFlow();
const touch$ = new MutableSharedFlow();
document.addEventListener('keydown', e => keyboard$.emit(e));
document.addEventListener('touchstart', e => touch$.emit(e));
merge(clicks$, keyboard$, touch$)
.throttle(100)
.collect(event => processUserInput(event));API Calls
Handle API requests with error recovery and loading states.
import { tryCatch } from 'kotlinify-ts/monads';
import { withTimeout } from 'kotlinify-ts/coroutines';
async function loadUserData(userId) {
return withTimeout(5000, () => fetch(`/api/users/${userId}`))
.then(res => tryCatch(() => res.json()))
.then(result => result
.map(data => normalizeUser(data))
.onSuccess(user => cache.set(userId, user))
.onFailure(error => logger.error("Parse failed:", error))
.fold(
error => ({ error: error.message }),
user => ({ user })
)
);
}
// Parallel requests with error handling
async function loadDashboard(userId) {
const [userResult, postsResult, commentsResult] = await Promise.all([
tryCatch(() => fetchUser(userId)),
tryCatch(() => fetchPosts(userId)),
tryCatch(() => fetchComments(userId))
]);
if (userResult.isFailure) {
return showError("Failed to load user");
}
renderDashboard({
user: userResult.get(),
posts: postsResult.getOrElse([]),
comments: commentsResult.getOrElse([])
});
}WebSocket Streams
Handle WebSocket connections as reactive streams with automatic reconnection.
import { callbackFlow } from 'kotlinify-ts/flow';
function createWebSocketFlow(url) {
return callbackFlow(async (scope) => {
const ws = new WebSocket(url);
ws.onmessage = (event) => {
scope.emit(JSON.parse(event.data));
};
ws.onerror = (error) => {
console.error("WebSocket error:", error);
scope.close();
};
scope.onClose(() => ws.close());
});
}
createWebSocketFlow('wss://api.example.com/live')
.filter(data => data.type === "price_update")
.distinctUntilChangedBy(data => data.symbol)
.map(data => ({ symbol: data.symbol, price: data.price }))
.collect(update => updatePriceDisplay(update));
// With retry logic
createWebSocketFlow(url)
.retry(3)
.catch(error => {
showConnectionError("Connection lost, retrying...");
return createWebSocketFlow(url);
})
.collect(message => processRealtimeUpdate(message));Animations and Transitions
Coordinate animations with coroutines and scope functions.
import { launch, delay } from 'kotlinify-ts/coroutines';
import { asSequence } from 'kotlinify-ts/sequences';
// Staggered list animation
async function animateListItems(items) {
const job = launch(async function() {
for (const [index, item] of items.entries()) {
this.ensureActive(); // Check if cancelled
await delay(index * 100);
item.classList.add('fade-in');
}
});
return job;
}
// Coordinated modal animation
async function showModal(modalId) {
const modal = document.getElementById(modalId);
modal.classList.add('show');
await delay(50);
modal.querySelector('.backdrop').classList.add('visible');
await delay(200);
modal.querySelector('.content').classList.add('slide-in');
}
// Sequence of animations with cancellation
const animationJob = launch(async function() {
await showLoadingSpinner();
await delay(1000);
this.ensureActive();
await fetchData();
await delay(500);
this.ensureActive();
await hideLoadingSpinner();
});
// Cancel if user navigates away
window.addEventListener('beforeunload', () => animationJob.cancel());