diff --git a/packages/volto/news/5093.feature b/packages/volto/news/5093.feature new file mode 100644 index 0000000000..cb0a0ab525 --- /dev/null +++ b/packages/volto/news/5093.feature @@ -0,0 +1 @@ +Refactor `ReferenceWidget` from class-based to functional component. @Tishasoumya \ No newline at end of file diff --git a/packages/volto/src/components/manage/Widgets/ReferenceWidget.jsx b/packages/volto/src/components/manage/Widgets/ReferenceWidget.jsx index 8ab70c31b5..93f90c9f1c 100644 --- a/packages/volto/src/components/manage/Widgets/ReferenceWidget.jsx +++ b/packages/volto/src/components/manage/Widgets/ReferenceWidget.jsx @@ -1,15 +1,9 @@ -/** - * ReferenceWidget component. - * @module components/manage/Widgets/ReferenceWidget - */ - -import React, { Component } from 'react'; +import { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { compose } from 'redux'; +import { useDispatch, useSelector } from 'react-redux'; import { Label, Dropdown, Popup, Icon } from 'semantic-ui-react'; import { compact, concat, fromPairs, map, values, uniqBy } from 'lodash'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, useIntl } from 'react-intl'; import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper'; import { resetSearchContent, searchContent } from '@plone/volto/actions'; @@ -26,130 +20,16 @@ const messages = defineMessages({ }, }); -/** - * ReferenceWidget component class. - * @class ReferenceWidget - * @extends Component - */ -class ReferenceWidget extends Component { - /** - * Property types. - * @property {Object} propTypes Property types. - * @static - */ - static propTypes = { - id: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - description: PropTypes.string, - required: PropTypes.bool, - multiple: PropTypes.bool, - error: PropTypes.arrayOf(PropTypes.string), - value: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.object), - PropTypes.object, - ]), - onChange: PropTypes.func.isRequired, - resetSearchContent: PropTypes.func.isRequired, - searchContent: PropTypes.func.isRequired, - search: PropTypes.arrayOf( - PropTypes.shape({ - '@id': PropTypes.string, - '@type': PropTypes.string, - title: PropTypes.string, - description: PropTypes.string, - }), - ), - wrapped: PropTypes.bool, - }; - - /** - * Default properties - * @property {Object} defaultProps Default properties. - * @static - */ - static defaultProps = { - description: null, - required: false, - error: [], - search: [], - value: null, - multiple: true, - }; - - /** - * Constructor - * @method constructor - * @param {Object} props Component properties - * @constructs Actions - */ - constructor(props) { - super(props); - this.onSearchChange = this.onSearchChange.bind(this); - - this.state = { - choices: props.value - ? props.multiple - ? fromPairs( - map(props.value, (value) => [ - value['@id'], - { - key: value['@id'], - text: flattenToAppURL(value['@id']), - value: value['@id'], - label: { - content: value.title, - }, - data: value, - }, - ]), - ) - : { - [props.value['@id']]: { - key: props.value['@id'], - text: flattenToAppURL(props.value), - value: props.value['@id'], - label: { - content: props.value.title, - }, - data: props.value, - }, - novalue: { - key: 'novalue', - text: this.props.intl.formatMessage(messages.no_value), - value: 'novalue', - data: null, - }, - } - : {}, - }; - } - - componentDidMount() { - this.props.resetSearchContent(); - } - - /** - * Component will receive props - * @method componentWillReceiveProps - * @param {Object} nextProps Next properties - * @returns {undefined} - */ - UNSAFE_componentWillReceiveProps(nextProps) { - this.setState({ - choices: { - ...fromPairs( - map( - uniqBy( - map( - compact(concat(nextProps.value, nextProps.search)), - (item) => ({ - ...item, - '@id': flattenToAppURL(item['@id']), - }), - ), - '@id', - ), - (value) => [ +const ReferenceWidget = (props) => { + const { id, title, value, multiple, onChange } = props; + const intl = useIntl(); + const dispatch = useDispatch(); + const search = useSelector((state) => state.search.items); + const [choices, setChoices] = useState( + value + ? multiple + ? fromPairs( + map(value, (value) => [ value['@id'], { key: value['@id'], @@ -160,37 +40,78 @@ class ReferenceWidget extends Component { }, data: value, }, - ], + ]), + ) + : { + [value['@id']]: { + key: value['@id'], + text: flattenToAppURL(value), + value: value['@id'], + label: { + content: value.title, + }, + data: value, + }, + novalue: { + key: 'novalue', + text: intl.formatMessage(messages.no_value), + value: 'novalue', + data: null, + }, + } + : {}, + ); + + useEffect(() => { + dispatch(resetSearchContent()); + }, [dispatch]); + + useEffect(() => { + setChoices({ + ...fromPairs( + map( + uniqBy( + map(compact(concat(value, search)), (item) => ({ + ...item, + '@id': flattenToAppURL(item['@id']), + })), + '@id', ), + (value) => [ + value['@id'], + { + key: value['@id'], + text: flattenToAppURL(value['@id']), + value: value['@id'], + label: { + content: value.title, + }, + data: value, + }, + ], ), - novalue: { - key: 'novalue', - text: this.props.intl.formatMessage(messages.no_value), - value: 'novalue', - data: null, - }, + ), + novalue: { + key: 'novalue', + text: intl.formatMessage(messages.no_value), + value: 'novalue', + data: null, }, }); - } - - /** - * On search change handler - * @method onSearchChange - * @param {object} event Event object. - * @param {object} data Event data. - * @returns {undefined} - */ + }, [intl, search, value]); - onSearchChange(event, data) { + const onSearchChange = (event, data) => { if (data.searchQuery && data.searchQuery !== '') { - this.props.searchContent('', { - Title: `*${data.searchQuery}*`, - }); + dispatch( + searchContent('', { + Title: `*${data.searchQuery}*`, + }), + ); } else { - this.props.resetSearchContent(); + dispatch(resetSearchContent()); } - } - renderLabel = (item, index, defaultProps) => { + }; + const renderLabel = (item, index, defaultProps) => { return ( - + + item && item['@id'] ? flattenToAppURL(item['@id']) : item, + ) + : [] + : value + ? flattenToAppURL(value['@id']) + : '' + } + onChange={(event, data) => { + return onChange( + id, multiple - ? value - ? map(value, (item) => - item && item['@id'] ? flattenToAppURL(item['@id']) : item, - ) - : [] - : value - ? flattenToAppURL(value['@id']) - : '' - } - onChange={(event, data) => { - return onChange( - id, - multiple - ? map(data.value, (item) => this.state.choices[item].data) - : this.state.choices[data.value].data, - ); - }} - onSearchChange={this.onSearchChange} - renderLabel={this.renderLabel} - /> - - ); - } -} + ? map(data.value, (item) => choices[item].data) + : choices[data.value].data, + ); + }} + onSearchChange={onSearchChange} + renderLabel={renderLabel} + /> + + ); +}; + +ReferenceWidget.propTypes = { + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string, + required: PropTypes.bool, + multiple: PropTypes.bool, + error: PropTypes.arrayOf(PropTypes.string), + value: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.object), + PropTypes.object, + ]), + onChange: PropTypes.func.isRequired, + wrapped: PropTypes.bool, +}; -export default compose( - injectIntl, - connect( - (state) => ({ - search: state.search.items, - }), - { resetSearchContent, searchContent }, - ), -)(ReferenceWidget); +ReferenceWidget.defaultProps = { + description: null, + required: false, + error: [], + value: null, + multiple: true, +}; +export default ReferenceWidget;