Skip to content

Commit cea5aba

Browse files
setProps and rendered event ordering causing unsent callbacks (#1415)
1 parent 98e81fc commit cea5aba

File tree

14 files changed

+200
-12
lines changed

14 files changed

+200
-12
lines changed

@plotly/dash-test-components/babel.config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@ const presets = [
33
'@babel/preset-react'
44
];
55

6-
module.exports = { presets };
6+
const plugins = [
7+
'@babel/plugin-syntax-dynamic-import'
8+
];
9+
10+
module.exports = { presets, plugins };

@plotly/dash-test-components/package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

@plotly/dash-test-components/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@
2222
"devDependencies": {
2323
"@babel/cli": "^7.4.0",
2424
"@babel/core": "^7.4.0",
25+
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
2526
"@babel/preset-env": "^7.4.1",
2627
"@babel/preset-react": "^7.0.0",
28+
"@plotly/dash-component-plugins": "^1.2.0",
29+
"@plotly/webpack-dash-dynamic-import": "^1.1.5",
2730
"babel-loader": "^8.0.5",
2831
"npm-run-all": "^4.1.5",
2932
"react": "16.13.0",
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import PropTypes from 'prop-types';
2+
import React, { Suspense } from 'react';
3+
import { asyncDecorator } from '@plotly/dash-component-plugins';
4+
import asyncComponentLoader from './../fragments/AsyncComponentLoader';
5+
6+
const AsyncComponent = props => (<Suspense fallback={null}>
7+
<RealAsyncComponent {...props} />
8+
</Suspense>);
9+
10+
const RealAsyncComponent = asyncDecorator(AsyncComponent, asyncComponentLoader);
11+
12+
AsyncComponent.propTypes = {
13+
id: PropTypes.string,
14+
value: PropTypes.string
15+
};
16+
17+
AsyncComponent.defaultProps = {};
18+
19+
export default AsyncComponent;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import PropTypes from 'prop-types';
2+
import React, { Fragment } from 'react';
3+
4+
const CollapseComponent = props => (<Fragment>
5+
{display ? props.children : null}
6+
</Fragment>);
7+
8+
CollapseComponent.propTypes = {
9+
children: PropTypes.node,
10+
display: PropTypes.bool,
11+
id: PropTypes.string
12+
};
13+
14+
CollapseComponent.defaultProps = {
15+
display: false
16+
};
17+
18+
export default CollapseComponent;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import PropTypes from 'prop-types';
2+
import React from 'react';
3+
4+
const DelayedEventComponent = ({ id, n_clicks, setProps }) => (<button
5+
id={id}
6+
onClick={() => setTimeout(() => setProps({ n_clicks: n_clicks + 1 }), 20)}
7+
/>);
8+
9+
DelayedEventComponent.propTypes = {
10+
id: PropTypes.string,
11+
n_clicks: PropTypes.number
12+
};
13+
14+
DelayedEventComponent.defaultProps = {
15+
n_clicks: 0
16+
};
17+
18+
export default DelayedEventComponent;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import PropTypes from 'prop-types';
2+
import React, { Fragment } from 'react';
3+
4+
const FragmentComponent = props => (<Fragment>
5+
{props.children}
6+
</Fragment>);
7+
8+
FragmentComponent.propTypes = {
9+
children: PropTypes.node,
10+
id: PropTypes.string
11+
};
12+
13+
FragmentComponent.defaultProps = {};
14+
15+
export default FragmentComponent;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import PropTypes from 'prop-types';
2+
import React, { Fragment } from 'react';
3+
import { asyncDecorator } from '@plotly/dash-component-plugins';
4+
5+
/**
6+
* MyComponent description
7+
*/
8+
const AsyncComponent = ({ value }) => (<Fragment>
9+
{value}
10+
</Fragment>);
11+
12+
AsyncComponent.propTypes = {
13+
id: PropTypes.string,
14+
value: PropTypes.string
15+
};
16+
17+
AsyncComponent.defaultProps = {};
18+
19+
export default AsyncComponent;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default () => import('./AsyncComponent');
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1+
import AsyncComponent from './components/AsyncComponent';
2+
import CollapseComponent from './components/CollapseComponent';
3+
import DelayedEventComponent from './components/DelayedEventComponent';
4+
import FragmentComponent from './components/FragmentComponent';
15
import StyledComponent from './components/StyledComponent';
26
import MyPersistedComponent from './components/MyPersistedComponent';
37
import MyPersistedComponentNested from './components/MyPersistedComponentNested';
48

59

610
export {
7-
StyledComponent, MyPersistedComponent, MyPersistedComponentNested
11+
AsyncComponent,
12+
CollapseComponent,
13+
DelayedEventComponent,
14+
FragmentComponent,
15+
MyPersistedComponent,
16+
MyPersistedComponentNested,
17+
StyledComponent
818
};

@plotly/dash-test-components/webpack.config.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const path = require('path');
22
const webpack = require('webpack');
3+
const WebpackDashDynamicImport = require('@plotly/webpack-dash-dynamic-import');
34

45
const packagejson = require('./package.json');
56

@@ -14,6 +15,7 @@ module.exports = {
1415
},
1516
output: {
1617
path: path.resolve(__dirname, dashLibraryName),
18+
chunkFilename: '[name].js',
1719
filename: `${dashLibraryName}.js`,
1820
library: dashLibraryName,
1921
libraryTarget: 'window',
@@ -28,5 +30,23 @@ module.exports = {
2830
}
2931
}
3032
],
31-
}
33+
},
34+
optimization: {
35+
splitChunks: {
36+
chunks: 'async',
37+
name: true,
38+
cacheGroups: {
39+
async: {
40+
chunks: 'async',
41+
minSize: 0,
42+
name(module, chunks, cacheGroupKey) {
43+
return `${cacheGroupKey}-${chunks[0].name}`;
44+
}
45+
}
46+
}
47+
}
48+
},
49+
plugins: [
50+
new WebpackDashDynamicImport()
51+
]
3252
};

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
All notable changes to `dash` will be documented in this file.
33
This project adheres to [Semantic Versioning](https://semver.org/).
44

5+
## Unreleased
6+
### Fixed
7+
- [#1415](https://github.com/plotly/dash/pull/1415) Fix a regression with some layouts callbacks involving dcc.Tabs, not yet loaded dash_table.DataTable and dcc.Graph to not be called
8+
59
## [1.16.1] - 2020-09-16
610
### Changed
711
- [#1376](https://github.com/plotly/dash/pull/1376) Extends the `getTransform` logic in the renderer to handle `persistenceTransforms` for both nested and non-nested persisted props. This was used to to fix [dcc#700](https://github.com/plotly/dash-core-components/issues/700) in conjunction with [dcc#854](https://github.com/plotly/dash-core-components/pull/854) by using persistenceTransforms to strip the time part of the datetime so that datepickers can persist when defined in callbacks.

dash-renderer/src/TreeContainer.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,6 @@ class BaseTreeContainer extends Component {
138138
// for persistence
139139
recordUiEdit(_dashprivate_layout, newProps, _dashprivate_dispatch);
140140

141-
// Always update this component's props
142-
_dashprivate_dispatch(
143-
updateProps({
144-
props: changedProps,
145-
itempath: _dashprivate_path
146-
})
147-
);
148-
149141
// Only dispatch changes to Dash if a watched prop changed
150142
if (watchedKeys.length) {
151143
_dashprivate_dispatch(
@@ -155,6 +147,14 @@ class BaseTreeContainer extends Component {
155147
})
156148
);
157149
}
150+
151+
// Always update this component's props
152+
_dashprivate_dispatch(
153+
updateProps({
154+
props: changedProps,
155+
itempath: _dashprivate_path
156+
})
157+
);
158158
}
159159
}
160160

tests/integration/callbacks/test_basic_callback.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import json
22
from multiprocessing import Lock, Value
3-
43
import pytest
54

65
import dash_core_components as dcc
76
import dash_html_components as html
87
import dash_table
98
import dash
9+
from dash_test_components import (
10+
AsyncComponent,
11+
CollapseComponent,
12+
DelayedEventComponent,
13+
FragmentComponent,
14+
)
1015
from dash.dependencies import Input, Output, State
1116
from dash.exceptions import PreventUpdate
1217
from dash.testing import wait
@@ -404,3 +409,43 @@ def update_text(data):
404409
assert input_call_count.value == 2 + len("hello world")
405410

406411
assert not dash_duo.get_logs()
412+
413+
414+
def test_cbsc009_callback_using_unloaded_async_component_and_graph(dash_duo):
415+
app = dash.Dash(__name__)
416+
app.layout = FragmentComponent(
417+
[
418+
CollapseComponent([AsyncComponent(id="async", value="A")]),
419+
html.Button("n", id="n"),
420+
DelayedEventComponent(id="d"),
421+
html.Div("Output init", id="output"),
422+
]
423+
)
424+
425+
@app.callback(
426+
Output("output", "children"),
427+
Input("n", "n_clicks"),
428+
Input("d", "n_clicks"),
429+
Input("async", "value"),
430+
)
431+
def content(n, d, v):
432+
return json.dumps([n, d, v])
433+
434+
dash_duo.start_server(app)
435+
436+
wait.until(lambda: dash_duo.find_element("#output").text == '[null, null, "A"]', 3)
437+
dash_duo.wait_for_element("#d").click()
438+
439+
wait.until(
440+
lambda: dash_duo.find_element("#output").text == '[null, 1, "A"]', 3,
441+
)
442+
443+
dash_duo.wait_for_element("#n").click()
444+
wait.until(
445+
lambda: dash_duo.find_element("#output").text == '[1, 1, "A"]', 3,
446+
)
447+
448+
dash_duo.wait_for_element("#d").click()
449+
wait.until(
450+
lambda: dash_duo.find_element("#output").text == '[1, 2, "A"]', 3,
451+
)

0 commit comments

Comments
 (0)