Skip to content

Commit ef0b0f1

Browse files
committed
React runtime warnings: add check for state initialization
1 parent 1719431 commit ef0b0f1

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

src/lib/react/ReactComponentMacro.hx

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ class ReactComponentMacro {
1717
react.ReactTypeMacro.ensureRenderOverride,
1818
#end
1919

20-
#if (debug && react_render_warning)
20+
// Note: react_render_warning should be deprecated
21+
#if (debug && (react_render_warning || react_runtime_warnings))
2122
react.ReactDebugMacro.buildComponent,
2223
#end
2324
];

src/lib/react/ReactDebugMacro.hx

+82
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class ReactDebugMacro
1717
var pos = Context.currentPos();
1818
var propsType:Null<ComplexType> = macro :Dynamic;
1919
var stateType:Null<ComplexType> = macro :Dynamic;
20+
var hasState = false;
2021

2122
switch (inClass.superClass)
2223
{
@@ -26,13 +27,17 @@ class ReactDebugMacro
2627

2728
stateType = TypeTools.toComplexType(params[1]);
2829
if (isEmpty(stateType)) stateType = null;
30+
else hasState = true;
2931

3032
default:
3133
}
3234

3335
if (!updateComponentUpdate(fields, inClass, propsType, stateType))
3436
addComponentUpdate(fields, inClass, propsType, stateType);
3537

38+
if (hasState && !updateConstructor(fields, inClass, propsType, stateType))
39+
addConstructor(fields, inClass, propsType, stateType);
40+
3641
return fields;
3742
}
3843

@@ -47,6 +52,83 @@ class ReactDebugMacro
4752
};
4853
}
4954

55+
static function updateConstructor(
56+
fields:Array<Field>,
57+
inClass:ClassType,
58+
propsType:Null<ComplexType>,
59+
stateType:Null<ComplexType>
60+
) {
61+
for (field in fields)
62+
{
63+
if (field.name == "new")
64+
{
65+
switch (field.kind) {
66+
case FFun(f):
67+
f.expr = macro {
68+
${f.expr}
69+
${exprConstructor(inClass)}
70+
};
71+
72+
return true;
73+
default:
74+
}
75+
}
76+
}
77+
78+
return false;
79+
}
80+
81+
static function addConstructor(
82+
fields:Array<Field>,
83+
inClass:ClassType,
84+
propsType:Null<ComplexType>,
85+
stateType:Null<ComplexType>
86+
) {
87+
var constructor = {
88+
args: [
89+
{
90+
meta: [],
91+
name: "props",
92+
type: propsType == null ? macro :react.Empty : propsType,
93+
opt: false,
94+
value: null
95+
}
96+
],
97+
ret: macro :Void,
98+
expr: macro {
99+
super(props);
100+
${exprConstructor(inClass)};
101+
}
102+
}
103+
104+
fields.push({
105+
name: 'new',
106+
access: [APublic],
107+
kind: FFun(constructor),
108+
pos: inClass.pos
109+
});
110+
}
111+
112+
static function exprConstructor(inClass:ClassType)
113+
{
114+
return macro {
115+
if (state == null) {
116+
js.Browser.console.error(
117+
'Warning: component ${inClass.name} is stateful but its '
118+
+ '`state` is not initialized inside its constructor. '
119+
120+
+ 'Either add a `state = { ... }` statement to its constructor '
121+
+ 'or define this component as a `ReactComponentOfProps` '
122+
+ 'if it is only using `props`. '
123+
124+
+ 'If it is using neither `props` nor `state`, you might '
125+
+ 'consider using `@:jsxStatic` to avoid unneeded lifecycle.'
126+
// TODO: link to @:jsxStatic documentation when available
127+
);
128+
}
129+
};
130+
}
131+
50132
static function updateComponentUpdate(
51133
fields:Array<Field>,
52134
inClass:ClassType,

0 commit comments

Comments
 (0)