Skip to content

Commit cc50f18

Browse files
Specify model values at React (#309)
1 parent 3f55d4f commit cc50f18

File tree

13 files changed

+243
-195
lines changed

13 files changed

+243
-195
lines changed

demo/server/DreamRSC.re

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,10 @@ let createFromRequest =
165165
) => {
166166
switch (Dream.header(request, "Accept")) {
167167
| Some(accept) when is_react_component_header(accept) =>
168-
stream_model(~location=Dream.target(request), element)
168+
stream_model(
169+
~location=Dream.target(request),
170+
React.Model.Element(element),
171+
)
169172
| _ =>
170173
stream_html(
171174
~skipRoot=disableSSR,

documentation/universal-code.mld

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ module ServerReasonReact = {
7474
| Fragment(array(element));
7575

7676
// createElement is a function that returns a React.element
77-
let createElement = name => React.Element(string);
77+
let createElement = name => React.Model.Element(string);
7878
};
7979
};
8080
]}

packages/react/src/React.ml

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,17 @@ end
374374

375375
type error = { message : string; stack : Yojson.Basic.t; env : string; digest : string }
376376

377+
module Model = struct
378+
type 'element t =
379+
| Function : 'server_function Runtime.server_function -> 'element t
380+
| List : 'element t list -> 'element t
381+
| Assoc : (string * 'element t) list -> 'element t
382+
| Json : Yojson.Basic.t -> 'element t
383+
| Error : error -> 'element t
384+
| Element : 'element -> 'element t
385+
| Promise : 'a Js.Promise.t * ('a -> Yojson.Basic.t) -> 'element t
386+
end
387+
377388
type element =
378389
| Lower_case_element of lower_case_element
379390
| Upper_case_component of string * (unit -> element)
@@ -390,15 +401,8 @@ type element =
390401
| Suspense of { key : string option; children : element; fallback : element }
391402

392403
and lower_case_element = { key : string option; tag : string; attributes : JSX.prop list; children : element list }
393-
and client_props = (string * client_value) list
394-
395-
and client_value =
396-
(* TODO: Do we need to add more types here? *)
397-
| Function : 'f Runtime.server_function -> client_value
398-
| Json : Yojson.Basic.t -> client_value
399-
| Error : error -> client_value
400-
| Element : element -> client_value
401-
| Promise : 'a Js.Promise.t * ('a -> Yojson.Basic.t) -> client_value
404+
and client_props = (string * element Model.t) list
405+
and model_value = element Model.t
402406

403407
exception Invalid_children of string
404408

packages/react/src/React.mli

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,17 @@ end
557557

558558
type error = { message : string; stack : Yojson.Basic.t; env : string; digest : string }
559559

560+
module Model : sig
561+
type 'element t =
562+
| Function : 'server_function Runtime.server_function -> 'element t
563+
| List : 'element t list -> 'element t
564+
| Assoc : (string * 'element t) list -> 'element t
565+
| Json : Yojson.Basic.t -> 'element t
566+
| Error : error -> 'element t
567+
| Element : 'element -> 'element t
568+
| Promise : 'a Js.Promise.t * ('a -> Yojson.Basic.t) -> 'element t
569+
end
570+
560571
type element =
561572
| Lower_case_element of lower_case_element
562573
| Upper_case_component of string * (unit -> element)
@@ -573,14 +584,8 @@ type element =
573584
| Suspense of { key : string option; children : element; fallback : element }
574585

575586
and lower_case_element = { key : string option; tag : string; attributes : JSX.prop list; children : element list }
576-
and client_props = (string * client_value) list
577-
578-
and client_value =
579-
| Function : 'server_function Runtime.server_function -> client_value
580-
| Json : Yojson.Basic.t -> client_value
581-
| Error : error -> client_value
582-
| Element : element -> client_value
583-
| Promise : 'a Js.Promise.t * ('a -> Yojson.Basic.t) -> client_value
587+
and client_props = (string * element Model.t) list
588+
and model_value = element Model.t
584589

