diff --git a/@plotly/dash-generator-test-component-typescript/src/components/RequiredChildrenComponent.tsx b/@plotly/dash-generator-test-component-typescript/src/components/RequiredChildrenComponent.tsx
new file mode 100644
index 0000000000..5842303ace
--- /dev/null
+++ b/@plotly/dash-generator-test-component-typescript/src/components/RequiredChildrenComponent.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import { RequiredChildrenComponentProps } from "../props";
+
+
+const RequiredChildrenComponent = (props: RequiredChildrenComponentProps) => {
+ const {children} = props;
+ return (
+
+ {children}
+
+ )
+}
+
+export default RequiredChildrenComponent;
diff --git a/@plotly/dash-generator-test-component-typescript/src/index.ts b/@plotly/dash-generator-test-component-typescript/src/index.ts
index db623b462d..d4dacf00c3 100644
--- a/@plotly/dash-generator-test-component-typescript/src/index.ts
+++ b/@plotly/dash-generator-test-component-typescript/src/index.ts
@@ -6,6 +6,7 @@ import WrappedHTML from './components/WrappedHTML';
import FCComponent from './components/FCComponent';
import EmptyComponent from './components/EmptyComponent';
import MixedComponent from './components/MixedComponent';
+import RequiredChildrenComponent from './components/RequiredChildrenComponent';
export {
TypeScriptComponent,
@@ -16,4 +17,5 @@ export {
FCComponent,
EmptyComponent,
MixedComponent,
+ RequiredChildrenComponent,
};
diff --git a/@plotly/dash-generator-test-component-typescript/src/props.ts b/@plotly/dash-generator-test-component-typescript/src/props.ts
index 86d84af5f6..3912f9ddf1 100644
--- a/@plotly/dash-generator-test-component-typescript/src/props.ts
+++ b/@plotly/dash-generator-test-component-typescript/src/props.ts
@@ -48,3 +48,7 @@ export type WrappedHTMLProps = {
children?: React.ReactNode;
id?: string;
} & Pick, 'autoFocus'>
+
+export type RequiredChildrenComponentProps = {
+ children: React.ReactNode;
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 80f8cffca7..ad18b27287 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
### Fixed
+- [#2218](https://github.com/plotly/dash/pull/2218) Fix bug [#1348](https://github.com/plotly/dash/issues/1348) Validate children prop (required or not).
- [#2223](https://github.com/plotly/dash/pull/2223) Exclude hidden folders when building `dash.page_registry`.
- [#2182](https://github.com/plotly/dash/pull/2182) Fix [#2172](https://github.com/plotly/dash/issues/2172) Make it so that when using pages, if `suppress_callback_exceptions=True` the `validation_layout` is not set.
- [#2152](https://github.com/plotly/dash/pull/2152) Fix bug [#2128](https://github.com/plotly/dash/issues/2128) preventing rendering of multiple components inside a dictionary.
diff --git a/dash/development/_py_components_generation.py b/dash/development/_py_components_generation.py
index 48b8aac5ae..04981a69dd 100644
--- a/dash/development/_py_components_generation.py
+++ b/dash/development/_py_components_generation.py
@@ -1,7 +1,7 @@
from collections import OrderedDict
import copy
import os
-from textwrap import fill
+from textwrap import fill, dedent
from dash.development.base_component import _explicitize_args
from dash.exceptions import NonExistentEventException
@@ -65,11 +65,8 @@ def __init__(self, {default_argtext}):
_explicit_args = kwargs.pop('_explicit_args')
_locals = locals()
_locals.update(kwargs) # For wildcard attrs and excess named props
- args = {{k: _locals[k] for k in _explicit_args if k != 'children'}}
- for k in {required_props}:
- if k not in args:
- raise TypeError(
- 'Required argument `' + k + '` was not specified.')
+ args = {args}
+ {required_validation}
super({typename}, self).__init__({argtext})
'''
@@ -87,18 +84,40 @@ def __init__(self, {default_argtext}):
description=description,
prop_reorder_exceptions=prop_reorder_exceptions,
).replace("\r\n", "\n")
+ required_args = required_props(filtered_props)
+ is_children_required = 'children' in required_args
+ required_args = [arg for arg in required_args if arg != "children"]
prohibit_events(props)
# pylint: disable=unused-variable
prop_keys = list(props.keys())
- if "children" in props:
+ if "children" in props and "children" in list_of_valid_keys:
prop_keys.remove("children")
default_argtext = "children=None, "
+ args = "{k: _locals[k] for k in _explicit_args if k != 'children'}"
argtext = "children=children, **args"
else:
default_argtext = ""
+ args = "{k: _locals[k] for k in _explicit_args}"
argtext = "**args"
+
+ if len(required_args) == 0:
+ required_validation = ""
+ else:
+ required_validation = f"""
+ for k in {required_args}:
+ if k not in args:
+ raise TypeError(
+ 'Required argument `' + k + '` was not specified.')
+ """
+
+ if is_children_required:
+ required_validation += """
+ if 'children' not in _explicit_args:
+ raise TypeError('Required argument children was not specified.')
+ """
+
default_arglist = [
(
f"{p:s}=Component.REQUIRED"
@@ -121,20 +140,23 @@ def __init__(self, {default_argtext}):
)
default_argtext += ", ".join(default_arglist + ["**kwargs"])
- required_args = required_props(filtered_props)
nodes = collect_nodes({k: v for k, v in props.items() if k != "children"})
- return c.format(
- typename=typename,
- namespace=namespace,
- filtered_props=filtered_props,
- list_of_valid_wildcard_attr_prefixes=wildcard_prefixes,
- list_of_valid_keys=list_of_valid_keys,
- docstring=docstring,
- default_argtext=default_argtext,
- argtext=argtext,
- required_props=required_args,
- children_props=nodes,
- base_nodes=filter_base_nodes(nodes) + ["children"],
+
+ return dedent(
+ c.format(
+ typename=typename,
+ namespace=namespace,
+ filtered_props=filtered_props,
+ list_of_valid_wildcard_attr_prefixes=wildcard_prefixes,
+ list_of_valid_keys=list_of_valid_keys,
+ docstring=docstring,
+ default_argtext=default_argtext,
+ args=args,
+ argtext=argtext,
+ required_validation=required_validation,
+ children_props=nodes,
+ base_nodes=filter_base_nodes(nodes) + ["children"],
+ )
)
diff --git a/tests/integration/test_generation.py b/tests/integration/test_generation.py
index c5c5f738f9..25f5178e7e 100644
--- a/tests/integration/test_generation.py
+++ b/tests/integration/test_generation.py
@@ -9,6 +9,7 @@
TypeScriptComponent,
TypeScriptClassComponent,
StandardComponent,
+ RequiredChildrenComponent,
)
from dash_test_components import StyledComponent
from dash.html import Button, Div
@@ -99,3 +100,10 @@ def test_gene003_max_props():
with pytest.raises(TypeError):
MyNestedComponent(valuey="nor this")
+
+
+def test_gene004_required_children_prop():
+ with pytest.raises(TypeError):
+ RequiredChildrenComponent()
+
+ RequiredChildrenComponent(children='worked')
diff --git a/tests/unit/development/metadata_test.py b/tests/unit/development/metadata_test.py
index 42f8b3fb78..8f1dac0a37 100644
--- a/tests/unit/development/metadata_test.py
+++ b/tests/unit/development/metadata_test.py
@@ -100,8 +100,5 @@ def __init__(self, children=None, optionalArray=Component.UNDEFINED, optionalBoo
_locals = locals()
_locals.update(kwargs) # For wildcard attrs and excess named props
args = {k: _locals[k] for k in _explicit_args if k != 'children'}
- for k in []:
- if k not in args:
- raise TypeError(
- 'Required argument `' + k + '` was not specified.')
+
super(Table, self).__init__(children=children, **args)
diff --git a/tests/unit/development/test_base_component.py b/tests/unit/development/test_base_component.py
index 49d3d969ff..6b7bb96757 100644
--- a/tests/unit/development/test_base_component.py
+++ b/tests/unit/development/test_base_component.py
@@ -534,3 +534,8 @@ def update(v):
@app.callback(Output(output1, "children"), Input(input1, "value"))
def update2(v):
return f"Input 1 {v}"
+
+
+def test_debc030_invalid_children_args():
+ with pytest.raises(TypeError):
+ dcc.Input(children='invalid children')