Using widgets with React
In this tutorial, you'll learn how to easily integrate widgets into React, using the WatchList widget as an example. Let's create a simple widget component.
Simple widget
Let's create a component of watch list widget as basic example:
import { newWatchListWidget } from '@dx-display/widgets-watch-list'
const dataProviders = {
  ipfPath: '/your-path/ipf',
  ipfAuthHeader: 'your-header',
  feedPath: 'wss://your-path/feed',
  feedAuthHeader: 'your-header',
  fundamentalsPath: '/api/fundamentals',
  fundamentalsAuthHeader: 'your-header',
  schedulePath: '/api/schedule',
}
export function WatchListWidget() {
  const [widgetRootRef, setWidgetRootRef] = useState(null)
  const [widget, setWidget] = useState(null)
  useEffect(() => {
    if (widgetRootRef !== null) {
      // Here you can enable or disable different widget features
      const initialConfig = { dataExportEnabled: true }
      // Initial state of you widget
      const initialState = {
        symbols: [],
        selectedSymbol: undefined,
        columns: ['symbol', 'lastPrice'],
        columnSizes: {},
        filters: {},
        sort: undefined,
      }
      const widget = newWatchListWidget({
        element: widgetRootRef,
        providers: dataProviders,
        config: initialConfig,
        state: initialState,
      })
      setWidget(widget)
      return () => {
        widget.unmount()
      }
    }
  }, [widgetRootRef])
  return <div style={{ height: 800, width: 600 }} ref={setWidgetRootRef} />
}
And now we can easily use this component in any place of our app just like that:
import { WatchListWidget } from '../path/to/our/widget'
export function App() {
  return <WatchListWidget />
}
Connecting widgets
Using standalone widget is a possible way to use library, but let's make something more complex and create a connection between two widgets.
Firstly, we need to establish a shareable state. As an example, we can connect widgets by symbol. In this case, our app will appear as follows:
export function App() {
  const [symbol, setSymbol] = useState('AAPL')
  return (
    <>
      <WatchListWidget onSymbolChange={setSymbol} />
    </>
  )
}
We provide some state on the top level of our application. Let's now adjust our simple component to facilitate the passing of widget state.
import { newWatchListWidget } from '@dx-display/widgets-watch-list'
import { newTimeAndSalesWidget } from '@dx-display/widgets-time-and-sales'
export function WatchListWidget(props) {
  const { onSymbolChange } = props
  const [widgetRootRef, setWidgetRootRef] = useState(null)
  const [widget, setWidget] = useState(null)
  useEffect(() => {
    if (widgetRootRef !== null) {
      const initialConfig = { dataExportEnabled: true }
      const initialState = {
        symbols: [],
        selectedSymbol: undefined,
        columns: ['symbol', 'lastPrice'],
        columnSizes: {},
        filters: {},
        sort: undefined,
      }
      const widget = newWatchListWidget({
        element: widgetRootRef,
        providers: dataProviders,
        config: initialConfig,
        state: initialState,
      })
      setWidget(widget)
      return () => {
        widget.unmount()
      }
    }
  }, [widgetRootRef])
  useEffect(() => {
    if (widget !== null) {
      widget.addStateListener('selectedSymbol', onSymbolChange)
      return () => {
        widget.removeStateListener('selectedSymbol', onSymbolChange)
      }
    }
  }, [widget, onSymbolChange])
  return <div style={{ height: 800, width: 600 }} ref={setWidgetRootRef} />
}
export function TimeAndSalesWidget(props) {
  const { symbol } = props
  const [widgetRootRef, setWidgetRootRef] = useState(null)
  const [widget, setWidget] = useState(null)
  useEffect(() => {
    if (widgetRootRef !== null) {
      const widget = newTimeAndSalesWidget({
        element: widgetRootRef,
        providers: dataProviders,
      })
      setWidget(widget)
      return () => {
        widget.unmount()
      }
    }
  }, [widgetRootRef])
  useEffect(() => {
    widget?.updateState({ symbol })
  }, [widget, symbol])
  return <div style={{ height: 800, width: 600 }} ref={setWidgetRootRef} />
}
Now we can provide this data to another component or widget the following way:
export function App() {
  const [symbol, setSymbol] = useState('AAPL')
  return (
    <>
      <WatchListWidget onSymbolChange={setSymbol} />
      <TimeAndSalesWidget symbol={symbol} />
      <AnyAnotherComponent symbol={symbol} />
    </>
  )
}
Universal widget hook factory
If you want to avoid code duplication and add widgets faster, you can add hook factory as in example bellow:
const createUseWidgetHook = (factory, providers) => (props) => {
  const { onStateChange, state, theme, config } = props
  const [widgetRootRef, setWidgetRootRef] = useState(null)
  const [widget, setWidget] = useState(null)
  useEffect(() => {
    if (widgetRootRef !== null) {
      const widget = factory({
        element: widgetRootRef,
        providers,
        config,
        state,
        theme,
      })
      setWidget(widget)
      return () => {
        widget.unmount()
      }
    }
  }, [widgetRootRef])
  useEffect(() => {
    widget?.setConfig(config)
  }, [widget, config])
  useEffect(() => {
    widget?.updateState(state)
  }, [widget, state])
  useEffect(() => {
    widget?.setTheme(theme)
  }, [widget, theme])
  useEffect(() => {
    if (widget !== null) {
      widget.addStateListener(onStateChange)
      return () => {
        widget.removeStateListener(onStateChange)
      }
    }
  }, [widget, onStateChange])
  return setWidgetRootRef
}
Now, we can proceed to create a widget as follows:
const useWatchList = createUseWidgetHook(newWatchListWidget, dataProviders)
function WatchListWidget() {
  const setRef = useWatchList({
    config: { dataExportEnabled: true },
    onStateChange: (state) => {
      /*...*/
    },
    state: {
      symbols: [],
      selectedSymbol,
      columns: ['symbol', 'lastPrice'],
      columnSizes: {},
      filters: {},
      sort: undefined,
    },
    theme: 'light',
  })
  return <div style={{ height: 800, width: 600 }} ref={setRef} />
}