585590
exception Invalid_children of string
586591

packages/reactDom/src/ReactServerDOM.ml

Lines changed: 39 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,8 @@ module Model = struct
245245
context.push (to_chunk (Debug_ref (`String debug_info_ref)) current_index) |> ignore
246246
else ()
247247

248-
let rec element_to_payload ~context ?(debug = false) ~to_chunk ~env element =
249-
let is_root = ref true in
250-
251-
let rec turn_element_into_payload ~context element =
248+
let rec element_to_payload ?(debug = false) ~context ~is_root ~to_chunk ~env element =
249+
let rec turn_element_into_payload ~context ~is_root element =
252250
match (element : React.element) with
253251
| Empty -> `Null
254252
| DangerouslyInnerHtml _ ->
@@ -272,35 +270,28 @@ module Model = struct
272270
attributes
273271
in
274272
let props = props_to_json attributes in
275-
node ~key ~tag ~props (List.map (turn_element_into_payload ~context) children)
276-
| Fragment children ->
277-
if is_root.contents then is_root := false;
278-
turn_element_into_payload ~context children
279-
| List children ->
280-
if is_root.contents then is_root := false;
281-
`List (List.map (turn_element_into_payload ~context) children)
282-
| Array children ->
283-
if is_root.contents then is_root := false;
284-
`List (Array.map (turn_element_into_payload ~context) children |> Array.to_list)
273+
node ~key ~tag ~props (List.map (turn_element_into_payload ~context ~is_root) children)
274+
| Fragment children -> turn_element_into_payload ~context ~is_root:false children
275+
| List children -> `List (List.map (turn_element_into_payload ~context ~is_root:false) children)
276+
| Array children -> `List (Array.map (turn_element_into_payload ~context ~is_root:false) children |> Array.to_list)
285277
| Upper_case_component (name, component) -> (
286278
(* TODO: Get the stack info from component *)
287279
match component () with
288280
| element ->
289281
(* TODO: Can we remove the is_root difference. It currently align with react.js behavior, but it's not clear what is the purpose of it *)
290-
if is_root.contents then (
291-
is_root := false;
282+
if is_root then (
292283
(*
293284
If it's the root element, React returns the element payload instead of a reference value.
294285
Root is a special case: https://github.com/facebook/react/blob/f3a803617ec4ba9d14bf5205ffece28ed1496a1d/packages/react-server/src/ReactFlightServer.js#L756-L766
295286
*)
296287
if debug then push_debug_info ~context ~to_chunk ~env ~index:0 ~ownerName:name else ();
297-
turn_element_into_payload ~context element)
288+
turn_element_into_payload ~context ~is_root:false element)
298289
else
299290
(* If it's not the root React push the element to the stream and return the reference value *)
300291
let element_index =
301292
Stream.push ~context (fun index ->
302293
if debug then push_debug_info ~context ~to_chunk ~env ~index ~ownerName:name else ();
303-
let payload = turn_element_into_payload ~context element in
294+
let payload = turn_element_into_payload ~context ~is_root element in
304295
to_chunk (Value payload) index)
305296
in
306297
`String (ref_value element_index)
@@ -318,12 +309,12 @@ module Model = struct
318309
let error = make_error ~message ~stack ~digest:"" in
319310
let index = Stream.push ~context (to_chunk (Error (env, error))) in
320311
`String (lazy_value index)
321-
| Return element -> turn_element_into_payload ~context element
312+
| Return element -> turn_element_into_payload ~context ~is_root:false element
322313
| Sleep ->
323314
let promise =
324315
try%lwt
325316
let%lwt element = promise in
326-
Lwt.return (to_chunk (Value (turn_element_into_payload ~context element)))
317+
Lwt.return (to_chunk (Value (turn_element_into_payload ~context ~is_root element)))
327318
with exn ->
328319
let message = Printexc.to_string exn in
329320
let stack = create_stack_trace () in
@@ -335,29 +326,26 @@ module Model = struct
335326
| Suspense { key; children; fallback } ->
336327
(* TODO: Need to check is_root? *)
337328
(* TODO: Maybe we need to push suspense index and suspense node separately *)
338-
let fallback = turn_element_into_payload ~context fallback in
339-
suspense_node ~key ~fallback [ turn_element_into_payload ~context children ]
329+
let fallback = turn_element_into_payload ~context ~is_root fallback in
330+
suspense_node ~key ~fallback [ turn_element_into_payload ~context ~is_root children ]
340331
| Client_component { import_module; import_name; props; client = _ } ->
341332
let ref = component_ref ~module_:import_module ~name:import_name in
342333
let index = Stream.push ~context (to_chunk (Component_ref ref)) in
343-
let client_props = client_values_to_json ~context ~to_chunk ~env props in
334+
let client_props = models_to_payload ~context ~to_chunk ~env props in
344335
node ~tag:(ref_value index) ~props:client_props []
345336
(* TODO: Do we need to do anything with Provider and Consumer? *)
346-
| Provider children -> turn_element_into_payload ~context children
347-
| Consumer children -> turn_element_into_payload ~context children
337+
| Provider children -> turn_element_into_payload ~context ~is_root children
338+
| Consumer children -> turn_element_into_payload ~context ~is_root children
348339
in
349-
turn_element_into_payload ~context element
340+
turn_element_into_payload ~context ~is_root element
350341

351-
and client_value_to_json ~context ?debug ~to_chunk ~env value =
352-
match (value : React.client_value) with
342+
and model_to_payload ~context ?debug ~is_root ~to_chunk ~env value =
343+
match (value : React.model_value) with
353344
| Json json -> json
354345
| Error error ->
355346
let index = Stream.push ~context (to_chunk (Error (env, error))) in
356347
`String (error_value index)
357-
| Element element ->
358-
let payload = element_to_payload ~context ?debug ~to_chunk ~env element in
359-
let index = Stream.push ~context (to_chunk (Value payload)) in
360-
`String (ref_value index)
348+
| Element element -> element_to_payload ~context ?debug ~is_root ~to_chunk ~env element
361349
| Promise (promise, value_to_json) -> (
362350
match Lwt.state promise with
363351
| Return value ->
@@ -380,20 +368,28 @@ module Model = struct
380368
| Fail exn ->
381369
(* TODO: https://github.com/ml-in-barcelona/server-reason-react/issues/251 *)
382370
raise exn)
371+
| List list ->
372+
let list = List.map (fun element -> model_to_payload ~context ~is_root ~to_chunk ~env element) list in
373+
`List list
374+
| Assoc assoc ->
375+
let assoc =
376+
List.map (fun (name, value) -> (name, model_to_payload ~context ~is_root ~to_chunk ~env value)) assoc
377+
in
378+
`Assoc assoc
383379
| Function action ->
384380
let index = Stream.push ~context (to_chunk (Value (`Assoc [ ("id", `String action.id); ("bound", `Null) ]))) in
385381
`String (action_value index)
386382

387-
and client_values_to_json ~context ~to_chunk ~env props =
388-
List.map (fun (name, value) -> (name, client_value_to_json ~context ~to_chunk ~env value)) props
383+
and models_to_payload ~context ~to_chunk ~env props =
384+
List.map (fun (name, value) -> (name, model_to_payload ~context ~is_root:false ~to_chunk ~env value)) props
389385

390-
let render ?(env = `Dev) ?(debug = false) ?subscribe element =
386+
let render ?(env = `Dev) ?(debug = false) ?subscribe model =
391387
let stream, context = Stream.make () in
392-
let to_root_chunk element id =
393-
let payload = element_to_payload ~debug ~context ~to_chunk ~env element in
388+
let to_root_chunk model id =
389+
let payload = model_to_payload ~debug ~is_root:true ~context ~to_chunk ~env model in
394390
to_chunk (Value payload) id
395391
in
396-
Stream.push ~context (to_root_chunk element) |> ignore;
392+
Stream.push ~context (to_root_chunk model) |> ignore;
397393
if context.pending = 0 then context.close ();
398394
match subscribe with None -> Lwt.return () | Some subscribe -> Lwt_stream.iter_s subscribe stream
399395

@@ -405,11 +401,11 @@ module Model = struct
405401
let stack = create_stack_trace () in
406402
(* TODO: Improve it to be an UUID *)
407403
let digest = stack |> Hashtbl.hash |> Int.to_string in
408-
Lwt.return (React.Error { message; stack; env = "Server"; digest })
404+
Lwt.return (React.Model.Error { message; stack; env = "Server"; digest })
409405
in
410406
let stream, context = Stream.make () in
411407
let to_root_chunk value id =
412-
let payload = client_value_to_json ~debug ~context ~to_chunk ~env value in
408+
let payload = model_to_payload ~debug ~is_root:true ~context ~to_chunk ~env value in
413409
to_chunk (Value payload) id
414410
in
415411
Stream.push ~context (to_root_chunk response) |> ignore;
@@ -563,7 +559,7 @@ let rec render_element_to_html ~(fiber : Fiber.t) (element : React.element) : (H
563559
| Client_component { import_module; import_name; props; client } ->
564560
let context = fiber.context in
565561
let env = fiber.env in
566-
let props = Model.client_values_to_json ~context ~to_chunk:model_to_chunk ~env props in
562+
let props = Model.models_to_payload ~context ~to_chunk:model_to_chunk ~env props in
567563
let%lwt html = client_to_html ~fiber client in
568564
let ref : json = Model.component_ref ~module_:import_module ~name:import_name in
569565
let index = Stream.push ~context (model_to_chunk (Component_ref ref)) in
@@ -929,8 +925,8 @@ let decodeFormDataReply formData =
929925
(args, aux (Js.FormData.make ()) formDataEntries)
930926

931927
type server_function =
932-
| FormData of (Yojson.Basic.t array -> Js.FormData.t -> React.client_value Lwt.t)
933-
| Body of (Yojson.Basic.t array -> React.client_value Lwt.t)
928+
| FormData of (Yojson.Basic.t array -> Js.FormData.t -> React.model_value Lwt.t)
929+
| Body of (Yojson.Basic.t array -> React.model_value Lwt.t)
934930

935931
module type FunctionReferences = sig
936932
type t

packages/reactDom/src/ReactServerDOM.mli

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ val render_html :
99
(string * ((string -> unit Lwt.t) -> unit Lwt.t)) Lwt.t
1010

1111
val render_model :
12-
?env:[ `Dev | `Prod ] -> ?debug:bool -> ?subscribe:(string -> unit Lwt.t) -> React.element -> unit Lwt.t
12+
?env:[ `Dev | `Prod ] -> ?debug:bool -> ?subscribe:(string -> unit Lwt.t) -> React.model_value -> unit Lwt.t
1313

1414
val create_action_response :
15-
?env:[ `Dev | `Prod ] -> ?debug:bool -> ?subscribe:(string -> unit Lwt.t) -> React.client_value Lwt.t -> unit Lwt.t
15+
?env:[ `Dev | `Prod ] -> ?debug:bool -> ?subscribe:(string -> unit Lwt.t) -> React.model_value Lwt.t -> unit Lwt.t
1616

1717
type server_function =
18-
| FormData of (Yojson.Basic.t array -> Js.FormData.t -> React.client_value Lwt.t)
19-
| Body of (Yojson.Basic.t array -> React.client_value Lwt.t)
18+
| FormData of (Yojson.Basic.t array -> Js.FormData.t -> React.model_value Lwt.t)
19+
| Body of (Yojson.Basic.t array -> React.model_value Lwt.t)
2020

2121
val decodeReply : string -> Yojson.Basic.t array
2222
val decodeFormDataReply : Js.FormData.t -> Yojson.Basic.t array * Js.FormData.t

packages/reactDom/test/test_RSC_html.ml

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,9 @@ let client_with_promise_props () =
299299
React.Client_component
300300
{
301301
props =
302-
[ ("promise", React.Promise (delayed_value ~ms:20 "||| Resolved |||", fun res -> `String res)) ];
302+
[
303+
("promise", React.Model.Promise (delayed_value ~ms:20 "||| Resolved |||", fun res -> `String res));
304+
];
303305
client = React.string "Client with Props";
304306
import_module = "./client-with-props.js";
305307
import_name = "ClientWithProps";
@@ -328,7 +330,7 @@ let client_with_element_props () =
328330
props =
329331
[
330332
( "element",
331-
React.Element
333+
React.Model.Element
332334
(React.createElement "span" [] [ React.string "server-component-as-props-to-client-component" ])
333335
);
334336
];
@@ -339,13 +341,11 @@ let client_with_element_props () =
339341
in
340342
assert_html (app ())
341343
~shell:
342-
"Client with elment prop<script data-payload='0:[\"$\",\"$2\",null,{\"element\":\"$1\"},null,[],{}]\n\
344+
"Client with elment prop<script \
345+
data-payload='0:[\"$\",\"$1\",null,{\"element\":[\"$\",\"span\",null,{\"children\":\"server-component-as-props-to-client-component\"},null,[],{}]},null,[],{}]\n\
343346
'>window.srr_stream.push()</script>"
344347
[
345-
"<script \
346-
data-payload='1:[\"$\",\"span\",null,{\"children\":\"server-component-as-props-to-client-component\"},null,[],{}]\n\
347-
'>window.srr_stream.push()</script>";
348-
"<script data-payload='2:I[\"./client-with-props.js\",[],\"ClientWithProps\"]\n\
348+
"<script data-payload='1:I[\"./client-with-props.js\",[],\"ClientWithProps\"]\n\
349349
'>window.srr_stream.push()</script>";
350350
]
351351

@@ -365,18 +365,17 @@ let client_component_with_async_component () =
365365
{
366366
import_module = "./client.js";
367367
import_name = "Client";
368-
props = [ ("children", React.Element children) ];
368+
props = [ ("children", React.Model.Element children) ];
369369
client = children;
370370
} )
371371
in
372372
assert_html (app ~children)
373373
~shell:
374-
"Async Component<script data-payload='0:[\"$\",\"$3\",null,{\"children\":\"$2\"},null,[],{}]\n\
374+
"Async Component<script data-payload='0:[\"$\",\"$2\",null,{\"children\":\"$L1\"},null,[],{}]\n\
375375
'>window.srr_stream.push()</script>"
376376
[
377-
"<script data-payload='2:\"$L1\"\n'>window.srr_stream.push()</script>";
378377
"<script data-payload='1:\"Async Component\"\n'>window.srr_stream.push()</script>";
379-
"<script data-payload='3:I[\"./client.js\",[],\"Client\"]\n'>window.srr_stream.push()</script>";
378+
"<script data-payload='2:I[\"./client.js\",[],\"Client\"]\n'>window.srr_stream.push()</script>";
380379
]
381380

382381
let suspense_with_error () =
@@ -607,7 +606,7 @@ let nested_context () =
607606
{
608607
import_module = "./provider.js";
609608
import_name = "Provider";
610-
props = [ ("value", React.Element value); ("children", React.Element children) ];
609+
props = [ ("value", React.Model.Element value); ("children", React.Model.Element children) ];
611610
client = React.Context.provider context ~value ~children ();
612611
} )
613612
in

0 commit comments

Comments
 (0)