-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Improve performance of context components re-rendering #3066
base: dev
Are you sure you want to change the base?
Conversation
The test_redraw shows failure with 5 redraw instead of 2. There is two cases of additional redraw that need to be fixed in this pr:
|
{Array.isArray(layout) ? ( | ||
layout.map((c, i) => | ||
isSimpleComponent(c) ? ( | ||
c | ||
) : ( | ||
<TreeContainer | ||
<DashWrapper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, new component - is this going to break any legacy code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it shouldn't as this is an internal component.
@@ -1,4 +1,4 @@ | |||
type Config = { | |||
export type DashConfig = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for the name change - why does it have to be exported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is used in the new DashWrapper.tsx.
@@ -26,6 +26,18 @@ export const apiRequests = [ | |||
'loginRequest' | |||
]; | |||
|
|||
function callbackNum(state = 0, action) { | |||
// With the refactor of TreeContainer to DashWrapper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you for the explanation
@@ -0,0 +1,425 @@ | |||
import React, {useMemo, useCallback} from 'react'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'm going to trust you on this - I don't know enough of Dash and TypeScript to review something this large. @emilykl can you please have a look? (who else might be a good reviewer?)
I think for the additional redraw might be coming from the new path, before it was |
Hi. Are there any updates on this? |
I ran the dmc-docs using this branch, and it's much faster! Thanks so much for doing this PR. This will make large apps using DMC perform much better. On the 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)
app.layout = dmc.MantineProvider(
dmc.Checkbox(
label="Custom checked icon",
checked=True,
icon=DashIconify(icon="ion:bag-check-sharp"),
size="lg",
)
)
if __name__ == "__main__":
app.run(debug=True)
Here's one of the error messages (There are a few more as well)
UpdateIt's not just the DashIconify library. It's not possible to pass any components to the icon prop: import dash_mantine_components as dmc
from dash import Dash, _dash_renderer, html
_dash_renderer._set_react_version("18.2.0")
FONT_AWESOME = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
app = Dash(external_stylesheets=[FONT_AWESOME])
app.layout = dmc.MantineProvider([
html.I(className="fa-solid fa-bag-shopping fa-3x"),
dmc.Checkbox(
label="Custom checked icon",
checked=True,
icon=html.I(className="fa-solid fa-bag-shopping fa-3x"),
size="lg",
)
])
if __name__ == "__main__":
app.run(debug=True) |
@AnnMarieW I removed the |
@T4rk1n Thanks for your speedy response. I updated to use Update: Actually it doesn't work 😢 It doesn't throw errors, but it doesn't render correctly 🤔 |
There's one other user of |
I removed the The proper way to handle this kind of pattern (send data up/down stream the component tree) in react is with context components, which this PR fixes their performance. |
Thanks for the suggestion of using context instead. We are using the You make a good point here:
Given that this PR substantially increases performance, would you consider keeping the |
Yes, I'll put it back since it's used everywhere. There could be an alternative |
This would be awesome:
❤️ ❤️ |
Not sure I can add back those props in the way it was, there was another component in between the TreeContainer and the library component that is not longer the case. Might need to refactor the new Wrapper to have a middle component but that changes the order of renders and is not as optimal as a single component since it add overhead. |
And I think now with this to access the child props the new path would be just without a level and the |
Actually, the props are not there anymore in the wrapper, they are now gotten from the selector, so the old hack cannot work anymore with this solution. |
Is there a "new hack" that might be easier than using context? |
A Context solution might only be valid in some cases, for the mantine checkbox case, it want to add extra props to the component. I see a couple solution for those cases:
For accessing the props (dcc.Tabs cases), a new |
@AnnMarieW I added all three solutions from the last post.
const iconFunc = ({ indeterminate, ...others }) => {
const selected: any = indeterminate ? indeterminateIcon : icon;
return React.cloneElement(selected, {extras: others});
};
Let me know if that work. |
Thanks for the update @T4rk1n I'll try to figure out how to use these new features. Will it handle this pattern too? https://github.com/snehilvj/dash-mantine-components/blob/83a1cc12e5e6b210b7a1e27c7a83068abe2830c5/src/ts/components/core/timeline/Timeline.tsx#L46 |
For that one can use |
Do you know how to get the component type as used here in in this Stepper component
|
Hmm... would it be possible to change it from |
I changed |
It looks like Here's the first example from the docs - loading spinners don't show up from dash import Dash, dcc, html, Input, Output, callback
import time
app = Dash()
app.layout = html.Div([
html.H3("Edit text input to see loading state"),
html.Div("Input triggers local spinner"),
dcc.Input(id="loading-input-1"),
dcc.Loading(
id="loading-1",
type="default",
children=html.Div(id="loading-output-1")
),
html.Div([
html.Div('Input triggers nested spinner'),
dcc.Input(id="loading-input-2"),
dcc.Loading(
id="loading-2",
children=[html.Div([html.Div(id="loading-output-2")])],
type="circle",
)
]),
])
@callback(Output("loading-output-1", "children"), Input("loading-input-1", "value"))
def input_triggers_spinner(value):
time.sleep(1)
return value
@callback(Output("loading-output-2", "children"), Input("loading-input-2", "value"))
def input_triggers_nested(value):
time.sleep(1)
return value
if __name__ == "__main__":
app.run(debug=False)
|
@T4rk1n In some cases, it is desireable (necessary?) to render other Dash components beyond what is passed to the
It seems that the proposed As a last resort, I guess I could re-write the |
Yes, the loading state stuff incurred two extra renders so I had merged the loading_state selector with the props but the equality function still need to be adapted or something, working on that. |
I don't think this is a good pattern, the components are already hydrated, they only need to be mounted for the rendering. Can now add props The |
I guess we could add a I guess this is a limitation of the |
In DMC PR # 458 I've used the new features you added to eliminate all the DMC is using Emil's Is it OK to continue to use |
@AnnMarieW Yea that is just for the location component, it shouldn't have been prefixed with |
@T4rk1n |
Yes, if it's really needed, there will be |
Could you show an example of how to update the dcc.Graph component? It would be good to still enabling styling a component that's loading via CSS as described on at the end of this page: https://dash.plotly.com/loading-states |
@AnnMarieW For class component we can replace the div with import React from 'react';
import PropTypes from 'prop-types';
export default function LoadingDiv({children, loading_state, ...props}) {
let loading;
if (window.dash_component_api) {
const ctx = window.dash_component_api.useDashContext();
loading = ctx.useLoading();
} else {
loading = loading_state?.is_loading;
}
return (
<div {...props} data-dash-is-loading={loading || undefined}>
{children}
</div>
);
} This would also be backward compatible with the old loading_state. |
If the component accept |
Fix #3057
Gif showing only the clicked button is re-rendered: