diff --git a/news/4971.feature b/news/4971.feature new file mode 100644 index 0000000000..d4cf470dd6 --- /dev/null +++ b/news/4971.feature @@ -0,0 +1 @@ +Refactor ContentsTagsModal -@Tishasoumya-02 diff --git a/src/components/manage/Contents/ContentsTagsModal.jsx b/src/components/manage/Contents/ContentsTagsModal.jsx index 315fb8ec48..042c5ec5b0 100644 --- a/src/components/manage/Contents/ContentsTagsModal.jsx +++ b/src/components/manage/Contents/ContentsTagsModal.jsx @@ -1,15 +1,10 @@ -/** - * Contents tags modal. - * @module components/manage/Contents/ContentsTagsModal - */ - -import React, { Component } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { compose } from 'redux'; +import { useDispatch, useSelector } from 'react-redux'; import { map } from 'lodash'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, useIntl } from 'react-intl'; +import { usePrevious } from '@plone/volto/helpers'; import { updateContent } from '@plone/volto/actions'; import { ModalForm } from '@plone/volto/components'; @@ -32,133 +27,91 @@ const messages = defineMessages({ }, }); -/** - * ContentsTagsModal class. - * @class ContentsTagsModal - * @extends Component - */ -class ContentsTagsModal extends Component { - /** - * Property types. - * @property {Object} propTypes Property types. - * @static - */ - static propTypes = { - updateContent: PropTypes.func.isRequired, - items: PropTypes.arrayOf( - PropTypes.shape({ - subjects: PropTypes.arrayOf(PropTypes.string), - url: PropTypes.string, - }), - ).isRequired, - request: PropTypes.shape({ - loading: PropTypes.bool, - loaded: PropTypes.bool, - }).isRequired, - open: PropTypes.bool.isRequired, - onOk: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, - }; - - /** - * Constructor - * @method constructor - * @param {Object} props Component properties - * @constructs ContentsUploadModal - */ - constructor(props) { - super(props); - this.onSubmit = this.onSubmit.bind(this); - } +const ContentsTagsModal = (props) => { + const { items, open, onCancel, onOk } = props; + const intl = useIntl(); + const dispatch = useDispatch(); + const request = useSelector((state) => state.content.update); + const prevrequestloading = usePrevious(request.loading); - /** - * Component will receive props - * @method componentWillReceiveProps - * @param {Object} nextProps Next properties - * @returns {undefined} - */ - UNSAFE_componentWillReceiveProps(nextProps) { - if (this.props.request.loading && nextProps.request.loaded) { - this.props.onOk(); + useEffect(() => { + if (prevrequestloading && request.loaded) { + onOk(); } - } + }, [onOk, prevrequestloading, request.loaded]); - /** - * Submit handler - * @method onSubmit - * @param {Object} data Form data - * @returns {undefined} - */ - onSubmit({ tags_to_add = [], tags_to_remove = [] }) { - this.props.updateContent( - map(this.props.items, (item) => item.url), - map(this.props.items, (item) => ({ - subjects: [ - ...new Set( - (item.subjects ?? []) - .filter((s) => !tags_to_remove.includes(s)) - .concat(tags_to_add), - ), - ], - })), - ); - } + const onSubmit = useCallback( + ({ tags_to_add = [], tags_to_remove = [] }) => { + dispatch( + updateContent( + map(items, (item) => item.url), + map(items, (item) => ({ + subjects: [ + ...new Set( + (item.subjects ?? []) + .filter((s) => !tags_to_remove.includes(s)) + .concat(tags_to_add), + ), + ], + })), + ), + ); + }, + [dispatch, items], + ); - /** - * Render method. - * @method render - * @returns {string} Markup for the component. - */ - render() { - const currentSetTags = [ - ...new Set(this.props.items.map((item) => item.subjects).flat()), - ]; + const currentSetTags = useMemo( + () => [...new Set(items.map((item) => item.subjects).flat())], + [items], + ); - return ( - this.props.open && ( - [tag, tag]), - }, - tags_to_add: { - type: 'array', - widget: 'token', - title: this.props.intl.formatMessage(messages.tagsToAdd), - items: { - vocabulary: { '@id': 'plone.app.vocabularies.Keywords' }, - }, + return ( + open && ( + [tag, tag]), + }, + tags_to_add: { + type: 'array', + widget: 'token', + title: intl.formatMessage(messages.tagsToAdd), + items: { + vocabulary: { '@id': 'plone.app.vocabularies.Keywords' }, }, }, - required: [], - }} - /> - ) - ); - } -} + }, + required: [], + }} + /> + ) + ); +}; -export default compose( - injectIntl, - connect( - (state) => ({ - request: state.content.update, +ContentsTagsModal.propTypes = { + items: PropTypes.arrayOf( + PropTypes.shape({ + subjects: PropTypes.arrayOf(PropTypes.string), + url: PropTypes.string, }), - { updateContent }, - ), -)(ContentsTagsModal); + ).isRequired, + open: PropTypes.bool.isRequired, + onOk: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +}; +export default ContentsTagsModal; diff --git a/src/components/manage/Contents/ContentsTagsModal.stories.jsx b/src/components/manage/Contents/ContentsTagsModal.stories.jsx new file mode 100644 index 0000000000..61050e2bb0 --- /dev/null +++ b/src/components/manage/Contents/ContentsTagsModal.stories.jsx @@ -0,0 +1,68 @@ +import { injectIntl } from 'react-intl'; +import React from 'react'; +import ContentsTagsModalComponent from './ContentsTagsModal'; +import { RealStoreWrapper as Wrapper } from '@plone/volto/storybook'; +import { bool } from 'prop-types'; + +const IntlContentTagsModalComponent = injectIntl(ContentsTagsModalComponent); + +function StoryComponent(args) { + return ( + +
+ {}} + onCancel={() => {}} + items={[ + { + ...args, + url: '/blog', + }, + ]} + /> + + ); +} + +export const ContentTagsModal = StoryComponent.bind({}); + +ContentTagsModal.args = { + subjects: ['plone 6 ', 'plone 5', 'plone'], + open: true, +}; + +export default { + title: 'Public components/Contents/Content Tags Modal', + component: ContentTagsModal, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: { + subjects: { + context: 'json', + description: 'Added tags', + }, + open: { + context: bool, + description: 'open/close modal', + }, + }, +};