Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates for dash 3 #499

Closed
wants to merge 28 commits into from
Closed

Conversation

AnnMarieW
Copy link
Collaborator

@AnnMarieW AnnMarieW commented Jan 31, 2025

Closes #453

This PR replaces #458 which only handled changes for Dash 3. This PR includes updates for Dash 3 that are also compatible with Dash 2.

  • handle removal of loading_state
  • handle deprecation of defaultProps, including new way of setting persistence props
  • handle removal of _dashprivate_loadingState used in the Skeleton component.
  • update Checkbox component which has an icon component prop that needs to be rerender with additional props.
  • update all components that used _dashprivate_layout: Checkbox, Popover, Menu, Hovercard, Timeline, Stepper
  • Check PR # 458 to make sure all changes are applied here too.
  • In package.json, pin "dash-extensions-js": "0.0.8" . Before upgrading, check for Dash 2 compatibility. This package is required for the renderDashComponent function, which may be affected by updates in future Dash versions.
  • Require dash >= 3.0 for install -- temporary change to run tests using dash 3. Reverted for pre-release.
  • Set up tests to run for dash 2 and dash 3

Note - do not use the new typing support because it does not handle Mantine typing correctly.

ignore this comment in the build and add notes to releasing.md to ensure it's not added in the future

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Missing proptypes.js in dash_mantine_components/__init__.py !
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Add the following to dash_mantine_components/__init__.py to enable
runtime prop types validation with tsx components:

@AnnMarieW
Copy link
Collaborator Author

AnnMarieW commented Jan 31, 2025

handling removal of loading_state, deprecation of defaultProps and new method for persistence props

Sample app to check:

  • data-dash-is-loading is in the dom while the component is loading.
  • persistence props works with both dash 2 and 3 without errors in the console.

Try this app with both dash 3 and dash 2

import time
from dash import Dash, _dash_renderer, Input, Output,, html
import dash_mantine_components as dmc

# remove this line to use react 18.3 with dash 3.0
_dash_renderer._set_react_version("18.2.0")


app = Dash()

component =  [
    dmc.NumberInput(id="number", persistence=True),
    dmc.Button("Go", id="btn")
]


app.layout = dmc.MantineProvider(
    component
)

@app.callback(
    Output("number", "value"),
    Input("btn", "n_clicks")
)
def update(n):
    time.sleep(3)
    return n

if __name__ == "__main__":
    app.run(debug=True, port=8050)



@AnnMarieW
Copy link
Collaborator Author

AnnMarieW commented Jan 31, 2025

handling the removal of dashprivate_layout

The Stepper component uses the child type to render a different component in the final step. It uses the child props to render different icons, descriptions etc for each step.

It uses:

  • dashprivate_layout for dash 2
  • dash_components_api for dash 3

Sample App:

from dash_iconify import DashIconify
import dash_mantine_components as dmc
from dash import Dash, _dash_renderer, Input, Output, State, callback, ctx
_dash_renderer._set_react_version("18.2.0")

app = Dash(external_stylesheets=dmc.styles.ALL)

min_step = 0
max_step = 3
active = 1


def get_icon(icon):
    return DashIconify(icon=icon, height=20)


component = dmc.Container(
    [
        dmc.Stepper(
            id="stepper-custom-icons",
            active=active,
            children=[
                dmc.StepperStep(
                    label="First step",
                    description="Create an account",
                    icon=get_icon(icon="material-symbols:account-circle"),
                    progressIcon=get_icon(icon="material-symbols:account-circle"),
                    completedIcon=get_icon(icon="mdi:account-check"),
                    children=[
                        dmc.Text("Step 1 content: Create an account", ta="center")
                    ],
                ),
                dmc.StepperStep(
                    label="Second step",
                    description="Verify email",
                    icon=get_icon(icon="ic:outline-email"),
                    progressIcon=get_icon(icon="ic:outline-email"),
                    completedIcon=get_icon(
                        icon="material-symbols:mark-email-read-rounded"
                    ),
                    children=[dmc.Text("Step 2 content: Verify email", ta="center")],
                ),
                dmc.StepperStep(
                    label="Final step",
                    description="Get full access",
                    icon=get_icon(icon="material-symbols:lock-outline"),
                    progressIcon=get_icon(icon="material-symbols:lock-outline"),
                    completedIcon=get_icon(icon="material-symbols:lock-open-outline"),
                    children=[dmc.Text("Step 3 content: Get full access", ta="center")],
                ),
                dmc.StepperCompleted(
                    children=[
                        dmc.Text(
                            "Completed, click back button to get to previous step",
                            ta="center",
                        )
                    ]
                ),
            ],
        ),
        dmc.Group(
            justify="center",
            mt="xl",
            children=[
                dmc.Button("Back", id="back-custom-icons", variant="default"),
                dmc.Button("Next step", id="next-custom-icons"),
            ],
        ),
    ]
)


@callback(
    Output("stepper-custom-icons", "active"),
    Input("back-custom-icons", "n_clicks"),
    Input("next-custom-icons", "n_clicks"),
    State("stepper-custom-icons", "active"),
    prevent_initial_call=True,
)
def update_with_icons(back, next_, current):
    button_id = ctx.triggered_id
    step = current if current is not None else active
    if button_id == "back-custom-icons":
        step = step - 1 if step > min_step else step
    else:
        step = step + 1 if step < max_step else step
    return step



