From 36364049da14ac3f848009a4649464c4a4704b65 Mon Sep 17 00:00:00 2001 From: Kyle Mellander Date: Wed, 15 Dec 2021 11:31:08 -0800 Subject: [PATCH 1/2] add detection for Reacts varying render functions --- react_ujs/index.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/react_ujs/index.js b/react_ujs/index.js index 9f56d2b1..927e67f3 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -111,13 +111,14 @@ var ReactRailsUJS = { } } - if (hydrate && typeof ReactDOM.hydrate === "function") { - component = ReactDOM.hydrate(component, node); + if (hydrate && typeof ReactDOM.hydrateRoot === "function") { + component = ReactDOM.hydrateRoot(component, node); } else { - component = ReactDOM.render(component, node); + const root = ReactDOM.createRoot(node) + component = root.render(component); } } - } + } }, // Within `searchSelector`, find nodes which have React components From 828ee368b4871be7c7d4a280ffedb541bf2afe4e Mon Sep 17 00:00:00 2001 From: Kyle Mellander Date: Wed, 15 Dec 2021 13:08:36 -0800 Subject: [PATCH 2/2] support React 18's client side render methods With React 18, the rendering interface will be updated in order to benefit from new features. https://github.com/reactwg/react-18/discussions/5 This allows React 18 rendering to be supported while creating backwards compatibility. For the time being, the return of the components does not change the output from the previous versions so as to not cause any breaking changes. --- lib/assets/javascripts/react_ujs.js | 111 ++++++++++++++++++---------- react_ujs/dist/react_ujs.js | 111 ++++++++++++++++++---------- react_ujs/index.js | 7 +- react_ujs/src/renderHelpers.js | 26 +++++++ 4 files changed, 178 insertions(+), 77 deletions(-) create mode 100644 react_ujs/src/renderHelpers.js diff --git a/lib/assets/javascripts/react_ujs.js b/lib/assets/javascripts/react_ujs.js index 0c00745d..1cfe8b4e 100644 --- a/lib/assets/javascripts/react_ujs.js +++ b/lib/assets/javascripts/react_ujs.js @@ -1,13 +1,13 @@ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(require("react"), require("react-dom"), require("react-dom/server")); + module.exports = factory(require("react-dom"), require("react"), require("react-dom/server")); else if(typeof define === 'function' && define.amd) - define(["react", "react-dom", "react-dom/server"], factory); + define(["react-dom", "react", "react-dom/server"], factory); else if(typeof exports === 'object') - exports["ReactRailsUJS"] = factory(require("react"), require("react-dom"), require("react-dom/server")); + exports["ReactRailsUJS"] = factory(require("react-dom"), require("react"), require("react-dom/server")); else - root["ReactRailsUJS"] = factory(root["React"], root["ReactDOM"], root["ReactDOMServer"]); -})(this, function(__WEBPACK_EXTERNAL_MODULE_3__, __WEBPACK_EXTERNAL_MODULE_4__, __WEBPACK_EXTERNAL_MODULE_5__) { + root["ReactRailsUJS"] = factory(root["ReactDOM"], root["React"], root["ReactDOMServer"]); +})(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_5__, __WEBPACK_EXTERNAL_MODULE_6__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; @@ -73,7 +73,7 @@ return /******/ (function(modules) { // webpackBootstrap /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 6); +/******/ return __webpack_require__(__webpack_require__.s = 7); /******/ }) /************************************************************************/ /******/ ([ @@ -106,13 +106,19 @@ module.exports = function(className) { /***/ }), /* 1 */ +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_1__; + +/***/ }), +/* 2 */ /***/ (function(module, exports, __webpack_require__) { -var nativeEvents = __webpack_require__(7) -var pjaxEvents = __webpack_require__(8) -var turbolinksEvents = __webpack_require__(9) -var turbolinksClassicDeprecatedEvents = __webpack_require__(11) -var turbolinksClassicEvents = __webpack_require__(10) +var nativeEvents = __webpack_require__(8) +var pjaxEvents = __webpack_require__(9) +var turbolinksEvents = __webpack_require__(10) +var turbolinksClassicDeprecatedEvents = __webpack_require__(12) +var turbolinksClassicEvents = __webpack_require__(11) // see what things are globally available // and setup event handlers to those things @@ -164,14 +170,14 @@ module.exports = function(ujs) { /***/ }), -/* 2 */ +/* 3 */ /***/ (function(module, exports, __webpack_require__) { // Make a function which: // - First tries to require the name // - Then falls back to global lookup var fromGlobal = __webpack_require__(0) -var fromRequireContext = __webpack_require__(12) +var fromRequireContext = __webpack_require__(13) module.exports = function(reqctx) { var fromCtx = fromRequireContext(reqctx) @@ -195,16 +201,41 @@ module.exports = function(reqctx) { /***/ }), -/* 3 */ -/***/ (function(module, exports) { +/* 4 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -module.exports = __WEBPACK_EXTERNAL_MODULE_3__; +"use strict"; +Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); +/* harmony export (immutable) */ __webpack_exports__["supportsHydration"] = supportsHydration; +/* harmony export (immutable) */ __webpack_exports__["reactHydrate"] = reactHydrate; +/* harmony export (immutable) */ __webpack_exports__["createReactRootLike"] = createReactRootLike; +const ReactDOM = __webpack_require__(1) -/***/ }), -/* 4 */ -/***/ (function(module, exports) { +function supportsHydration() { + return typeof ReactDOM.hydrate === "function" || typeof ReactDOM.hydrateRoot === "function" +} + +function reactHydrate(node, component) { + if (typeof ReactDOM.hydrateRoot === "function") { + return ReactDOM.hydrateRoot(node, component) + } else { + return ReactDOM.hydrate(component, node) + } +} + +function createReactRootLike(node) { + return ReactDOM.createRoot ? ReactDOM.createRoot(node) : legacyReactRootLike(node) +} + +function legacyReactRootLike(node) { + const root = { + render(component) { + return ReactDOM.render(component, node) + } + } + return root +} -module.exports = __WEBPACK_EXTERNAL_MODULE_4__; /***/ }), /* 5 */ @@ -214,15 +245,22 @@ module.exports = __WEBPACK_EXTERNAL_MODULE_5__; /***/ }), /* 6 */ +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_6__; + +/***/ }), +/* 7 */ /***/ (function(module, exports, __webpack_require__) { -var React = __webpack_require__(3) -var ReactDOM = __webpack_require__(4) -var ReactDOMServer = __webpack_require__(5) +var React = __webpack_require__(5) +var ReactDOM = __webpack_require__(1) +var ReactDOMServer = __webpack_require__(6) -var detectEvents = __webpack_require__(1) +var detectEvents = __webpack_require__(2) var constructorFromGlobal = __webpack_require__(0) -var constructorFromRequireContextWithGlobalFallback = __webpack_require__(2) +var constructorFromRequireContextWithGlobalFallback = __webpack_require__(3) +const { supportsHydration, reactHydrate, createReactRootLike } = __webpack_require__(4) var ReactRailsUJS = { // This attribute holds the name of component which should be mounted @@ -329,13 +367,14 @@ var ReactRailsUJS = { } } - if (hydrate && typeof ReactDOM.hydrate === "function") { - component = ReactDOM.hydrate(component, node); + if (hydrate && supportsHydration()) { + component = reactHydrate(node, component); } else { - component = ReactDOM.render(component, node); + const root = createReactRootLike(node) + component = root.render(component); } } - } + } }, // Within `searchSelector`, find nodes which have React components @@ -390,7 +429,7 @@ module.exports = ReactRailsUJS /***/ }), -/* 7 */ +/* 8 */ /***/ (function(module, exports) { module.exports = { @@ -413,7 +452,7 @@ module.exports = { /***/ }), -/* 8 */ +/* 9 */ /***/ (function(module, exports) { module.exports = { @@ -433,25 +472,23 @@ module.exports = { /***/ }), -/* 9 */ +/* 10 */ /***/ (function(module, exports) { module.exports = { // Turbolinks 5+ got rid of named events (?!) setup: function(ujs) { ujs.handleEvent('turbolinks:load', ujs.handleMount); - ujs.handleEvent('turbolinks:before-render', ujs.handleUnmount); }, teardown: function(ujs) { ujs.removeEvent('turbolinks:load', ujs.handleMount); - ujs.removeEvent('turbolinks:before-render', ujs.handleUnmount); }, } /***/ }), -/* 10 */ +/* 11 */ /***/ (function(module, exports) { module.exports = { @@ -469,7 +506,7 @@ module.exports = { /***/ }), -/* 11 */ +/* 12 */ /***/ (function(module, exports) { module.exports = { @@ -490,7 +527,7 @@ module.exports = { /***/ }), -/* 12 */ +/* 13 */ /***/ (function(module, exports) { // Load React components by requiring them from "components/", for example: diff --git a/react_ujs/dist/react_ujs.js b/react_ujs/dist/react_ujs.js index 0c00745d..1cfe8b4e 100644 --- a/react_ujs/dist/react_ujs.js +++ b/react_ujs/dist/react_ujs.js @@ -1,13 +1,13 @@ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(require("react"), require("react-dom"), require("react-dom/server")); + module.exports = factory(require("react-dom"), require("react"), require("react-dom/server")); else if(typeof define === 'function' && define.amd) - define(["react", "react-dom", "react-dom/server"], factory); + define(["react-dom", "react", "react-dom/server"], factory); else if(typeof exports === 'object') - exports["ReactRailsUJS"] = factory(require("react"), require("react-dom"), require("react-dom/server")); + exports["ReactRailsUJS"] = factory(require("react-dom"), require("react"), require("react-dom/server")); else - root["ReactRailsUJS"] = factory(root["React"], root["ReactDOM"], root["ReactDOMServer"]); -})(this, function(__WEBPACK_EXTERNAL_MODULE_3__, __WEBPACK_EXTERNAL_MODULE_4__, __WEBPACK_EXTERNAL_MODULE_5__) { + root["ReactRailsUJS"] = factory(root["ReactDOM"], root["React"], root["ReactDOMServer"]); +})(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_5__, __WEBPACK_EXTERNAL_MODULE_6__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; @@ -73,7 +73,7 @@ return /******/ (function(modules) { // webpackBootstrap /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 6); +/******/ return __webpack_require__(__webpack_require__.s = 7); /******/ }) /************************************************************************/ /******/ ([ @@ -106,13 +106,19 @@ module.exports = function(className) { /***/ }), /* 1 */ +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_1__; + +/***/ }), +/* 2 */ /***/ (function(module, exports, __webpack_require__) { -var nativeEvents = __webpack_require__(7) -var pjaxEvents = __webpack_require__(8) -var turbolinksEvents = __webpack_require__(9) -var turbolinksClassicDeprecatedEvents = __webpack_require__(11) -var turbolinksClassicEvents = __webpack_require__(10) +var nativeEvents = __webpack_require__(8) +var pjaxEvents = __webpack_require__(9) +var turbolinksEvents = __webpack_require__(10) +var turbolinksClassicDeprecatedEvents = __webpack_require__(12) +var turbolinksClassicEvents = __webpack_require__(11) // see what things are globally available // and setup event handlers to those things @@ -164,14 +170,14 @@ module.exports = function(ujs) { /***/ }), -/* 2 */ +/* 3 */ /***/ (function(module, exports, __webpack_require__) { // Make a function which: // - First tries to require the name // - Then falls back to global lookup var fromGlobal = __webpack_require__(0) -var fromRequireContext = __webpack_require__(12) +var fromRequireContext = __webpack_require__(13) module.exports = function(reqctx) { var fromCtx = fromRequireContext(reqctx) @@ -195,16 +201,41 @@ module.exports = function(reqctx) { /***/ }), -/* 3 */ -/***/ (function(module, exports) { +/* 4 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -module.exports = __WEBPACK_EXTERNAL_MODULE_3__; +"use strict"; +Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); +/* harmony export (immutable) */ __webpack_exports__["supportsHydration"] = supportsHydration; +/* harmony export (immutable) */ __webpack_exports__["reactHydrate"] = reactHydrate; +/* harmony export (immutable) */ __webpack_exports__["createReactRootLike"] = createReactRootLike; +const ReactDOM = __webpack_require__(1) -/***/ }), -/* 4 */ -/***/ (function(module, exports) { +function supportsHydration() { + return typeof ReactDOM.hydrate === "function" || typeof ReactDOM.hydrateRoot === "function" +} + +function reactHydrate(node, component) { + if (typeof ReactDOM.hydrateRoot === "function") { + return ReactDOM.hydrateRoot(node, component) + } else { + return ReactDOM.hydrate(component, node) + } +} + +function createReactRootLike(node) { + return ReactDOM.createRoot ? ReactDOM.createRoot(node) : legacyReactRootLike(node) +} + +function legacyReactRootLike(node) { + const root = { + render(component) { + return ReactDOM.render(component, node) + } + } + return root +} -module.exports = __WEBPACK_EXTERNAL_MODULE_4__; /***/ }), /* 5 */ @@ -214,15 +245,22 @@ module.exports = __WEBPACK_EXTERNAL_MODULE_5__; /***/ }), /* 6 */ +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_6__; + +/***/ }), +/* 7 */ /***/ (function(module, exports, __webpack_require__) { -var React = __webpack_require__(3) -var ReactDOM = __webpack_require__(4) -var ReactDOMServer = __webpack_require__(5) +var React = __webpack_require__(5) +var ReactDOM = __webpack_require__(1) +var ReactDOMServer = __webpack_require__(6) -var detectEvents = __webpack_require__(1) +var detectEvents = __webpack_require__(2) var constructorFromGlobal = __webpack_require__(0) -var constructorFromRequireContextWithGlobalFallback = __webpack_require__(2) +var constructorFromRequireContextWithGlobalFallback = __webpack_require__(3) +const { supportsHydration, reactHydrate, createReactRootLike } = __webpack_require__(4) var ReactRailsUJS = { // This attribute holds the name of component which should be mounted @@ -329,13 +367,14 @@ var ReactRailsUJS = { } } - if (hydrate && typeof ReactDOM.hydrate === "function") { - component = ReactDOM.hydrate(component, node); + if (hydrate && supportsHydration()) { + component = reactHydrate(node, component); } else { - component = ReactDOM.render(component, node); + const root = createReactRootLike(node) + component = root.render(component); } } - } + } }, // Within `searchSelector`, find nodes which have React components @@ -390,7 +429,7 @@ module.exports = ReactRailsUJS /***/ }), -/* 7 */ +/* 8 */ /***/ (function(module, exports) { module.exports = { @@ -413,7 +452,7 @@ module.exports = { /***/ }), -/* 8 */ +/* 9 */ /***/ (function(module, exports) { module.exports = { @@ -433,25 +472,23 @@ module.exports = { /***/ }), -/* 9 */ +/* 10 */ /***/ (function(module, exports) { module.exports = { // Turbolinks 5+ got rid of named events (?!) setup: function(ujs) { ujs.handleEvent('turbolinks:load', ujs.handleMount); - ujs.handleEvent('turbolinks:before-render', ujs.handleUnmount); }, teardown: function(ujs) { ujs.removeEvent('turbolinks:load', ujs.handleMount); - ujs.removeEvent('turbolinks:before-render', ujs.handleUnmount); }, } /***/ }), -/* 10 */ +/* 11 */ /***/ (function(module, exports) { module.exports = { @@ -469,7 +506,7 @@ module.exports = { /***/ }), -/* 11 */ +/* 12 */ /***/ (function(module, exports) { module.exports = { @@ -490,7 +527,7 @@ module.exports = { /***/ }), -/* 12 */ +/* 13 */ /***/ (function(module, exports) { // Load React components by requiring them from "components/", for example: diff --git a/react_ujs/index.js b/react_ujs/index.js index 927e67f3..d78d1b7e 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -5,6 +5,7 @@ var ReactDOMServer = require("react-dom/server") var detectEvents = require("./src/events/detect") var constructorFromGlobal = require("./src/getConstructor/fromGlobal") var constructorFromRequireContextWithGlobalFallback = require("./src/getConstructor/fromRequireContextWithGlobalFallback") +const { supportsHydration, reactHydrate, createReactRootLike } = require("./src/renderHelpers") var ReactRailsUJS = { // This attribute holds the name of component which should be mounted @@ -111,10 +112,10 @@ var ReactRailsUJS = { } } - if (hydrate && typeof ReactDOM.hydrateRoot === "function") { - component = ReactDOM.hydrateRoot(component, node); + if (hydrate && supportsHydration()) { + component = reactHydrate(node, component); } else { - const root = ReactDOM.createRoot(node) + const root = createReactRootLike(node) component = root.render(component); } } diff --git a/react_ujs/src/renderHelpers.js b/react_ujs/src/renderHelpers.js new file mode 100644 index 00000000..1d9a5790 --- /dev/null +++ b/react_ujs/src/renderHelpers.js @@ -0,0 +1,26 @@ +const ReactDOM = require("react-dom") + +export function supportsHydration() { + return typeof ReactDOM.hydrate === "function" || typeof ReactDOM.hydrateRoot === "function" +} + +export function reactHydrate(node, component) { + if (typeof ReactDOM.hydrateRoot === "function") { + return ReactDOM.hydrateRoot(node, component) + } else { + return ReactDOM.hydrate(component, node) + } +} + +export function createReactRootLike(node) { + return ReactDOM.createRoot ? ReactDOM.createRoot(node) : legacyReactRootLike(node) +} + +function legacyReactRootLike(node) { + const root = { + render(component) { + return ReactDOM.render(component, node) + } + } + return root +}