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