diff --git a/pkg/webui/components/key-value-map/entry.js b/pkg/webui/components/key-value-map/entry.js
index 6fe2f4e78b..d19190222b 100644
--- a/pkg/webui/components/key-value-map/entry.js
+++ b/pkg/webui/components/key-value-map/entry.js
@@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import React from 'react'
-import bind from 'autobind-decorator'
+import React, { useCallback } from 'react'
import { defineMessages } from 'react-intl'
import classnames from 'classnames'
@@ -27,117 +26,104 @@ const m = defineMessages({
deleteEntry: 'Delete entry',
})
-class Entry extends React.Component {
- static propTypes = {
- readOnly: PropTypes.bool,
- }
- static defaultProps = { readOnly: false }
-
- _getKeyInputName() {
- const { name, index } = this.props
-
- return `${name}[${index}].key`
- }
-
- _getValueInputName() {
- const { name, index } = this.props
-
- return `${name}[${index}].value`
- }
-
- @bind
- handleRemoveButtonClicked(event) {
- const { onRemoveButtonClick, index } = this.props
-
- onRemoveButtonClick(index, event)
- }
-
- @bind
- handleKeyChanged(newKey) {
- const { onChange, index } = this.props
-
- onChange(index, { key: newKey })
- }
-
- @bind
- handleValueChanged(newValue) {
- const { onChange, index } = this.props
-
- onChange(index, { value: newValue })
- }
-
- @bind
- handleBlur(event) {
- const { name, onBlur, value } = this.props
-
- const { relatedTarget } = event
- const nextTarget = relatedTarget || {}
-
- if (
- nextTarget.name !== this._getKeyInputName() &&
- nextTarget.name !== this._getValueInputName()
- ) {
- onBlur({
- target: {
- name,
- value,
- },
- })
- }
- }
-
- render() {
- const {
- keyPlaceholder,
- valuePlaceholder,
- value,
- indexAsKey,
- readOnly,
- inputElement: InputElement,
- additionalInputProps,
- } = this.props
-
- return (
-
- {!indexAsKey && (
-
- )}
+const Entry = ({
+ readOnly,
+ name,
+ value,
+ index,
+ onRemoveButtonClick,
+ onChange,
+ onBlur,
+ inputElement,
+ indexAsKey,
+ valuePlaceholder,
+ keyPlaceholder,
+ additionalInputProps,
+}) => {
+ const _getKeyInputName = useCallback(() => `${name}[${index}].key`, [index, name])
+
+ const _getValueInputName = useCallback(() => `${name}[${index}].value`, [index, name])
+
+ const handleRemoveButtonClicked = useCallback(
+ event => {
+ onRemoveButtonClick(index, event)
+ },
+ [index, onRemoveButtonClick],
+ )
+
+ const handleKeyChanged = useCallback(
+ newKey => {
+ onChange(index, { key: newKey })
+ },
+ [index, onChange],
+ )
+
+ const handleValueChanged = useCallback(
+ newValue => {
+ onChange(index, { value: newValue })
+ },
+ [index, onChange],
+ )
+
+ const handleBlur = useCallback(
+ event => {
+ const { relatedTarget } = event
+ const nextTarget = relatedTarget || {}
+
+ if (nextTarget.name !== _getKeyInputName() && nextTarget.name !== _getValueInputName()) {
+ onBlur({
+ target: {
+ name,
+ value,
+ },
+ })
+ }
+ },
+ [onBlur, name, value, _getKeyInputName, _getValueInputName],
+ )
+
+ const { InputElement } = inputElement
+
+ return (
+
+ {!indexAsKey && (
-
-
- )
- }
+ )}
+
+
+
+ )
}
Entry.propTypes = {
@@ -150,6 +136,7 @@ Entry.propTypes = {
onBlur: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onRemoveButtonClick: PropTypes.func.isRequired,
+ readOnly: PropTypes.bool,
value: PropTypes.oneOfType([
PropTypes.shape({
key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
@@ -162,6 +149,7 @@ Entry.propTypes = {
Entry.defaultProps = {
value: undefined,
+ readOnly: false,
}
export default Entry
diff --git a/pkg/webui/components/key-value-map/index.js b/pkg/webui/components/key-value-map/index.js
index 7c1b4744df..42900f0730 100644
--- a/pkg/webui/components/key-value-map/index.js
+++ b/pkg/webui/components/key-value-map/index.js
@@ -1,4 +1,4 @@
-// Copyright © 2019 The Things Network Foundation, The Things Industries B.V.
+// Copyright © 2023 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import React from 'react'
-import bind from 'autobind-decorator'
+import React, { useCallback } from 'react'
import { defineMessages } from 'react-intl'
import classnames from 'classnames'
@@ -30,124 +29,121 @@ const m = defineMessages({
addEntry: 'Add entry',
})
-class KeyValueMap extends React.PureComponent {
- static propTypes = {
- addMessage: PropTypes.message,
- additionalInputProps: PropTypes.shape({}),
- className: PropTypes.string,
- disabled: PropTypes.bool,
- indexAsKey: PropTypes.bool,
- inputElement: PropTypes.elementType,
- isReadOnly: PropTypes.func,
- keyPlaceholder: PropTypes.message,
- name: PropTypes.string.isRequired,
- onBlur: PropTypes.func,
- onChange: PropTypes.func,
- value: PropTypes.arrayOf(
- PropTypes.oneOfType([
- PropTypes.shape({
- key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
- value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+const KeyValueMap = ({
+ addMessage,
+ additionalInputProps,
+ className,
+ disabled,
+ indexAsKey,
+ inputElement,
+ isReadOnly,
+ keyPlaceholder,
+ name,
+ onBlur,
+ onChange,
+ value,
+ valuePlaceholder,
+}) => {
+ const handleEntryChange = useCallback(
+ (index, newValues) => {
+ onChange(
+ value.map((val, idx) => {
+ if (index !== idx) {
+ return val
+ }
+
+ return indexAsKey ? newValues.value : { ...val, ...newValues }
}),
- PropTypes.string,
- ]),
- ),
- valuePlaceholder: PropTypes.message.isRequired,
- }
-
- static defaultProps = {
- additionalInputProps: {},
- className: undefined,
- onBlur: () => null,
- onChange: () => null,
- value: [],
- addMessage: m.addEntry,
- indexAsKey: false,
- keyPlaceholder: '',
- disabled: false,
- isReadOnly: () => null,
- inputElement: Input,
- }
-
- @bind
- handleEntryChange(index, newValues) {
- const { onChange, value, indexAsKey } = this.props
- onChange(
- value.map((val, idx) => {
- if (index !== idx) {
- return val
- }
-
- return indexAsKey ? newValues.value : { ...val, ...newValues }
- }),
- )
- }
-
- @bind
- removeEntry(index) {
- const { onChange, value } = this.props
- onChange(value.filter((_, i) => i !== index) || [], true)
- }
-
- @bind
- addEmptyEntry() {
- const { onChange, value, indexAsKey } = this.props
+ )
+ },
+ [indexAsKey, onChange, value],
+ )
+
+ const removeEntry = useCallback(
+ index => {
+ onChange(value.filter((_, i) => i !== index) || [], true)
+ },
+ [onChange, value],
+ )
+
+ const addEmptyEntry = useCallback(() => {
const entry = indexAsKey ? '' : { key: '', value: '' }
onChange([...value, entry])
- }
+ }, [indexAsKey, onChange, value])
+
+ return (
+
+
+ {value &&
+ value.map((value, index) => (
+
+ ))}
+
+
+
+
+
+ )
+}
- render() {
- const {
- className,
- name,
- value,
- keyPlaceholder,
- valuePlaceholder,
- addMessage,
- onBlur,
- indexAsKey,
- disabled,
- isReadOnly,
- inputElement,
- additionalInputProps,
- } = this.props
+KeyValueMap.propTypes = {
+ addMessage: PropTypes.message,
+ additionalInputProps: PropTypes.shape({}),
+ className: PropTypes.string,
+ disabled: PropTypes.bool,
+ indexAsKey: PropTypes.bool,
+ inputElement: PropTypes.elementType,
+ isReadOnly: PropTypes.func,
+ keyPlaceholder: PropTypes.message,
+ name: PropTypes.string.isRequired,
+ onBlur: PropTypes.func,
+ onChange: PropTypes.func,
+ value: PropTypes.arrayOf(
+ PropTypes.oneOfType([
+ PropTypes.shape({
+ key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ }),
+ PropTypes.string,
+ ]),
+ ),
+ valuePlaceholder: PropTypes.message.isRequired,
+}
- return (
-
-
- {value &&
- value.map((value, index) => (
-
- ))}
-
-
-
-
-
- )
- }
+KeyValueMap.defaultProps = {
+ additionalInputProps: {},
+ className: undefined,
+ onBlur: () => null,
+ onChange: () => null,
+ value: [],
+ addMessage: m.addEntry,
+ indexAsKey: false,
+ keyPlaceholder: '',
+ disabled: false,
+ isReadOnly: () => null,
+ inputElement: Input,
}
export default KeyValueMap