@@ -49,6 +49,30 @@ module internal ReactComponentHelpers =
49
49
{ decl with MemberRef = info; Args = []; Body = body }
50
50
51
51
| _ -> { decl with Body = injectReactImport decl.Body }
52
+
53
+ let rewriteArgs ( decl : MemberDecl ) =
54
+ // rewrite all other arguments into getters of a single props object
55
+ // TODO: transform any callback into into useCallback(callback) to stabilize reference
56
+ let propsArg = AstUtils.makeIdent ( sprintf " %s InputProps" ( AstUtils.camelCase decl.Name))
57
+ let propBindings =
58
+ ([], decl.Args) ||> List.fold ( fun bindings arg ->
59
+ let getterKey = if arg.DisplayName = " key" then " $key" else arg.DisplayName
60
+ let getterKind = ExprGet( AstUtils.makeStrConst getterKey)
61
+ let getter = Get( IdentExpr propsArg, getterKind, Any, None)
62
+ ( arg, getter):: bindings)
63
+ |> List.rev
64
+
65
+ let body =
66
+ match decl.Body with
67
+ // If the body is surrounded by a memo call we put the bindings within the call
68
+ // because Fable will later move the surrounding function into memo
69
+ | Call( ReactMemo reactMemo, ({ Args = arg:: restArgs } as callInfo), t, r) ->
70
+ let arg = propBindings |> List.fold ( fun body ( k , v ) -> Let( k, v, body)) arg
71
+ Call( reactMemo, { callInfo with Args = arg:: restArgs }, t, r)
72
+ | _ ->
73
+ propBindings |> List.fold ( fun body ( k , v ) -> Let( k, v, body)) decl.Body
74
+
75
+ { decl with Args = [ propsArg]; Body = body }
52
76
53
77
open ReactComponentHelpers
54
78
@@ -74,16 +98,39 @@ type ReactComponentAttribute(?exportDefault: bool, ?import: string, ?from:string
74
98
callee
75
99
76
100
if List.length membArgs = info.Args.Length && info.Args.Length = 1 && AstUtils.isRecord compiler info.Args[ 0 ]. Type then
101
+
102
+ // declared record
103
+ // https://github.com/Zaid-Ajaj/Feliz/issues/603
104
+ // F# Component { Value = 1 }
105
+ // JSX <Component props={ { Value={1} } } />
106
+ // JS createElement(Component, { props = { Value: 1 } })
107
+
108
+ // anonymous record
77
109
// F# Component { Value = 1 }
78
110
// JSX <Component Value={1} />
79
111
// JS createElement(Component, { Value: 1 })
112
+
113
+ let isDeclaredRecord = AstUtils.isDeclaredRecord compiler info.Args[ 0 ]. Type
114
+
80
115
if AstUtils.recordHasField " Key" compiler info.Args[ 0 ]. Type then
81
116
// When the key property is upper-case (which is common in record fields)
82
117
// then we should rewrite it
83
- let modifiedRecord = AstUtils.emitJs " (($value) => { $value.key = $value.Key; return $value; })($0)" [ info.Args[ 0 ]]
118
+ let modifiedRecord =
119
+ if isDeclaredRecord then
120
+ AstUtils.objExpr [
121
+ " key" , AstUtils.emitJs " $0.Key" [ info.Args[ 0 ]];
122
+ membArgs[ 0 ]. Name.Value, info.Args[ 0 ]
123
+ ]
124
+ else
125
+ AstUtils.emitJs " (($value) => { $value.key = $value.Key; return $value; })($0)" [ info.Args[ 0 ]]
84
126
AstUtils.createElement reactElType [ reactComponent; modifiedRecord]
85
127
else
86
- AstUtils.createElement reactElType [ reactComponent; info.Args[ 0 ]]
128
+ let value =
129
+ if isDeclaredRecord then
130
+ AstUtils.objExpr [ membArgs[ 0 ]. Name.Value, info.Args[ 0 ] ]
131
+ else
132
+ info.Args[ 0 ]
133
+ AstUtils.createElement reactElType [ reactComponent; value]
87
134
elif info.Args.Length = 1 && info.Args[ 0 ]. Type = Type.Unit then
88
135
// F# Component()
89
136
// JSX <Component />
@@ -93,7 +140,8 @@ type ReactComponentAttribute(?exportDefault: bool, ?import: string, ?from:string
93
140
let mutable keyBinding = None
94
141
95
142
let propsObj =
96
- List.zip ( List.take info.Args.Length membArgs) info.Args
143
+ info.Args
144
+ |> List.zip ( List.take info.Args.Length membArgs)
97
145
|> List.collect ( fun ( arg , expr ) ->
98
146
match arg.Name, expr with
99
147
| Some " key" , IdentExpr _ -> [ " key" , expr; " $key" , expr]
@@ -127,12 +175,12 @@ type ReactComponentAttribute(?exportDefault: bool, ?import: string, ?from:string
127
175
let info = compiler.GetMember( decl.MemberRef)
128
176
if info.IsValue || info.IsGetter || info.IsSetter then
129
177
// Invalid attribute usage
130
- let errorMessage = sprintf " Expecting a function declaration for %s when using [<ReactComponent>]" decl.Name
178
+ let errorMessage = sprintf " Expecting a function declaration for %s when using [<PortalLibs.ClientPlugin. ReactComponent>]" decl.Name
131
179
compiler.LogWarning( errorMessage, ?range= decl.Body.Range)
132
180
decl
133
181
else if not ( AstUtils.isReactElement decl.Body.Type) then
134
182
// output of a React function component must be a ReactElement
135
- let errorMessage = sprintf " Expected function %s to return a ReactElement when using [<ReactComponent>]. Instead it returns %A " decl.Name decl.Body.Type
183
+ let errorMessage = sprintf " Expected function %s to return a ReactElement when using [<PortalLibs.ClientPlugin. ReactComponent>]. Instead it returns %A " decl.Name decl.Body.Type
136
184
compiler.LogWarning( errorMessage, ?range= decl.Body.Range)
137
185
decl
138
186
else
@@ -144,8 +192,13 @@ type ReactComponentAttribute(?exportDefault: bool, ?import: string, ?from:string
144
192
| Some true -> { decl with Tags = " export-default" :: decl.Tags }
145
193
| Some false | None -> decl
146
194
147
- // do not rewrite components accepting records as input
148
- if decl.Args.Length = 1 && AstUtils.isRecord compiler decl.Args[ 0 ]. Type then
195
+ // do not rewrite components accepting anonymous records as input
196
+ if decl.Args.Length = 1 && AstUtils.isAnonymousRecord decl.Args.[ 0 ]. Type then
197
+ decl
198
+ |> applyImportOrMemo import from memo
199
+ // put record into a single props object to stabilize prototype chain
200
+ // https://github.com/Zaid-Ajaj/Feliz/issues/603
201
+ elif decl.Args.Length = 1 && AstUtils.isDeclaredRecord compiler decl.Args[ 0 ]. Type then
149
202
// check whether the record type is defined in this file
150
203
// trigger warning if that is case
151
204
let definedInThisFile =
@@ -186,34 +239,15 @@ type ReactComponentAttribute(?exportDefault: bool, ?import: string, ?from:string
186
239
()
187
240
188
241
decl
242
+ |> rewriteArgs
189
243
|> applyImportOrMemo import from memo
190
244
else if decl.Args.Length = 1 && decl.Args[ 0 ]. Type = Type.Unit then
191
245
// remove arguments from functions requiring unit as input
192
246
{ decl with Args = [ ] }
193
247
|> applyImportOrMemo import from memo
194
248
else
195
- // rewrite all other arguments into getters of a single props object
196
- // TODO: transform any callback into into useCallback(callback) to stabilize reference
197
- let propsArg = AstUtils.makeIdent ( sprintf " %s InputProps" ( AstUtils.camelCase decl.Name))
198
- let propBindings =
199
- ([], decl.Args) ||> List.fold ( fun bindings arg ->
200
- let getterKey = if arg.DisplayName = " key" then " $key" else arg.DisplayName
201
- let getterKind = ExprGet( AstUtils.makeStrConst getterKey)
202
- let getter = Get( IdentExpr propsArg, getterKind, Any, None)
203
- ( arg, getter):: bindings)
204
- |> List.rev
205
-
206
- let body =
207
- match decl.Body with
208
- // If the body is surrounded by a memo call we put the bindings within the call
209
- // because Fable will later move the surrounding function into memo
210
- | Call( ReactMemo reactMemo, ({ Args = arg:: restArgs } as callInfo), t, r) ->
211
- let arg = propBindings |> List.fold ( fun body ( k , v ) -> Let( k, v, body)) arg
212
- Call( reactMemo, { callInfo with Args = arg:: restArgs }, t, r)
213
- | _ ->
214
- propBindings |> List.fold ( fun body ( k , v ) -> Let( k, v, body)) decl.Body
215
-
216
- { decl with Args = [ propsArg]; Body = body }
249
+ decl
250
+ |> rewriteArgs
217
251
|> applyImportOrMemo import from memo
218
252
219
253
type ReactMemoComponentAttribute (? exportDefault : bool ) =
0 commit comments