From ee3557f4accf7210988dfd52c7d96a5f9f718aff Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Sat, 25 Apr 2020 16:29:19 -0600 Subject: [PATCH 01/20] add renderComponent extension point --- react_ujs/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/react_ujs/index.js b/react_ujs/index.js index 9f56d2b17..f0499e9b7 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -80,6 +80,10 @@ var ReactRailsUJS = { return ReactDOMServer[renderFunction](element) }, + renderComponent: function(renderFunction, component, node, props) { + ReactDOM[renderFunction](component, node); + }, + // Within `searchSelector`, find nodes which should have React components // inside them, and mount them with their props. mountComponents: function(searchSelector) { @@ -112,9 +116,9 @@ var ReactRailsUJS = { } if (hydrate && typeof ReactDOM.hydrate === "function") { - component = ReactDOM.hydrate(component, node); + renderComponent("hydrate", component, node, props); } else { - component = ReactDOM.render(component, node); + renderComponent("render", component, node, props); } } } From 7cb3d152496182c059245cee7be7b820066ffc3d Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Sat, 25 Apr 2020 16:36:17 -0600 Subject: [PATCH 02/20] doc + default --- react_ujs/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/react_ujs/index.js b/react_ujs/index.js index f0499e9b7..cbed2c99b 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -80,7 +80,12 @@ var ReactRailsUJS = { return ReactDOMServer[renderFunction](element) }, - renderComponent: function(renderFunction, component, node, props) { + // Render `component` using the specified `renderFunction` from `react-dom`. + // Override this function to render components in a custom way, + // the default is ReactRailsUJS.renderWithReactDOM + renderComponent: this.renderWithReactDOM, + + renderWithReactDOM: function(renderFunction, component, node, props) { ReactDOM[renderFunction](component, node); }, From acdee60bd13d0e71e81ad6a2c03879ab84493088 Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Sat, 25 Apr 2020 17:05:47 -0600 Subject: [PATCH 03/20] mirror getConstructor structure --- react_ujs/index.js | 35 +++++++++--- .../src/renderComponent/withHotReload.js | 54 +++++++++++++++++++ react_ujs/src/renderComponent/withReactDOM.js | 8 +++ 3 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 react_ujs/src/renderComponent/withHotReload.js create mode 100644 react_ujs/src/renderComponent/withReactDOM.js diff --git a/react_ujs/index.js b/react_ujs/index.js index cbed2c99b..12065efc1 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -6,6 +6,9 @@ var detectEvents = require("./src/events/detect") var constructorFromGlobal = require("./src/getConstructor/fromGlobal") var constructorFromRequireContextWithGlobalFallback = require("./src/getConstructor/fromRequireContextWithGlobalFallback") +var renderWithReactDOM = require("./src/renderComponent/withReactDOM") +var renderWithHotReload = require("./src/renderComponent/withHotReload") + var ReactRailsUJS = { // This attribute holds the name of component which should be mounted // example: `data-react-class="MyApp.Items.EditForm"` @@ -81,12 +84,32 @@ var ReactRailsUJS = { }, // Render `component` using the specified `renderFunction` from `react-dom`. - // Override this function to render components in a custom way, - // the default is ReactRailsUJS.renderWithReactDOM - renderComponent: this.renderWithReactDOM, - - renderWithReactDOM: function(renderFunction, component, node, props) { - ReactDOM[renderFunction](component, node); + // Override this function to render components in a custom way. + renderComponent: renderWithReactDOM, + + // Enables hot reload for component rendering. + // + // Ensure + // 1. [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) are installed; + // 2. your webpack config has the following in dev: + // { + // module: { + // rules: [ + // { + // test: /\.(jsx|tsx)?$/, + // use: ["react-hot-loader/webpack"], + // }, + // ], + // }, + // resolve: { + // alias: { + // "react-dom": "@hot-loader/react-dom", + // }, + // }, + // } + // + useHotReload: function(requireContext) { + this.renderComponent = renderWithHotReload(requireContext) }, // Within `searchSelector`, find nodes which should have React components diff --git a/react_ujs/src/renderComponent/withHotReload.js b/react_ujs/src/renderComponent/withHotReload.js new file mode 100644 index 000000000..4dfebe433 --- /dev/null +++ b/react_ujs/src/renderComponent/withHotReload.js @@ -0,0 +1,54 @@ +var ReactDOM = require("react-dom") +var reactHotLoader = require("react-hot-loader") +var AppContainer = reactHotLoader.AppContainer; + +// Render React component with hot reload. +// +// Ensure +// 1. [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) are installed; +// 2. your webpack config has the following in dev: +// { +// module: { +// rules: [ +// { +// test: /\.(jsx|tsx)?$/, +// use: ["react-hot-loader/webpack"], +// }, +// ], +// }, +// resolve: { +// alias: { +// "react-dom": "@hot-loader/react-dom", +// }, +// }, +// } +// +module.exports = function(reqctx) { + return function(renderFunctionName, component, node, props) { + var className = node.getAttribute(ReactRailsUJS.CLASS_NAME_ATTR); + var filename = getFileNameFromClassName(className); + var path = reqctx.resolve("./" + filename); + var cache = require.cache; + var module = cache[path]; + var moduleParent = module && cache[module.parents[0]]; + if (!moduleParent || !moduleParent.hot) { + console.warn(`Cannot hot reload for ${path}. Ensure webpack-dev-server is started with --hot and WEBPACKER_DEV_SERVER_HMR=true`); + return; + } + moduleParent.hot.accept(path, () => { + var FreshConstructor = ReactRailsUJS.getConstructor(className); + var FreshComponent = React.createElement(FreshConstructor, props); + + ReactDOM[renderFunctionName](React.createElement(AppContainer, null, FreshComponent), node); + }); + + ReactDOM[renderFunctionName](React.createElement(AppContainer, null, component), node); + }; +} + +function getFileNameFromClassName(className) { + var parts = className.split("."); + var filename = parts.shift(); + + return filename; +} diff --git a/react_ujs/src/renderComponent/withReactDOM.js b/react_ujs/src/renderComponent/withReactDOM.js new file mode 100644 index 000000000..bf56a54f9 --- /dev/null +++ b/react_ujs/src/renderComponent/withReactDOM.js @@ -0,0 +1,8 @@ +// Render React component via ReactDOM, for example: +// +// - `renderComponent("hydrate", component, node, props)` -> `ReactDOM.hydrate(component, node);` +// - `renderComponent("render", component, node, props)` -> `ReactDOM.render(component, node);` +// +module.exports = function(renderFunctionName, component, node) { + ReactDOM[renderFunctionName](component, node); +}; From 65439672525bfee0644df72aa6f340e00260781f Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Sat, 25 Apr 2020 17:06:23 -0600 Subject: [PATCH 04/20] add missing variable --- react_ujs/src/renderComponent/withReactDOM.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/react_ujs/src/renderComponent/withReactDOM.js b/react_ujs/src/renderComponent/withReactDOM.js index bf56a54f9..a6785a2ed 100644 --- a/react_ujs/src/renderComponent/withReactDOM.js +++ b/react_ujs/src/renderComponent/withReactDOM.js @@ -1,3 +1,5 @@ +var ReactDOM = require("react-dom") + // Render React component via ReactDOM, for example: // // - `renderComponent("hydrate", component, node, props)` -> `ReactDOM.hydrate(component, node);` From bfe05492f9ed8770d13d292be4e63ede5ad216e9 Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Sat, 25 Apr 2020 17:11:19 -0600 Subject: [PATCH 05/20] update readme --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 425d77c55..a962e1139 100644 --- a/README.md +++ b/README.md @@ -683,7 +683,34 @@ LibV8 itself is already [beyond version 7](https://github.com/cowboyd/libv8/rele ### HMR Hot Module Replacement is [possible with this gem](https://stackoverflow.com/a/54846330/193785) as it does just pass through to Webpacker. Please open an issue to let us know tips and tricks for it to add to the wiki. -Sample repo that shows HMR working with `react-rails`: [https://github.com/edelgado/react-rails-hmr](https://github.com/edelgado/react-rails-hmr) +Similar to `useContext`, you can pass the webpack context into `useHotReload` to enable hot reload: +```js +var myCustomContext = require.context("custom_components", true) +var ReactRailsUJS = require("react_ujs") +// use `custom_components/` for <%= react_component(...) %> calls +ReactRailsUJS.useHotReload(myCustomContext) +``` + +You must ensure +1. [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) are installed; +2. your webpack config has the following in dev: +```js +{ + module: { + rules: [ + { + test: /\.(jsx|tsx)?$/, + use: ["react-hot-loader/webpack"], + }, + ], + }, + resolve: { + alias: { + "react-dom": "@hot-loader/react-dom", + }, + }, +} +``` One caveat is that currently you [cannot Server-Side Render along with HMR](https://github.com/reactjs/react-rails/issues/925#issuecomment-415469572). From ad27aee8ff15734d277d86b20540976759c25027 Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Sat, 25 Apr 2020 17:18:06 -0600 Subject: [PATCH 06/20] rollback hot reload specfic diffs --- README.md | 29 +--------- react_ujs/index.js | 27 +--------- .../src/renderComponent/withHotReload.js | 54 ------------------- 3 files changed, 2 insertions(+), 108 deletions(-) delete mode 100644 react_ujs/src/renderComponent/withHotReload.js diff --git a/README.md b/README.md index a962e1139..425d77c55 100644 --- a/README.md +++ b/README.md @@ -683,34 +683,7 @@ LibV8 itself is already [beyond version 7](https://github.com/cowboyd/libv8/rele ### HMR Hot Module Replacement is [possible with this gem](https://stackoverflow.com/a/54846330/193785) as it does just pass through to Webpacker. Please open an issue to let us know tips and tricks for it to add to the wiki. -Similar to `useContext`, you can pass the webpack context into `useHotReload` to enable hot reload: -```js -var myCustomContext = require.context("custom_components", true) -var ReactRailsUJS = require("react_ujs") -// use `custom_components/` for <%= react_component(...) %> calls -ReactRailsUJS.useHotReload(myCustomContext) -``` - -You must ensure -1. [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) are installed; -2. your webpack config has the following in dev: -```js -{ - module: { - rules: [ - { - test: /\.(jsx|tsx)?$/, - use: ["react-hot-loader/webpack"], - }, - ], - }, - resolve: { - alias: { - "react-dom": "@hot-loader/react-dom", - }, - }, -} -``` +Sample repo that shows HMR working with `react-rails`: [https://github.com/edelgado/react-rails-hmr](https://github.com/edelgado/react-rails-hmr) One caveat is that currently you [cannot Server-Side Render along with HMR](https://github.com/reactjs/react-rails/issues/925#issuecomment-415469572). diff --git a/react_ujs/index.js b/react_ujs/index.js index 12065efc1..0ef70a5a3 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -7,7 +7,6 @@ var constructorFromGlobal = require("./src/getConstructor/fromGlobal") var constructorFromRequireContextWithGlobalFallback = require("./src/getConstructor/fromRequireContextWithGlobalFallback") var renderWithReactDOM = require("./src/renderComponent/withReactDOM") -var renderWithHotReload = require("./src/renderComponent/withHotReload") var ReactRailsUJS = { // This attribute holds the name of component which should be mounted @@ -85,33 +84,9 @@ var ReactRailsUJS = { // Render `component` using the specified `renderFunction` from `react-dom`. // Override this function to render components in a custom way. + // function signature: ("hydrate" | "render", component, node, props) renderComponent: renderWithReactDOM, - // Enables hot reload for component rendering. - // - // Ensure - // 1. [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) are installed; - // 2. your webpack config has the following in dev: - // { - // module: { - // rules: [ - // { - // test: /\.(jsx|tsx)?$/, - // use: ["react-hot-loader/webpack"], - // }, - // ], - // }, - // resolve: { - // alias: { - // "react-dom": "@hot-loader/react-dom", - // }, - // }, - // } - // - useHotReload: function(requireContext) { - this.renderComponent = renderWithHotReload(requireContext) - }, - // Within `searchSelector`, find nodes which should have React components // inside them, and mount them with their props. mountComponents: function(searchSelector) { diff --git a/react_ujs/src/renderComponent/withHotReload.js b/react_ujs/src/renderComponent/withHotReload.js deleted file mode 100644 index 4dfebe433..000000000 --- a/react_ujs/src/renderComponent/withHotReload.js +++ /dev/null @@ -1,54 +0,0 @@ -var ReactDOM = require("react-dom") -var reactHotLoader = require("react-hot-loader") -var AppContainer = reactHotLoader.AppContainer; - -// Render React component with hot reload. -// -// Ensure -// 1. [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) are installed; -// 2. your webpack config has the following in dev: -// { -// module: { -// rules: [ -// { -// test: /\.(jsx|tsx)?$/, -// use: ["react-hot-loader/webpack"], -// }, -// ], -// }, -// resolve: { -// alias: { -// "react-dom": "@hot-loader/react-dom", -// }, -// }, -// } -// -module.exports = function(reqctx) { - return function(renderFunctionName, component, node, props) { - var className = node.getAttribute(ReactRailsUJS.CLASS_NAME_ATTR); - var filename = getFileNameFromClassName(className); - var path = reqctx.resolve("./" + filename); - var cache = require.cache; - var module = cache[path]; - var moduleParent = module && cache[module.parents[0]]; - if (!moduleParent || !moduleParent.hot) { - console.warn(`Cannot hot reload for ${path}. Ensure webpack-dev-server is started with --hot and WEBPACKER_DEV_SERVER_HMR=true`); - return; - } - moduleParent.hot.accept(path, () => { - var FreshConstructor = ReactRailsUJS.getConstructor(className); - var FreshComponent = React.createElement(FreshConstructor, props); - - ReactDOM[renderFunctionName](React.createElement(AppContainer, null, FreshComponent), node); - }); - - ReactDOM[renderFunctionName](React.createElement(AppContainer, null, component), node); - }; -} - -function getFileNameFromClassName(className) { - var parts = className.split("."); - var filename = parts.shift(); - - return filename; -} From 304fa1e5011ec48511c963336dee767443b3d366 Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Sat, 25 Apr 2020 17:48:27 -0600 Subject: [PATCH 07/20] Revert "rollback hot reload specfic diffs" This reverts commit ad27aee8ff15734d277d86b20540976759c25027. --- README.md | 29 +++++++++- react_ujs/index.js | 27 +++++++++- .../src/renderComponent/withHotReload.js | 54 +++++++++++++++++++ 3 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 react_ujs/src/renderComponent/withHotReload.js diff --git a/README.md b/README.md index 425d77c55..a962e1139 100644 --- a/README.md +++ b/README.md @@ -683,7 +683,34 @@ LibV8 itself is already [beyond version 7](https://github.com/cowboyd/libv8/rele ### HMR Hot Module Replacement is [possible with this gem](https://stackoverflow.com/a/54846330/193785) as it does just pass through to Webpacker. Please open an issue to let us know tips and tricks for it to add to the wiki. -Sample repo that shows HMR working with `react-rails`: [https://github.com/edelgado/react-rails-hmr](https://github.com/edelgado/react-rails-hmr) +Similar to `useContext`, you can pass the webpack context into `useHotReload` to enable hot reload: +```js +var myCustomContext = require.context("custom_components", true) +var ReactRailsUJS = require("react_ujs") +// use `custom_components/` for <%= react_component(...) %> calls +ReactRailsUJS.useHotReload(myCustomContext) +``` + +You must ensure +1. [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) are installed; +2. your webpack config has the following in dev: +```js +{ + module: { + rules: [ + { + test: /\.(jsx|tsx)?$/, + use: ["react-hot-loader/webpack"], + }, + ], + }, + resolve: { + alias: { + "react-dom": "@hot-loader/react-dom", + }, + }, +} +``` One caveat is that currently you [cannot Server-Side Render along with HMR](https://github.com/reactjs/react-rails/issues/925#issuecomment-415469572). diff --git a/react_ujs/index.js b/react_ujs/index.js index 0ef70a5a3..12065efc1 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -7,6 +7,7 @@ var constructorFromGlobal = require("./src/getConstructor/fromGlobal") var constructorFromRequireContextWithGlobalFallback = require("./src/getConstructor/fromRequireContextWithGlobalFallback") var renderWithReactDOM = require("./src/renderComponent/withReactDOM") +var renderWithHotReload = require("./src/renderComponent/withHotReload") var ReactRailsUJS = { // This attribute holds the name of component which should be mounted @@ -84,9 +85,33 @@ var ReactRailsUJS = { // Render `component` using the specified `renderFunction` from `react-dom`. // Override this function to render components in a custom way. - // function signature: ("hydrate" | "render", component, node, props) renderComponent: renderWithReactDOM, + // Enables hot reload for component rendering. + // + // Ensure + // 1. [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) are installed; + // 2. your webpack config has the following in dev: + // { + // module: { + // rules: [ + // { + // test: /\.(jsx|tsx)?$/, + // use: ["react-hot-loader/webpack"], + // }, + // ], + // }, + // resolve: { + // alias: { + // "react-dom": "@hot-loader/react-dom", + // }, + // }, + // } + // + useHotReload: function(requireContext) { + this.renderComponent = renderWithHotReload(requireContext) + }, + // Within `searchSelector`, find nodes which should have React components // inside them, and mount them with their props. mountComponents: function(searchSelector) { diff --git a/react_ujs/src/renderComponent/withHotReload.js b/react_ujs/src/renderComponent/withHotReload.js new file mode 100644 index 000000000..4dfebe433 --- /dev/null +++ b/react_ujs/src/renderComponent/withHotReload.js @@ -0,0 +1,54 @@ +var ReactDOM = require("react-dom") +var reactHotLoader = require("react-hot-loader") +var AppContainer = reactHotLoader.AppContainer; + +// Render React component with hot reload. +// +// Ensure +// 1. [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) are installed; +// 2. your webpack config has the following in dev: +// { +// module: { +// rules: [ +// { +// test: /\.(jsx|tsx)?$/, +// use: ["react-hot-loader/webpack"], +// }, +// ], +// }, +// resolve: { +// alias: { +// "react-dom": "@hot-loader/react-dom", +// }, +// }, +// } +// +module.exports = function(reqctx) { + return function(renderFunctionName, component, node, props) { + var className = node.getAttribute(ReactRailsUJS.CLASS_NAME_ATTR); + var filename = getFileNameFromClassName(className); + var path = reqctx.resolve("./" + filename); + var cache = require.cache; + var module = cache[path]; + var moduleParent = module && cache[module.parents[0]]; + if (!moduleParent || !moduleParent.hot) { + console.warn(`Cannot hot reload for ${path}. Ensure webpack-dev-server is started with --hot and WEBPACKER_DEV_SERVER_HMR=true`); + return; + } + moduleParent.hot.accept(path, () => { + var FreshConstructor = ReactRailsUJS.getConstructor(className); + var FreshComponent = React.createElement(FreshConstructor, props); + + ReactDOM[renderFunctionName](React.createElement(AppContainer, null, FreshComponent), node); + }); + + ReactDOM[renderFunctionName](React.createElement(AppContainer, null, component), node); + }; +} + +function getFileNameFromClassName(className) { + var parts = className.split("."); + var filename = parts.shift(); + + return filename; +} From 210f835ed37d6cfd8d12c28719755f205de2308a Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Sat, 25 Apr 2020 17:50:03 -0600 Subject: [PATCH 08/20] add back fn signature --- react_ujs/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/react_ujs/index.js b/react_ujs/index.js index 12065efc1..5b4145b19 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -85,6 +85,7 @@ var ReactRailsUJS = { // Render `component` using the specified `renderFunction` from `react-dom`. // Override this function to render components in a custom way. + // function signature: ("hydrate" | "render", component, node, props) renderComponent: renderWithReactDOM, // Enables hot reload for component rendering. From 6537709aebaaa904a640b511e08d3dcd8524c4e2 Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Sat, 25 Apr 2020 18:26:09 -0600 Subject: [PATCH 09/20] Update README.md --- README.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a962e1139..23ca61908 100644 --- a/README.md +++ b/README.md @@ -684,16 +684,8 @@ LibV8 itself is already [beyond version 7](https://github.com/cowboyd/libv8/rele Hot Module Replacement is [possible with this gem](https://stackoverflow.com/a/54846330/193785) as it does just pass through to Webpacker. Please open an issue to let us know tips and tricks for it to add to the wiki. Similar to `useContext`, you can pass the webpack context into `useHotReload` to enable hot reload: -```js -var myCustomContext = require.context("custom_components", true) -var ReactRailsUJS = require("react_ujs") -// use `custom_components/` for <%= react_component(...) %> calls -ReactRailsUJS.useHotReload(myCustomContext) -``` - -You must ensure -1. [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) are installed; -2. your webpack config has the following in dev: +1. install [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) +1. add the following to your webpack config in dev: ```js { module: { @@ -711,6 +703,12 @@ You must ensure }, } ``` +1. in your entry file, usually where you call `ReactRailsUJS.useContext` already, call `useHotReload`: +```js +var ReactRailsUJS = require("react_ujs") +var myCustomContext = require.context("custom_components", true) +ReactRailsUJS.useHotReload(myCustomContext) +``` One caveat is that currently you [cannot Server-Side Render along with HMR](https://github.com/reactjs/react-rails/issues/925#issuecomment-415469572). From 5c4cf924dcf051ce04034df7b2b1135e93cb7f5d Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Sat, 25 Apr 2020 18:26:57 -0600 Subject: [PATCH 10/20] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 23ca61908..8bcb843df 100644 --- a/README.md +++ b/README.md @@ -685,7 +685,7 @@ Hot Module Replacement is [possible with this gem](https://stackoverflow.com/a/5 Similar to `useContext`, you can pass the webpack context into `useHotReload` to enable hot reload: 1. install [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) -1. add the following to your webpack config in dev: +2. add the following to your webpack config in dev: ```js { module: { @@ -703,7 +703,7 @@ Similar to `useContext`, you can pass the webpack context into `useHotReload` to }, } ``` -1. in your entry file, usually where you call `ReactRailsUJS.useContext` already, call `useHotReload`: +3. in your entry file, usually where you call `ReactRailsUJS.useContext` already, call `useHotReload`: ```js var ReactRailsUJS = require("react_ujs") var myCustomContext = require.context("custom_components", true) From 4a7113896669440802a7fb262181f5a3c67936ce Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Sat, 25 Apr 2020 18:32:10 -0600 Subject: [PATCH 11/20] Update index.js --- react_ujs/index.js | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/react_ujs/index.js b/react_ujs/index.js index 5b4145b19..40ad8508e 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -90,25 +90,7 @@ var ReactRailsUJS = { // Enables hot reload for component rendering. // - // Ensure - // 1. [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) are installed; - // 2. your webpack config has the following in dev: - // { - // module: { - // rules: [ - // { - // test: /\.(jsx|tsx)?$/, - // use: ["react-hot-loader/webpack"], - // }, - // ], - // }, - // resolve: { - // alias: { - // "react-dom": "@hot-loader/react-dom", - // }, - // }, - // } - // + // See the HMR section in README to ensure required steps are completed. useHotReload: function(requireContext) { this.renderComponent = renderWithHotReload(requireContext) }, From bff3b073d9d463d93f03711c04841b3da7839fdd Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Sat, 25 Apr 2020 18:34:00 -0600 Subject: [PATCH 12/20] Update withHotReload.js --- .../src/renderComponent/withHotReload.js | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/react_ujs/src/renderComponent/withHotReload.js b/react_ujs/src/renderComponent/withHotReload.js index 4dfebe433..eee22d113 100644 --- a/react_ujs/src/renderComponent/withHotReload.js +++ b/react_ujs/src/renderComponent/withHotReload.js @@ -4,25 +4,7 @@ var AppContainer = reactHotLoader.AppContainer; // Render React component with hot reload. // -// Ensure -// 1. [react-hot-loader](https://github.com/gaearon/react-hot-loader) and [@hot-loader/react-dom](https://github.com/hot-loader/react-dom) are installed; -// 2. your webpack config has the following in dev: -// { -// module: { -// rules: [ -// { -// test: /\.(jsx|tsx)?$/, -// use: ["react-hot-loader/webpack"], -// }, -// ], -// }, -// resolve: { -// alias: { -// "react-dom": "@hot-loader/react-dom", -// }, -// }, -// } -// +// See the HMR section in README to ensure required steps are completed. module.exports = function(reqctx) { return function(renderFunctionName, component, node, props) { var className = node.getAttribute(ReactRailsUJS.CLASS_NAME_ATTR); From dbc43fd72b01b020657411c6be3290f42e8acaf7 Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Sat, 25 Apr 2020 18:35:05 -0600 Subject: [PATCH 13/20] Update withHotReload.js --- react_ujs/src/renderComponent/withHotReload.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react_ujs/src/renderComponent/withHotReload.js b/react_ujs/src/renderComponent/withHotReload.js index eee22d113..54ca438e2 100644 --- a/react_ujs/src/renderComponent/withHotReload.js +++ b/react_ujs/src/renderComponent/withHotReload.js @@ -5,11 +5,11 @@ var AppContainer = reactHotLoader.AppContainer; // Render React component with hot reload. // // See the HMR section in README to ensure required steps are completed. -module.exports = function(reqctx) { +module.exports = function(webpackRequireContext) { return function(renderFunctionName, component, node, props) { var className = node.getAttribute(ReactRailsUJS.CLASS_NAME_ATTR); var filename = getFileNameFromClassName(className); - var path = reqctx.resolve("./" + filename); + var path = webpackRequireContext.resolve("./" + filename); var cache = require.cache; var module = cache[path]; var moduleParent = module && cache[module.parents[0]]; From 01eb0f05857c440e64dc4d55eddd791d11d2c1cf Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Tue, 28 Apr 2020 21:11:33 -0600 Subject: [PATCH 14/20] reload multiple elements of same component --- react_ujs/src/renderComponent/withHotReload.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/react_ujs/src/renderComponent/withHotReload.js b/react_ujs/src/renderComponent/withHotReload.js index 54ca438e2..ed4c5e6fb 100644 --- a/react_ujs/src/renderComponent/withHotReload.js +++ b/react_ujs/src/renderComponent/withHotReload.js @@ -21,7 +21,11 @@ module.exports = function(webpackRequireContext) { var FreshConstructor = ReactRailsUJS.getConstructor(className); var FreshComponent = React.createElement(FreshConstructor, props); - ReactDOM[renderFunctionName](React.createElement(AppContainer, null, FreshComponent), node); + var nodes = findAllReactNodes(className); + for (var i = 0; i < nodes.length; ++i) { + var reactNode = nodes[i]; + ReactDOM[renderFunctionName](React.createElement(AppContainer, null, FreshComponent), reactNode); + } }); ReactDOM[renderFunctionName](React.createElement(AppContainer, null, component), node); @@ -34,3 +38,12 @@ function getFileNameFromClassName(className) { return filename; } + +function findAllReactNodes(className) { + var selector = '[' + ReactRailsUJS.CLASS_NAME_ATTR + '="' + className + '"]'; + if (ReactRailsUJS.jQuery) { + return ReactRailsUJS.jQuery(selector, document); + } else { + return parent.querySelectorAll(selector); + } +} From 1741e737b5c8b67c8c19b1c52d99455ac980bdf2 Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Tue, 28 Apr 2020 22:38:08 -0600 Subject: [PATCH 15/20] render correct props --- react_ujs/src/renderComponent/withHotReload.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/react_ujs/src/renderComponent/withHotReload.js b/react_ujs/src/renderComponent/withHotReload.js index ed4c5e6fb..28d4b942b 100644 --- a/react_ujs/src/renderComponent/withHotReload.js +++ b/react_ujs/src/renderComponent/withHotReload.js @@ -19,11 +19,13 @@ module.exports = function(webpackRequireContext) { } moduleParent.hot.accept(path, () => { var FreshConstructor = ReactRailsUJS.getConstructor(className); - var FreshComponent = React.createElement(FreshConstructor, props); var nodes = findAllReactNodes(className); for (var i = 0; i < nodes.length; ++i) { var reactNode = nodes[i]; + var propsJson = reactNode.getAttribute(ujs.PROPS_ATTR); + var props = propsJson && JSON.parse(propsJson); + var FreshComponent = React.createElement(FreshConstructor, props); ReactDOM[renderFunctionName](React.createElement(AppContainer, null, FreshComponent), reactNode); } }); From 02f72174efc803001b9ff66175959486ca973bf8 Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Tue, 28 Apr 2020 22:49:42 -0600 Subject: [PATCH 16/20] refactor re-render --- .../src/renderComponent/withHotReload.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/react_ujs/src/renderComponent/withHotReload.js b/react_ujs/src/renderComponent/withHotReload.js index 28d4b942b..ede11c758 100644 --- a/react_ujs/src/renderComponent/withHotReload.js +++ b/react_ujs/src/renderComponent/withHotReload.js @@ -17,18 +17,7 @@ module.exports = function(webpackRequireContext) { console.warn(`Cannot hot reload for ${path}. Ensure webpack-dev-server is started with --hot and WEBPACKER_DEV_SERVER_HMR=true`); return; } - moduleParent.hot.accept(path, () => { - var FreshConstructor = ReactRailsUJS.getConstructor(className); - - var nodes = findAllReactNodes(className); - for (var i = 0; i < nodes.length; ++i) { - var reactNode = nodes[i]; - var propsJson = reactNode.getAttribute(ujs.PROPS_ATTR); - var props = propsJson && JSON.parse(propsJson); - var FreshComponent = React.createElement(FreshConstructor, props); - ReactDOM[renderFunctionName](React.createElement(AppContainer, null, FreshComponent), reactNode); - } - }); + moduleParent.hot.accept(path, () => reRenderAllNodes(className, renderFunctionName)); ReactDOM[renderFunctionName](React.createElement(AppContainer, null, component), node); }; @@ -41,6 +30,17 @@ function getFileNameFromClassName(className) { return filename; } +function reRenderAllNodes(className, renderFunctionName) { + var nodes = findAllReactNodes(className); + for (var i = 0; i < nodes.length; ++i) { + var node = nodes[i]; + var propsJson = node.getAttribute(ujs.PROPS_ATTR); + var props = propsJson && JSON.parse(propsJson); + var FreshComponent = React.createElement(FreshConstructor, props); + ReactDOM[renderFunctionName](React.createElement(AppContainer, null, FreshComponent), node); + } +} + function findAllReactNodes(className) { var selector = '[' + ReactRailsUJS.CLASS_NAME_ATTR + '="' + className + '"]'; if (ReactRailsUJS.jQuery) { From 087211e0e3c63f3512ba8dd841d6e1e36740aa2d Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Wed, 29 Apr 2020 00:09:52 -0600 Subject: [PATCH 17/20] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 8bcb843df..7ec16cd0f 100644 --- a/README.md +++ b/README.md @@ -709,6 +709,12 @@ var ReactRailsUJS = require("react_ujs") var myCustomContext = require.context("custom_components", true) ReactRailsUJS.useHotReload(myCustomContext) ``` +4. optionally, for CSS to hot reload, update the following for dev in `webpacker.yml`: +```yml +development: + <<: *default + extract_css: false +``` One caveat is that currently you [cannot Server-Side Render along with HMR](https://github.com/reactjs/react-rails/issues/925#issuecomment-415469572). From 6963eaffedb1a04ddd36c8b0f8fbff6c4138eb2a Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Wed, 29 Apr 2020 10:05:55 -0600 Subject: [PATCH 18/20] only re-render mounted components --- react_ujs/src/renderComponent/withHotReload.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/react_ujs/src/renderComponent/withHotReload.js b/react_ujs/src/renderComponent/withHotReload.js index ede11c758..0a082ba4b 100644 --- a/react_ujs/src/renderComponent/withHotReload.js +++ b/react_ujs/src/renderComponent/withHotReload.js @@ -34,6 +34,8 @@ function reRenderAllNodes(className, renderFunctionName) { var nodes = findAllReactNodes(className); for (var i = 0; i < nodes.length; ++i) { var node = nodes[i]; + if (!isReactMountedAtNode(node)) continue; + var propsJson = node.getAttribute(ujs.PROPS_ATTR); var props = propsJson && JSON.parse(propsJson); var FreshComponent = React.createElement(FreshConstructor, props); @@ -49,3 +51,7 @@ function findAllReactNodes(className) { return parent.querySelectorAll(selector); } } + +function isReactMountedAtNode(node) { + return node.hasChildNodes(); +} From fffc5e8511b048fc6fcafeb48ac11ccb12bf0cd2 Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Wed, 29 Apr 2020 16:08:34 -0600 Subject: [PATCH 19/20] Update index.js --- react_ujs/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/react_ujs/index.js b/react_ujs/index.js index 0ef70a5a3..84e9665ea 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -73,6 +73,11 @@ var ReactRailsUJS = { useContext: function(requireContext) { this.getConstructor = constructorFromRequireContextWithGlobalFallback(requireContext) }, + + // Called after React unmounts component at `node` + // Override this function to perform any cleanup + // the default function does nothing + onComponentUnmountAtNode: function (node) {}, // Render `componentName` with `props` to a string, // using the specified `renderFunction` from `react-dom/server`. @@ -135,6 +140,7 @@ var ReactRailsUJS = { for (var i = 0; i < nodes.length; ++i) { var node = nodes[i]; ReactDOM.unmountComponentAtNode(node); + ReactRailsUJS.onComponentUnmountAtNode(node); } }, From 5d06769f130f70f0f20bab1159af1c8d620c837c Mon Sep 17 00:00:00 2001 From: Edmond Chui Date: Wed, 29 Apr 2020 16:16:30 -0600 Subject: [PATCH 20/20] support re-rendering portal elements --- react_ujs/src/renderComponent/withHotReload.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/react_ujs/src/renderComponent/withHotReload.js b/react_ujs/src/renderComponent/withHotReload.js index 0a082ba4b..ed38edaaf 100644 --- a/react_ujs/src/renderComponent/withHotReload.js +++ b/react_ujs/src/renderComponent/withHotReload.js @@ -2,10 +2,16 @@ var ReactDOM = require("react-dom") var reactHotLoader = require("react-hot-loader") var AppContainer = reactHotLoader.AppContainer; +var IS_MOUNTED_ATTR = "data-react-is-mounted"; + // Render React component with hot reload. // // See the HMR section in README to ensure required steps are completed. module.exports = function(webpackRequireContext) { + ReactRailsUJS.onComponentUnmountAtNode = function(node) { + node.setAttribute(IS_MOUNTED_ATTR, "false"); + } + return function(renderFunctionName, component, node, props) { var className = node.getAttribute(ReactRailsUJS.CLASS_NAME_ATTR); var filename = getFileNameFromClassName(className); @@ -20,6 +26,7 @@ module.exports = function(webpackRequireContext) { moduleParent.hot.accept(path, () => reRenderAllNodes(className, renderFunctionName)); ReactDOM[renderFunctionName](React.createElement(AppContainer, null, component), node); + node.setAttribute(IS_MOUNTED_ATTR, "true"); }; } @@ -53,5 +60,5 @@ function findAllReactNodes(className) { } function isReactMountedAtNode(node) { - return node.hasChildNodes(); + return node.matches('[' + IS_MOUNTED_ATTR + '="true"]'); }