Dash-leaflet - Problem updating hideout in a clientside callback

Hi, hoping someone (@Emil ?) can help with this. I’ve been trying to update the hideout for a geojson layer in clientside callback, but can’t get it to work. The code below is self-contained example and demonstrates my problem. The basic idea is to switch markers on and off based on a checkbox. If the variable ‘working_version’ is set to True, the callback creates a new hideout object, and everything works fine. If it’s set False, the callback updates the existing hideout object and it doesn’t seem to work.

(Updating the existing hideout object also works fine if a server side callback is used)

Is there any reason this should happen, or am I missing something obvious?
dash-leaflet 1.0.15, dash 2.17.1

from dash import Dash, Input, Output, State, dcc, html
from dash_extensions.javascript import assign
import dash_leaflet as dl
import dash_leaflet.express as dlx

working_version = True

draw_marker = assign(
    """
        function(feature, latlng, context){
        const {show_labels} = context.hideout;
        if(show_labels) {
            return new L.marker(latlng);
        } else {
            return;
        }
    }"""
)

cities = [dict(name="Aalborg", lat=57.0268172, lon=9.837735),
          dict(name="Aarhus", lat=56.1780842, lon=10.1119354),
          dict(name="Copenhagen", lat=55.6712474, lon=12.5237848)]
geojson = dlx.dicts_to_geojson([{**c, **dict(tooltip=c['name'])} for c in cities])

app = Dash(__name__) 
app.layout = [
        html.H1(f"Working version?: {working_version}"),
        dcc.Checklist(['Show markers'],[], id='check-show'),
        dl.Map(
            children=[
                dl.TileLayer(),
                dl.GeoJSON(
                    data=geojson, id="geojson",
                    pointToLayer=draw_marker, hideout=dict(show_labels=False)
                )
            ], 
            style={'height': '50vh'}, center=[56, 10], zoom=6
        ),
    ]

if working_version:
    app.clientside_callback(
        """
        function(values) {
            hideout = {show_labels: values.length > 0}
            console.log(hideout)
            return hideout
        }    
        """,
        Output("geojson","hideout"),
        Input("check-show","value"),
        prevent_initial_call=True
    )
else:  # the version that doesn't work
    app.clientside_callback(
        """
        function(values, hideout) {
            hideout.show_labels = (values.length > 0)
            console.log(hideout)
            return hideout;
        }    
        """,
        Output("geojson","hideout"),
        Input("check-show","value"),
        State("geojson","hideout"),
        prevent_initial_call=True
    )

if __name__ == "__main__":
    app.run()
1 Like

At the top of my head, i think it might be related to the React rendering process. React only performs an update on property changes. If the equality check of the property is performed on the memory pointer, updating the underlying object won’t work, as the pointer itself doesn’t change. Assigning a new object on the other hand changes the pointer, and hence triggers an update.

1 Like

Thank you, that makes sense. So if I want to update properties in the hideout, I need something like this, which seems to work perfectly:

app.clientside_callback(
    """
    function(values, hideout) {
        hdclone = structuredClone(hideout);
        hdclone.show_labels = (values.length > 0)
        return hdclone;
    }    
    """,
    Output("geojson","hideout"),
    Input("check-show","value"),
    State("geojson","hideout"),
    prevent_initial_call=True
)
1 Like

Yes, I would expect that approach (doing a deep clone, could be using any library; or even just JSON dump/parse) to work robustly :+1: