diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c128e6c..ddfb19ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ If you need help upgrading `react-rails`, `webpacker` to `shakapacker`, or JS pa Changes since last non-beta release. _Please add entries here for your pull requests that are not yet released._ +- Add option to replace `null`s in props with `undefined` via `ReactRailsUJS.setOptions` #1273 ### Breaking Changes - Remove support & testing for Webpacker 3/4. diff --git a/README.md b/README.md index 6941d39d..1041c563 100644 --- a/README.md +++ b/README.md @@ -528,6 +528,19 @@ use it like so: ReactUJS.getConstructor = ReactUJS.constructorFromRequireContext(require.context('components', true)); ``` +### Configure UJS + +You can change the behaviours of `ReactRailsUJS` by calling `setOptions(options)` function: + +```js +ReactRailsUJS.setOptions({ replaceNull: false }); +``` + +Current acceptable options are the following: + +| Key | Value type | Description | Default | +| --- | ---------- | ----------- | ------- | +| `replaceNull` | boolean (`true` / `false`) | Whether to replace all `null`s in the props from Rails (originally `nil` in Rails) with `undefined`. May be helpful when defining the types of the props in TypeScript. See [discussion#1272](https://github.com/reactjs/react-rails/discussions/1272). | `false`| ## Server-Side Rendering diff --git a/react_ujs/index.js b/react_ujs/index.js index 6b8effc8..484cc98b 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -8,6 +8,7 @@ var constructorFromRequireContext = require("./src/getConstructor/fromRequireCon var constructorFromRequireContextWithGlobalFallback = require("./src/getConstructor/fromRequireContextWithGlobalFallback") var constructorFromRequireContextsWithGlobalFallback = require("./src/getConstructor/fromRequireContextsWithGlobalFallback") const { supportsHydration, reactHydrate, createReactRootLike } = require("./src/renderHelpers") +const { replaceNullWithUndefined, overwriteOption } = require("./src/options") var ReactRailsUJS = { // This attribute holds the name of component which should be mounted @@ -31,6 +32,15 @@ var ReactRailsUJS = { components: {}, + // Set default values for options. + options: { + replaceNull: false, + }, + + setOptions: function(newOptions) { + overwriteOption(ReactRailsUJS.options, newOptions, "replaceNull") + }, + // helper method for the mount and unmount methods to find the // `data-react-class` DOM elements findDOMNodes: function(searchSelector) { @@ -106,7 +116,8 @@ var ReactRailsUJS = { var className = node.getAttribute(ujs.CLASS_NAME_ATTR); var constructor = ujs.getConstructor(className); var propsJson = node.getAttribute(ujs.PROPS_ATTR); - var props = propsJson && JSON.parse(propsJson); + var props = propsJson && (ujs.options.replaceNull ? replaceNullWithUndefined(JSON.parse(propsJson)) + : JSON.parse(propsJson)); var hydrate = node.getAttribute(ujs.RENDER_ATTR); var cacheId = node.getAttribute(ujs.CACHE_ID_ATTR); var turbolinksPermanent = node.hasAttribute(ujs.TURBOLINKS_PERMANENT_ATTR); diff --git a/react_ujs/src/options.js b/react_ujs/src/options.js new file mode 100644 index 00000000..4153ad1f --- /dev/null +++ b/react_ujs/src/options.js @@ -0,0 +1,18 @@ +export function replaceNullWithUndefined(obj) { + Object.entries(obj).forEach((entry) => { + const key = entry[0] + const value = entry[1] + if(!!value && typeof value === 'object') { + return replaceNullWithUndefined(value) + } + if (value === null) { + obj[key] = undefined + } + }) + return obj +} + +export function overwriteOption(ujsOptions, newOptions, key) { + if (!Object.prototype.hasOwnProperty.call(newOptions, key)) return + ujsOptions[key] = newOptions[key] +}