@@ -74,28 +74,70 @@ def self.create_element(type, *properties, &block)
7474 # end
7575
7676 def self . render ( element , container )
77- raise "ReactDOM.render is not defined. In React >= v15 you must import it with ReactDOM" if ( `typeof ReactDOM === 'undefined'` )
77+ raise "ReactDOM is not defined. In React >= v15 you must import it with ReactDOM" if ( `typeof ReactDOM === 'undefined'` )
7878
7979 container = `container.$$class ? container[0] : container`
8080
81- if block_given?
82- cb = %x{
83- function(){
84- setTimeout(function(){
85- #{ yield }
86- }, 0)
87- }
81+ cb = if block_given?
82+ %x{ function(){ setTimeout(function(){ #{ yield } }, 0) } }
83+ else
84+ `null`
85+ end
86+
87+ if `typeof ReactDOM.createRoot === 'function'`
88+ # React 18+: ReactDOM.render is gone. Use createRoot (one root per
89+ # container, kept on the node for re-render + unmount). createRoot.render()
90+ # returns nothing, but Hyperstack's mount contract needs the component
91+ # instance — capture it via a ref and force a synchronous commit with
92+ # flushSync so the ref fires before we return. (#18)
93+ native = %x{
94+ (function(){
95+ var root = container.__hyperstackReactRoot;
96+ if (!root) {
97+ // React 19 (#19): an uncaught error during render is reported to the
98+ // root's onUncaughtError callback instead of propagating synchronously
99+ // out of render/flushSync (the React <= 18 behavior Hyperstack's mount
100+ // contract relies on — e.g. a NoMethodError raised in render must reach
101+ // the caller). Stash it on the container and re-throw below. On React
102+ // <= 18 the option is ignored and flushSync re-throws as before.
103+ root = ReactDOM.createRoot(container, {
104+ onUncaughtError: function(error){ container.__hyperstackRenderError = error; }
105+ });
106+ container.__hyperstackReactRoot = root;
107+ }
108+ container.__hyperstackRenderError = null;
109+ var captured = null;
110+ var elem = React.cloneElement(#{ element . to_n } , { ref: function(inst){ if (inst) { captured = inst; } } });
111+ var flush = ReactDOM.flushSync || function(f){ f(); };
112+ flush(function(){ root.render(elem); });
113+ if (container.__hyperstackRenderError) {
114+ var err = container.__hyperstackRenderError;
115+ container.__hyperstackRenderError = null;
116+ throw err;
117+ }
118+ var cb = #{ cb } ;
119+ if (cb) { cb(); }
120+ return captured;
121+ })()
88122 }
89- native = `ReactDOM.render(#{ element . to_n } , container, cb)`
123+ elsif block_given?
124+ native = `ReactDOM.render(#{ element . to_n } , container, #{ cb } )`
90125 else
91126 native = `ReactDOM.render(#{ element . to_n } , container)`
92127 end
93128
94- return unless `#{ native } !== null`
129+ return unless `#{ native } !== null && #{ native } !== undefined `
95130
96131 if `#{ native } .__opalInstance !== undefined && #{ native } .__opalInstance !== null`
97132 `#{ native } .__opalInstance`
98- elsif `ReactDOM.findDOMNode !== undefined && #{ native } .nodeType === undefined`
133+ elsif `#{ native } .nodeType !== undefined`
134+ native # already a DOM node (e.g. a host-element ref)
135+ elsif `container.firstChild !== null && container.firstChild !== undefined`
136+ # React 18 (#18): the mounted root's DOM node, without ReactDOM.findDOMNode
137+ # (deprecated in 18, removed in 19). Hyperstack mounts a single element into
138+ # `container`, so its firstChild is that component's top DOM node.
139+ `container.firstChild`
140+ elsif `ReactDOM.findDOMNode !== undefined` # React < 18 fallback
99141 `ReactDOM.findDOMNode(#{ native } )`
100142 else
101143 native
@@ -130,10 +172,15 @@ def self.render_to_static_markup(element)
130172 end
131173
132174 def self . unmount_component_at_node ( node )
133- if !( `typeof ReactDOM === 'undefined'` )
134- `ReactDOM.unmountComponentAtNode(node.$$class ? node[0] : node)` # v0.15+
175+ raise "ReactDOM is not defined. In React >= v15 you must import it with ReactDOM" if `typeof ReactDOM === 'undefined'`
176+ node = `node.$$class ? node[0] : node`
177+ if `node.__hyperstackReactRoot` # React 18+: unmount the createRoot we made
178+ %x{ node.__hyperstackReactRoot.unmount(); delete node.__hyperstackReactRoot; }
179+ true
180+ elsif `typeof ReactDOM.unmountComponentAtNode === 'function'` # React < 18
181+ `ReactDOM.unmountComponentAtNode(node)`
135182 else
136- raise "unmountComponentAtNode is not defined. In React >= v15 you must import it with ReactDOM"
183+ false
137184 end
138185 end
139186
0 commit comments