diff --git a/app/javascript/components/Loadable.jsx b/app/javascript/components/Loadable.jsx deleted file mode 100644 index 13d57e2e1..000000000 --- a/app/javascript/components/Loadable.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React, { Suspense, lazy } from 'react'; -import PropTypes from 'prop-types'; - -const Loader = () =>
; - -function LazyLoad({ component: Component, ...props }) { - return ( - }> - - - ); -} - -LazyLoad.propTypes = { - component: PropTypes.object -}; - -export default function Loadable(loader) { - const LazyComponent = lazy(loader); - - return function PureComponent(props) { - return ; - }; -} diff --git a/app/javascript/loaders/Chartkick.js b/app/javascript/loaders/Chartkick.js deleted file mode 100644 index 0be342493..000000000 --- a/app/javascript/loaders/Chartkick.js +++ /dev/null @@ -1,3 +0,0 @@ -import Loadable from '../components/Loadable'; - -export default Loadable(() => import('../components/Chartkick')); diff --git a/app/javascript/loaders/ComboAdresseSearch.js b/app/javascript/loaders/ComboAdresseSearch.js deleted file mode 100644 index d37c73f31..000000000 --- a/app/javascript/loaders/ComboAdresseSearch.js +++ /dev/null @@ -1,3 +0,0 @@ -import Loadable from '../components/Loadable'; - -export default Loadable(() => import('../components/ComboAdresseSearch')); diff --git a/app/javascript/loaders/ComboAnnuaireEducationSearch.js b/app/javascript/loaders/ComboAnnuaireEducationSearch.js deleted file mode 100644 index c1f80a0dc..000000000 --- a/app/javascript/loaders/ComboAnnuaireEducationSearch.js +++ /dev/null @@ -1,5 +0,0 @@ -import Loadable from '../components/Loadable'; - -export default Loadable(() => - import('../components/ComboAnnuaireEducationSearch') -); diff --git a/app/javascript/loaders/ComboCommunesSearch.js b/app/javascript/loaders/ComboCommunesSearch.js deleted file mode 100644 index 7689f27f7..000000000 --- a/app/javascript/loaders/ComboCommunesSearch.js +++ /dev/null @@ -1,3 +0,0 @@ -import Loadable from '../components/Loadable'; - -export default Loadable(() => import('../components/ComboCommunesSearch')); diff --git a/app/javascript/loaders/ComboDepartementsSearch.js b/app/javascript/loaders/ComboDepartementsSearch.js deleted file mode 100644 index d19d5570a..000000000 --- a/app/javascript/loaders/ComboDepartementsSearch.js +++ /dev/null @@ -1,3 +0,0 @@ -import Loadable from '../components/Loadable'; - -export default Loadable(() => import('../components/ComboDepartementsSearch')); diff --git a/app/javascript/loaders/ComboMultipleDropdownList.js b/app/javascript/loaders/ComboMultipleDropdownList.js deleted file mode 100644 index db778b339..000000000 --- a/app/javascript/loaders/ComboMultipleDropdownList.js +++ /dev/null @@ -1,5 +0,0 @@ -import Loadable from '../components/Loadable'; - -export default Loadable(() => - import('../components/ComboMultipleDropdownList') -); diff --git a/app/javascript/loaders/ComboPaysSearch.js b/app/javascript/loaders/ComboPaysSearch.js deleted file mode 100644 index e4e6ed33d..000000000 --- a/app/javascript/loaders/ComboPaysSearch.js +++ /dev/null @@ -1,3 +0,0 @@ -import Loadable from '../components/Loadable'; - -export default Loadable(() => import('../components/ComboPaysSearch')); diff --git a/app/javascript/loaders/ComboRegionsSearch.js b/app/javascript/loaders/ComboRegionsSearch.js deleted file mode 100644 index 4b281012d..000000000 --- a/app/javascript/loaders/ComboRegionsSearch.js +++ /dev/null @@ -1,3 +0,0 @@ -import Loadable from '../components/Loadable'; - -export default Loadable(() => import('../components/ComboRegionsSearch')); diff --git a/app/javascript/loaders/MapEditor.js b/app/javascript/loaders/MapEditor.js deleted file mode 100644 index 4ab3246eb..000000000 --- a/app/javascript/loaders/MapEditor.js +++ /dev/null @@ -1,3 +0,0 @@ -import Loadable from '../components/Loadable'; - -export default Loadable(() => import('../components/MapEditor')); diff --git a/app/javascript/loaders/MapReader.js b/app/javascript/loaders/MapReader.js deleted file mode 100644 index adb90193f..000000000 --- a/app/javascript/loaders/MapReader.js +++ /dev/null @@ -1,3 +0,0 @@ -import Loadable from '../components/Loadable'; - -export default Loadable(() => import('../components/MapReader')); diff --git a/app/javascript/loaders/Trix.js b/app/javascript/loaders/Trix.js deleted file mode 100644 index 1f62cdbe2..000000000 --- a/app/javascript/loaders/Trix.js +++ /dev/null @@ -1,3 +0,0 @@ -import Loadable from '../components/Loadable'; - -export default Loadable(() => import('../components/Trix')); diff --git a/app/javascript/loaders/TypesDeChampEditor.js b/app/javascript/loaders/TypesDeChampEditor.js deleted file mode 100644 index 8862e5ae9..000000000 --- a/app/javascript/loaders/TypesDeChampEditor.js +++ /dev/null @@ -1,3 +0,0 @@ -import Loadable from '../components/Loadable'; - -export default Loadable(() => import('../components/TypesDeChampEditor')); diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index f6a7fb267..f0cc6ec15 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -2,7 +2,6 @@ import '../shared/polyfills'; import Rails from '@rails/ujs'; import * as ActiveStorage from '@rails/activestorage'; import 'whatwg-fetch'; // window.fetch polyfill -import ReactRailsUJS from 'react_ujs'; import '../shared/page-update-event'; import '../shared/activestorage/ujs'; @@ -48,6 +47,38 @@ import { showNewAccountPasswordConfirmation } from '../new_design/fc-fusion'; +import { + registerReactComponents, + Loadable +} from '../shared/register-react-components'; + +registerReactComponents({ + Chartkick: Loadable(() => import('../components/Chartkick')), + ComboAdresseSearch: Loadable(() => + import('../components/ComboAdresseSearch') + ), + ComboAnnuaireEducationSearch: Loadable(() => + import('../components/ComboAnnuaireEducationSearch') + ), + ComboCommunesSearch: Loadable(() => + import('../components/ComboCommunesSearch') + ), + ComboDepartementsSearch: Loadable(() => + import('../components/ComboDepartementsSearch') + ), + ComboMultipleDropdownList: Loadable(() => + import('../components/ComboMultipleDropdownList') + ), + ComboPaysSearch: Loadable(() => import('../components/ComboPaysSearch')), + ComboRegionsSearch: Loadable(() => + import('../components/ComboRegionsSearch') + ), + MapEditor: Loadable(() => import('../components/MapEditor')), + MapReader: Loadable(() => import('../components/MapReader')), + Trix: Loadable(() => import('../components/Trix')), + TypesDeChampEditor: Loadable(() => import('../components/TypesDeChampEditor')) +}); + // This is the global application namespace where we expose helpers used from rails views const DS = { fire: (eventName, data) => Rails.fire(document, eventName, data), @@ -69,7 +100,3 @@ ActiveStorage.start(); // Expose globals window.DS = window.DS || DS; - -// eslint-disable-next-line no-undef, react-hooks/rules-of-hooks -ReactRailsUJS.useContext(require.context('loaders', true)); -addEventListener('ds:page:update', ReactRailsUJS.handleMount); diff --git a/app/javascript/shared/register-react-components.jsx b/app/javascript/shared/register-react-components.jsx new file mode 100644 index 000000000..d7d0c9e19 --- /dev/null +++ b/app/javascript/shared/register-react-components.jsx @@ -0,0 +1,108 @@ +import React, { Suspense, lazy, createElement } from 'react'; +import { render } from 'react-dom'; + +// This attribute holds the name of component which should be mounted +// example: `data-react-class="MyApp.Items.EditForm"` +const CLASS_NAME_ATTR = 'data-react-class'; + +// This attribute holds JSON stringified props for initializing the component +// example: `data-react-props="{\"item\": { \"id\": 1, \"name\": \"My Item\"} }"` +const PROPS_ATTR = 'data-react-props'; + +// A unique identifier to identify a node +const CACHE_ID_ATTR = 'data-react-cache-id'; + +const CLASS_NAME_SELECTOR = `[${CLASS_NAME_ATTR}]`; + +// helper method for the mount and unmount methods to find the +// `data-react-class` DOM elements +function findDOMNodes(searchSelector) { + const [selector, parent] = getSelector(searchSelector); + return parent.querySelectorAll(selector); +} + +function getSelector(searchSelector) { + switch (typeof searchSelector) { + case 'undefined': + return [CLASS_NAME_SELECTOR, document]; + case 'object': + return [CLASS_NAME_SELECTOR, searchSelector]; + case 'string': + return [ + ['', ' '] + .map( + (separator) => `${searchSelector}${separator}${CLASS_NAME_SELECTOR}` + ) + .join(', '), + document + ]; + } +} + +class ReactComponentRegistry { + #cache = {}; + #components; + + constructor(components) { + this.#components = components; + } + + getConstructor(className) { + return this.#components[className]; + } + + mountComponents(searchSelector) { + const nodes = findDOMNodes(searchSelector); + + for (const node of nodes) { + const className = node.getAttribute(CLASS_NAME_ATTR); + const ComponentClass = this.getConstructor(className); + const propsJson = node.getAttribute(PROPS_ATTR); + const props = propsJson && JSON.parse(propsJson); + const cacheId = node.getAttribute(CACHE_ID_ATTR); + + if (!ComponentClass) { + const message = `Cannot find component: "${className}"`; + console?.log( + `%c[react-rails] %c${message} for element`, + 'font-weight: bold', + '', + node + ); + throw new Error( + `${message}. Make sure your component is available to render.` + ); + } else { + let component = this.#cache[cacheId]; + if (!component) { + this.#cache[cacheId] = component = createElement( + ComponentClass, + props + ); + } + + render(component, node); + } + } + } +} + +const Loader = () =>
; + +export function Loadable(loader) { + const LazyComponent = lazy(loader); + + return function PureComponent(props) { + return ( + }> + + + ); + }; +} + +export function registerReactComponents(components) { + const registry = new ReactComponentRegistry(components); + + addEventListener('ds:page:update', () => registry.mountComponents()); +} diff --git a/package.json b/package.json index cf73fc61d..4cde890fa 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "react-popper": "^2.2.5", "react-query": "^3.9.7", "react-sortable-hoc": "^1.11.0", - "react_ujs": "^2.6.1", "trix": "^1.2.3", "use-debounce": "^5.2.0", "webpack": "^4.46.0", diff --git a/yarn.lock b/yarn.lock index db905cc7f..74f1b3856 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12394,13 +12394,6 @@ react@^17.0.1: loose-envify "^1.1.0" object-assign "^4.1.1" -react_ujs@^2.6.0, react_ujs@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/react_ujs/-/react_ujs-2.6.1.tgz#a202a33c95c9e2bb18ab56926b7e79f3325ec855" - integrity sha512-9M33/A8cubStkZ2cpJSimcTD0RlCWiqXF6e90IQmMw/Caf/W0dtAzOtHtiQE3JjLbt/nhRR7NLPxMfnlm141ig== - dependencies: - react_ujs "^2.6.0" - read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"