Skip to content

State Management

TradeX Chart uses a centralized state management system with reactive updates.

State Architecture

const chartState = {
  data: {
    ohlcv: [],
    indicators: {},
    overlays: {}
  },
  viewport: {
    start: 0,
    end: 100,
    zoom: 1.0
  },
  ui: {
    crosshair: { x: 0, y: 0 },
    selection: null,
    theme: 'dark'
  },
  config: {
    candleWidth: 8,
    showVolume: true,
    showGrid: true
  }
}

State Store

Implementation

class StateStore {
  constructor(initialState = {}) {
    this.state = initialState
    this.listeners = new Map()
    this.history = []
    this.historyIndex = -1
  }

  getState(path) {
    if (!path) return this.state
    
    return path.split('.').reduce((obj, key) => obj?.[key], this.state)
  }

  setState(path, value) {
    const oldState = { ...this.state }
    
    if (typeof path === 'object') {
      this.state = { ...this.state, ...path }
    } else {
      this.setNestedValue(path, value)
    }
    
    this.saveHistory(oldState)
    this.notify(path, value)
  }

  setNestedValue(path, value) {
    const keys = path.split('.')
    const lastKey = keys.pop()
    
    const target = keys.reduce((obj, key) => {
      if (!obj[key]) obj[key] = {}
      return obj[key]
    }, this.state)
    
    target[lastKey] = value
  }

  subscribe(path, callback) {
    if (!this.listeners.has(path)) {
      this.listeners.set(path, [])
    }
    
    this.listeners.get(path).push(callback)
    
    return () => this.unsubscribe(path, callback)
  }

  unsubscribe(path, callback) {
    const callbacks = this.listeners.get(path)
    if (callbacks) {
      const index = callbacks.indexOf(callback)
      if (index > -1) callbacks.splice(index, 1)
    }
  }

  notify(path, value) {
    const callbacks = this.listeners.get(path) || []
    callbacks.forEach(cb => cb(value, this.state))
    
    // Notify wildcard listeners
    const wildcardCallbacks = this.listeners.get('*') || []
    wildcardCallbacks.forEach(cb => cb(path, value, this.state))
  }
}

Reactive Updates

Auto-Update Pattern

class ReactiveChart {
  constructor() {
    this.store = new StateStore()
    this.setupReactivity()
  }

  setupReactivity() {
    // React to data changes
    this.store.subscribe('data.ohlcv', (data) => {
      this.updateChart(data)
    })

    // React to viewport changes
    this.store.subscribe('viewport', (viewport) => {
      this.updateViewport(viewport)
    })

    // React to theme changes
    this.store.subscribe('ui.theme', (theme) => {
      this.applyTheme(theme)
    })
  }

  setData(data) {
    this.store.setState('data.ohlcv', data)
  }

  zoom(level) {
    this.store.setState('viewport.zoom', level)
  }
}

State Persistence

Save/Restore State

class StatePersistence {
  saveState(store, key = 'chart_state') {
    const state = store.getState()
    const serialized = JSON.stringify(state)
    
    try {
      localStorage.setItem(key, serialized)
      return true
    } catch (error) {
      console.error('Failed to save state:', error)
      return false
    }
  }

  loadState(key = 'chart_state') {
    try {
      const serialized = localStorage.getItem(key)
      if (!serialized) return null
      
      return JSON.parse(serialized)
    } catch (error) {
      console.error('Failed to load state:', error)
      return null
    }
  }

  clearState(key = 'chart_state') {
    localStorage.removeItem(key)
  }
}

// Usage
const persistence = new StatePersistence()

// Save
persistence.saveState(chart.store)

// Restore
const savedState = persistence.loadState()
if (savedState) {
  chart.store.setState(savedState)
}

Time Travel Debugging

Undo/Redo

class TimeTravel {
  constructor(store) {
    this.store = store
    this.history = []
    this.currentIndex = -1
    this.maxHistory = 50
  }

  saveSnapshot() {
    const state = JSON.parse(JSON.stringify(this.store.getState()))
    
    // Remove future states if we're not at the end
    if (this.currentIndex < this.history.length - 1) {
      this.history = this.history.slice(0, this.currentIndex + 1)
    }
    
    this.history.push(state)
    
    // Limit history size
    if (this.history.length > this.maxHistory) {
      this.history.shift()
    } else {
      this.currentIndex++
    }
  }

  undo() {
    if (this.currentIndex > 0) {
      this.currentIndex--
      this.store.setState(this.history[this.currentIndex])
      return true
    }
    return false
  }

  redo() {
    if (this.currentIndex < this.history.length - 1) {
      this.currentIndex++
      this.store.setState(this.history[this.currentIndex])
      return true
    }
    return false
  }

  canUndo() {
    return this.currentIndex > 0
  }

  canRedo() {
    return this.currentIndex < this.history.length - 1
  }
}

State Middleware

Logger Middleware

class LoggerMiddleware {
  constructor(store) {
    this.store = store
    this.setupLogging()
  }

  setupLogging() {
    const originalSetState = this.store.setState.bind(this.store)
    
    this.store.setState = (path, value) => {
      console.group(`State Update: ${path}`)
      console.log('Previous:', this.store.getState(path))
      console.log('Next:', value)
      console.groupEnd()
      
      return originalSetState(path, value)
    }
  }
}