app.layout = dmc.MantineProvider(
    component
)

if __name__ == "__main__":
    app.run(debug=True, port=8053)

@AnnMarieW
Copy link
Collaborator Author

Handling removal of _dashprivate_isLoadingComponent

This is used for components like Skeleton which is like dcc.Loading. A loading overlay is displayed based on the loading state of it's children.

For use in Dash 2, it sets:
Skeleton._dashprivate_isLoadingComponent = true;

For Dash 3, it checks the loading state of each child using:
(window as any).dash_component_api.useDashContext.useLoading({ rawPath: child.props?.componentPath })

Here is a sample app:

import time

import dash_mantine_components as dmc
from dash import Dash, _dash_renderer, Input, Output,  html, callback
_dash_renderer._set_react_version("18.2.0")

app = Dash()

component = html.Div(
    [
        dmc.Skeleton(
            visible=False,
            children=[html.Div(id="div1", children=html.Div("default figure")), html.Div( id="div2")],
            mb=10,
        ),
        dmc.Button("Click Me!", id="button"),
    ]
)


app.layout = dmc.MantineProvider(component)

@callback(
    Output("div1", "children"),
    Input("button", "n_clicks"),
    prevent_initial_call=True
)
def update_graph(n):
    time.sleep(2)
   # return html.Div(f"figure updated {n} times")
    return f"figure updated {n} times"


@callback(
    Output("div2", "children"),
    Input("button", "n_clicks"),
    prevent_initial_call=True
)
def update_div(n):
    time.sleep(5)
    return f"div updated {n} times"

if __name__ == "__main__":
    app.run(debug=True, port=8050)

@emilhe
Copy link
Contributor

emilhe commented Feb 1, 2025

@AnnMarieW Great work! Did you find an alternative solution to using the (old) renderDashComponent function?

@AnnMarieW
Copy link
Collaborator Author

AnnMarieW commented Feb 1, 2025

Hi @emilhe

I chatted with Philippe about that and he mentioned he could add a render(component, path) to dash_component_api quite easily, but wanted to wait for another release. Maybe we should open an issue for it in Dash.

He said the only thing he didn't like was that renderDashComponent is not connected to the store.

It's still used in 4 components in this repo.

@emilhe
Copy link
Contributor

emilhe commented Feb 2, 2025

That sounds like a great idea. It would be neat with a 'native' solution 😊

Does that mean that DMC cannot be updated before this fix is available? Or does the old hack still work in Dash 3? (:

@AnnMarieW
Copy link
Collaborator Author

@emilhe

does the old hack still work in Dash 3?

Yes, it works perfectly in both Dash 2 and Dash 3 🎉

I would call it a "brilliant solution" rather than an "old hack" though 🤓

Thanks for making this available!

@emilhe
Copy link
Contributor

emilhe commented Feb 2, 2025

That's great! Thanks for the update. I guess there are no known Dash 3 imcompabilites left then (after this PR)? :)

@AnnMarieW
Copy link
Collaborator Author

no known Dash 3 imcompabilites left then (after this PR)?

If you are referring to just DMC, I think I've found them all. The update just needs to be applied to all the components. I'm planning on doing that after the next release.

Also, I would welcome any input on what I have so far. Is there a better way to do this?

@AnnMarieW
Copy link
Collaborator Author

Here is the sample app for the Checkbox component. It renders an icon with extra props and needs to be handled differently in dash 2 and dash 2

import dash_mantine_components as dmc
from dash import Dash, _dash_renderer
_dash_renderer._set_react_version("18.2.0")
from dash_iconify import DashIconify

app = Dash(external_stylesheets=dmc.styles.ALL)

component = dmc.Stack([
    dmc.Checkbox(
        label="Custom checked icon",
        checked=True,
        icon=DashIconify(icon="ion:bag-check-sharp"),
        size="lg",
        p=0,
        persistence=True,
        id="checkbox"
    ),
    dmc.Checkbox(
        label="Custom intermediate icons",
        indeterminate=True,
        indeterminateIcon=DashIconify(icon="material-symbols:indeterminate-question-box"),
        size="lg"
    )
])

app.layout = dmc.MantineProvider(
    component
)

if __name__ == "__main__":
    app.run(debug=True)

@AnnMarieW AnnMarieW changed the base branch from master to dev February 3, 2025 22:17
@AnnMarieW AnnMarieW changed the base branch from dev to master February 3, 2025 22:17
const childArray = React.Children.toArray(children);

// Get loading states for all children
const loadingStates = childArray.map((child: any) =>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if you use some here rather than map you can benefit from short-circuit evaluation. I.e. you're return true the first time you encounter a loading child rather than calling useLoading on all children then calling some on the resultant array.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Updated 🙂

@AnnMarieW AnnMarieW marked this pull request as ready for review February 5, 2025 17:04
@AnnMarieW AnnMarieW mentioned this pull request Feb 6, 2025
9 tasks
@AnnMarieW
Copy link
Collaborator Author

Closed in favor of #506

@AnnMarieW AnnMarieW closed this Feb 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update for Dash 3.0
3 participants