Can Transcrypt produce React class components?

118 Views Asked by At

I've tried to convert the Transcrypt react example to use a class component. Full code at the end but the main change is to convert this function component:

def ListItems(props):
    items = props['items']
    return [el('li', {'key': item}, item) for item in items]

to a class component:

class ListItems(React.Component):
    def render(self):
        items = self.props['items']
        return [el('li', {'key': item}, item) for item in items]

But this fails with this stack trace in the browser:

Uncaught TypeError: Cannot read properties of undefined (reading '__new__')
    at __class__ (app.12cad815.js:447:15)
    at Object.parcelRequire.__target__/app.js../org.transcrypt.__runtime__.js (app.12cad815.js:31357:57)
    at newRequire (app.12cad815.js:47:24)
    at localRequire (app.12cad815.js:53:14)
    at Object.parcelRequire.app.py../__target__/app.js (app.12cad815.js:31422:12)
    at newRequire (app.12cad815.js:47:24)
    at app.12cad815.js:81:7
    at app.12cad815.js:120:3

Full code is based on the tutorial here: https://transcrypt.org/pdfs/rtptutorial.pdf

pyreact.py:

# __pragma__('skip')
def require(lib):
    return lib

class document:
    getElementById = None
    addEventListener = None

# __pragma__ ('noskip')

React = require('react')
ReactDOM = require('react-dom')
createElement = React.createElement
useState = React.useState

def render(root_component, props, container):
    def main():
        ReactDOM.render(
            createElement(root_component, props),
            document.getElementById(container)
        )
    document.addEventListener('DOMContentLoaded', main)

app.py

from .pyreact import useState, render, createElement, React

el = createElement

class ListItems(React.Component):
    def render(self):
        items = self.props['items']
        return [el('li', {'key': item}, item) for item in items]


def App():
    newItem, setNewItem = useState("")
    items, setItems = useState([])

    def handleSubmit(event):
        event.preventDefault()
        setItems(items + [newItem]) # __:opov
        setNewItem("")

    def handleChange(event):
        target = event["target"]
        setNewItem(target["value"])

    return el(
        "form", {"onSubmit": handleSubmit},
            el("label", {"htmlFor": "newItem"}, "New Item: "),
            el("input", {"id": "newItem", "onChange": handleChange, "value": newItem}),
            el("input", {"type": "submit"}),
            el("ol", None,
                el(ListItems, {"items": items})
            )
    )

render(App, None, 'root')

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <script src="app.py"></script>
  <title>React to Python</title>
</head>

<body>
  <div id="root"></div>
</body>

</html>
1

There are 1 best solutions below

1
On

Eventually I found tictacreact which gives a decent example of how to do this. Basically, add this to pyreact.py:

registered_components = {}

class ComponentMeta(type):
    def __new__(meta, name, bases, attribs):
        cls = type.__new__(meta, name, bases, attribs)
        registered_components[name] = cls
        # override the default name='cls' property to make error messages and debugging more meaningful
        descrip = Object.getOwnPropertyDescriptor(cls, 'name');
        descrip.value = name
        Object.defineProperty(cls, 'name', descrip);
        return cls


class AbstractComponent(object, metaclass=ComponentMeta):
    '''Superclass for React Components.  Use Component below.'''
    def __init__(self, props):
        object.__init__(self)

    def render(self):
        return 'Subclass should override render()'


class Component(AbstractComponent, React.Component.prototype):
    '''Superclass for React Components.  PyReact version of React.Component'''

    def __init__(self, props):
        AbstractComponent.__init__(self)
        React.Component.apply(self, [props])

Then derive your component classes from pyreact.Component instead of React.Component. To make the IDE happy, you could also add:

class Object:
    pass

to the skipped section of pyreact.py.