Skip to content

WIP: feat: native React client via client_type="react" - #69

Open
patrickoleary wants to merge 1 commit into
masterfrom
react-client-type
Open

WIP: feat: native React client via client_type="react"#69
patrickoleary wants to merge 1 commit into
masterfrom
react-client-type

Conversation

@patrickoleary

Copy link
Copy Markdown
Member

Summary

Add a React client (react-app/) as a peer of vue2-app/vue3-app. The server
side is unchanged: when client_type == "react", the widget tree serializes
to a JSON component tree (utils/react.py) pushed through the same
trame__template_* state keys, and the client renders it natively with
React.createElement.

  • widgets/core.py: structured attribute capture alongside the Vue string
    formatting (vue output byte-identical, regression-tested), react_node
    property, and r_* directive aliases (r_if/r_show/r_for/r_model/r_bind_/
    r_on_
    ) for React-flavored apps
  • react-app/: registry + renderer (expression evaluation against the shared
    state, conditionals, lists, controlled-component models, scoped slots,
    event modifiers), 14 built-in component ports, vite build into
    module/react-www
  • module/react.py + ui/core.py flush branch for the react client type

Add a React client (react-app/) as a peer of vue2-app/vue3-app. The server
side is unchanged: when client_type == "react", the widget tree serializes
to a JSON component tree (utils/react.py) pushed through the same
trame__template_* state keys, and the client renders it natively with
React.createElement.

- widgets/core.py: structured attribute capture alongside the Vue string
  formatting (vue output byte-identical, regression-tested), react_node
  property, and r_* directive aliases (r_if/r_show/r_for/r_model/r_bind_*/
  r_on_*) for React-flavored apps
- react-app/: registry + renderer (expression evaluation against the shared
  state, conditionals, lists, controlled-component models, scoped slots,
  event modifiers), 14 built-in component ports, vite build into
  module/react-www
- module/react.py + ui/core.py flush branch for the react client type
- examples/react/, serializer unit tests, Playwright e2e, react steps in CI,
  react-www in wheel data
@@ -0,0 +1,2 @@
/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That file can be removed. We don't need it anymore. No need to keep it for react

return body, arg, []


def _apply_directive(dirs, js_key, expr):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove directives since they are not part of the react logic.

if isinstance(elem, str):
return {"tag": "__fragment", "children": split_text(elem)}

server = getattr(elem, "server", None)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we pass the first if, we should be an abstract element which should have a server even if it is initialized to None...

We could have an explicit is_instance in an assert.

I can be wrong but that code like AI generated with lot of guarding while it should not needed.

("r_model_lazy", "v-model.lazy"),
("r_model_number", "v-model.number"),
("r_model_trim", "v-model.trim"),
("r_slot", "v-slot"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove all r_ as it is not part of react logic

return "\n".join(out_buffer)

@property
def react_node(self):

@jourdain jourdain Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should match the AbstractLayout react() property name.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually since in react they are different. The current name make sense.

name
for name in self._py_attr.keys()
if name.startswith("v_model_") or name.startswith("v_bind_")
if name.startswith(("v_model_", "r_model_", "v_bind_", "r_bind_"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove r_*


# smart key handling
if name.startswith("v_model_"):
if name.startswith(("v_model_", "r_model_")):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove r_*

else:
js_key = f"v-model:{model_name}{'.' if len(modifiers) else ''}{'.'.join(modifiers)}"
elif name.startswith("v_bind_"):
elif name.startswith(("v_bind_", "r_bind_")):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove r_*

}
return self

def attrs(self, *names):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that method should be revisited to better handle vue vs react logic. Having both at the same time is confusing.

return f"<{self._elem_name} html-error />"

@property
def react_node(self):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should match the AbstractLayout react() property name.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually since in react they are different. The current name make sense.

def to_react_template(root):
"""Serialize a layout root into the JSON string pushed to the client"""
node = root.react_node if hasattr(root, "react_node") else to_react_node(root)
return json.dumps({"version": 1, "root": node})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not convert to a string... We should keep the native structure as it will speedup network and handling on the client side.

function parsePayload(payload: unknown): TrameJsonNode | null {
if (!payload) return null;
try {
const { root } = JSON.parse(payload as string) as TrameTemplatePayload;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be the native structure rather than a json string

stateKey?: string | null;
}) {
const trame = useTrame();
useTrameState(); // template-wide re-render on any dirty state

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that full re-render is quite bad, I'm wondering if there is no other path that will make it cleaner. Maybe something based on Zustand for state handling?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants