How to operate the object in dcc.Store outside of a callback with Dash? (network event triggering)

Expectation: update the persistent storage and UI when receiving the network message.

# Update the persistent storage, the following is pseudo code. Not working. 

def onMessage(message)
#  messageList = dcc.Store(id = 'message-list', storage_type = 'local').data
#  messageList.append(message)
#  messageList.save()
...


# update the UI by data changing, working well.
@app.callback(
    Output('message-display', 'children'),
    Input('message-list', 'data')
)
def updateMessage(messageList):
...

# When it receives the message via websocket, it will call onMessage(), working well.

async with websockets.connect(URL) as websocket_connection:
...
  async def receive():
    result = await ws_connection.recv()
    if result is not None:
       onMessage(result.message)
...

I tried search the documentation and google for a few hours, but haven’t found a working solution for this case. I wonder if I missed something, appreciate any help.

You can use the websocket component to capture the message in a callback,

1 Like

Hi Emil,
Thanks for suggesting websocket component, its basic functionality works for simple test.
But in my case, it’s missing some feature, like extra_headers.

                # WebSocket(
                #     id='websocket-com',
                #     url=websocket_url
                # )
                CustomWebSocketComponent(
                    id='websocket-com',
                    url=websocket_url,
                    extra_headers=(("Authorization", api_key),),
                    ping_interval=5,
                    ping_timeout=20
                )

I am trying to extend the WebSocket component, but have not been successful. I wonder if there is a better approach.

My custom WebSocket component : custom_websocket.py

import dash_extensions as de
import dash
import dash_html_components as html
from dash.development.base_component import ComponentMeta
from dash.dependencies import Output
import websockets

class CustomWebSocket(de.WebSocket):
    def __init__(self, id=None, url=None, extra_headers=None, ping_interval=None, ping_timeout=None, **kwargs):
        super().__init__(id=id, url=url, **kwargs)
        self.extra_headers = extra_headers
        self.ping_interval = ping_interval
        self.ping_timeout = ping_timeout

    def _create_websocket(self):
        if not self.url:
            raise ValueError("WebSocket 'url' property must be provided.")

        async def handle_websocket_message():
            while True:
                try:
                    message = await self.websocket.recv()
                    self._trigger_callback(Output(self.id, 'message'), message)
                except websockets.exceptions.ConnectionClosed:
                    break

        async def open_websocket():
            self.websocket = await websockets.connect(
                self.url,
                extra_headers=self.extra_headers,
                ping_interval=self.ping_interval,
                ping_timeout=self.ping_timeout
            )
            await self.websocket.send('Connection established.')

            self._trigger_callback(Output(self.id, 'status'), 'connected')

            await handle_websocket_message()

            await self.websocket.close()
            self._trigger_callback(Output(self.id, 'status'), 'closed')

        return open_websocket

    def start(self):
        if not self.websocket:
            self.websocket = self._create_websocket()

    def stop(self):
        if self.websocket:
            self.websocket.close()
            self.websocket = None


# class CustomWebSocketComponent(dash.Component):
class CustomWebSocketComponent(html.Component):
    def __init__(self, id=None, url=None, extra_headers=None, ping_interval=None, ping_timeout=None, **kwargs):
        super().__init__(id=id, **kwargs)
        self.url = url
        self.extra_headers = extra_headers
        self.ping_interval = ping_interval
        self.ping_timeout = ping_timeout
        self.websocket = None

    def start(self):
        self.websocket.start()

    def stop(self):
        self.websocket.stop()

    def render(self):
        return html.Div(id=self.id)


CustomWebSocketComponentMeta = ComponentMeta(
    'CustomWebSocketComponent',
    (CustomWebSocketComponent,),
    {'__module__': 'custom_websocket', '__doc__': CustomWebSocketComponent.__doc__}
)

CustomWebSocketComponentMetaWrapper = dash.ensure_io_component(CustomWebSocketComponentMeta)

CustomWebSocketMeta = ComponentMeta(
    'CustomWebSocket',
    (CustomWebSocket,),
    {'__module__': 'custom_websocket', '__doc__': CustomWebSocket.__doc__}
)

CustomWebSocketMetaWrapper = dash.ensure_io_component(CustomWebSocketMeta)

Unfortunately, it is not possible to extend Dash components in that way. If you need to add functionality, you will instead have to fork the dash-extensions repository, add the functionality in the React layer, and rebuild the Python components. If you end up doing so, I would be happy to look at a PR :slight_smile:

Hello @Vigogear,

You could also possibly do this all client side, having a message to the client trigger an update on the dcc.Store, then pushing a button that’s hooked to sync the store:

app.clientside_callback(
"""function (n) {
    if (n) { return sessionStorage.getItem('yourStore') }
    return window.dash_clientside.no_update
    }""",
    Output('yourStore', 'data'), Input('syncButton','n_clicks')
)

Have the button hidden in the layout and then trigger a click on it when the client receives a message. This will sync your session or local storage with React. You cannot access the store that is held in memory, at least I havent found a way.

All this being said, I’d look forward to you building a version that can handle your needs via dash-extensions :slight_smile:

1 Like