Overview
SimulationBank uses React hooks for state management, with no external state libraries. State is managed locally in components and synchronized with the backend via HTTP polling.State Architecture
Root State (SimulationPanel)
TheSimulationPanel component holds the primary state:
import { useState, useEffect } from 'react';
function SimulationPanel() {
// Simulation configuration
const [config, setConfig] = useState({
num_tellers: 3,
arrival_config: {
arrival_rate: 1.0,
arrival_dist: 'exponential',
priority_weights: [0.1, 0.3, 0.6]
},
service_config: {
service_mean: 5.0,
service_dist: 'exponential',
service_stddev: 1.0
},
max_simulation_time: 28800,
max_queue_capacity: 100
});
// Current simulation state
const [simState, setSimState] = useState(null);
// Loading and error states
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// Polling simulation state
useEffect(() => {
if (!simState || simState.status !== 'RUNNING') return;
const interval = setInterval(async () => {
try {
const response = await fetch('http://localhost:5000/api/simulation/state');
if (!response.ok) throw new Error('Failed to fetch state');
const data = await response.json();
setSimState(data);
} catch (err) {
setError(err.message);
}
}, 1000); // Poll every 1 second
return () => clearInterval(interval);
}, [simState?.status]);
// ... render
}
State Flow Patterns
Lifting State Up
Configuration state is lifted toSimulationPanel and passed down:
function SimulationPanel() {
const [config, setConfig] = useState(defaultConfig);
return (
<>
<ConfigForm config={config} onConfigChange={setConfig} />
<SimulationControls config={config} onStart={handleStart} />
</>
);
}
function ConfigForm({ config, onConfigChange }) {
const handleChange = (field, value) => {
onConfigChange({ ...config, [field]: value });
};
// ... render
}
Derived State
Some state is computed from simState:function SimulationPanel() {
const [simState, setSimState] = useState(null);
// Derived: queue length
const queueLength = simState?.waiting_queue?.length || 0;
// Derived: number of busy tellers
const busyTellers = simState?.tellers
? Object.values(simState.tellers).filter(t => t.status === 'BUSY').length
: 0;
// Derived: system utilization
const utilization = simState?.tellers
? busyTellers / Object.keys(simState.tellers).length
: 0;
return (
<>
<div>Queue: {queueLength} customers</div>
<div>Utilization: {(utilization * 100).toFixed(1)}%</div>
</>
);
}
API Communication
Starting a Simulation
const handleStart = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('http://localhost:5000/api/simulation/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(config)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setSimState(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
Pausing/Stopping
const handlePause = async () => {
try {
await fetch('http://localhost:5000/api/simulation/pause', {
method: 'POST'
});
// Update local state optimistically
setSimState(prev => ({ ...prev, status: 'PAUSED' }));
} catch (err) {
setError(err.message);
}
};
const handleStop = async () => {
try {
await fetch('http://localhost:5000/api/simulation/stop', {
method: 'POST'
});
setSimState(prev => ({ ...prev, status: 'FINISHED' }));
} catch (err) {
setError(err.message);
}
};
Polling Strategy
Simulation State Polling
useEffect(() => {
// Only poll when simulation is running
if (simState?.status !== 'RUNNING') return;
const interval = setInterval(async () => {
const response = await fetch('/api/simulation/state');
const data = await response.json();
setSimState(data);
}, 1000); // 1 second interval
return () => clearInterval(interval);
}, [simState?.status]);
Metrics Polling
useEffect(() => {
const interval = setInterval(async () => {
const response = await fetch('/api/metrics/report');
const data = await response.json();
setMetrics(data);
}, 2000); // 2 second interval (less frequent)
return () => clearInterval(interval);
}, []);
Polling intervals are different: simulation state updates every 1s, metrics every 2s to reduce load.
Custom Hooks
useSimulation
Encapsulates simulation state logic:function useSimulation() {
const [simState, setSimState] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const startSimulation = async (config) => {
setLoading(true);
try {
const response = await fetch('/api/simulation/start', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
const data = await response.json();
setSimState(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const pauseSimulation = async () => {
await fetch('/api/simulation/pause', { method: 'POST' });
setSimState(prev => ({ ...prev, status: 'PAUSED' }));
};
const stopSimulation = async () => {
await fetch('/api/simulation/stop', { method: 'POST' });
setSimState(prev => ({ ...prev, status: 'FINISHED' }));
};
useEffect(() => {
if (simState?.status !== 'RUNNING') return;
const interval = setInterval(async () => {
const response = await fetch('/api/simulation/state');
const data = await response.json();
setSimState(data);
}, 1000);
return () => clearInterval(interval);
}, [simState?.status]);
return {
simState,
loading,
error,
startSimulation,
pauseSimulation,
stopSimulation
};
}
function SimulationPanel() {
const {
simState,
loading,
error,
startSimulation,
pauseSimulation,
stopSimulation
} = useSimulation();
// ... render
}
useMetrics
function useMetrics(simId) {
const [metrics, setMetrics] = useState(null);
useEffect(() => {
if (!simId) return;
const interval = setInterval(async () => {
const response = await fetch('/api/metrics/report');
const data = await response.json();
setMetrics(data);
}, 2000);
return () => clearInterval(interval);
}, [simId]);
return metrics;
}
Error Handling
Error State Pattern
function SimulationPanel() {
const [error, setError] = useState(null);
const fetchData = async () => {
setError(null);
try {
const response = await fetch('/api/simulation/state');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
setSimState(data);
} catch (err) {
setError(err.message);
}
};
return (
<>
{error && (
<div className="error-banner bg-red-100 text-red-800 p-4 rounded">
Error: {error}
</div>
)}
{/* ... rest of UI */}
</>
);
}
Loading States
function SimulationPanel() {
const [loading, setLoading] = useState(false);
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="spinner">Loading...</div>
</div>
);
}
// ... normal render
}
Optimistic Updates
Update UI immediately, sync with backend later:const handlePause = async () => {
// Optimistic update
setSimState(prev => ({ ...prev, status: 'PAUSED' }));
try {
await fetch('/api/simulation/pause', { method: 'POST' });
} catch (err) {
// Revert on error
setSimState(prev => ({ ...prev, status: 'RUNNING' }));
setError('Failed to pause simulation');
}
};
State Persistence
LocalStorage for Config
function usePersistedConfig() {
const [config, setConfig] = useState(() => {
const saved = localStorage.getItem('sim_config');
return saved ? JSON.parse(saved) : defaultConfig;
});
useEffect(() => {
localStorage.setItem('sim_config', JSON.stringify(config));
}, [config]);
return [config, setConfig];
}
Performance Optimization
Memoization
import { useMemo } from 'react';
function MetricsDisplay({ metrics }) {
const avgWaitTime = useMemo(() => {
if (!metrics?.wait_times) return 0;
const sum = metrics.wait_times.reduce((acc, wt) => acc + wt.wait_time, 0);
return sum / metrics.wait_times.length;
}, [metrics?.wait_times]);
return <div>Avg Wait: {avgWaitTime.toFixed(2)}s</div>;
}
Callback Memoization
import { useCallback } from 'react';
function ConfigForm({ onConfigChange }) {
const handleArrivalRateChange = useCallback((value) => {
onConfigChange(prev => ({
...prev,
arrival_config: { ...prev.arrival_config, arrival_rate: value }
}));
}, [onConfigChange]);
return <Slider onChange={handleArrivalRateChange} />;
}
State Debugging
React DevTools
View component state in browser:function SimulationPanel() {
const [simState, setSimState] = useState(null);
// Expose state to window for debugging
useEffect(() => {
window.DEBUG_simState = simState;
}, [simState]);
// ...
}
window.DEBUG_simState
State Logging
useEffect(() => {
console.log('SimState updated:', simState);
}, [simState]);
Next Steps
Component Structure
Learn about component organization
Visualization
How charts are rendered and updated
API Integration
Complete API reference
Running Simulations
User guide for the simulation interface