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"