From daf346151e2f65e23fa942277c2ee3f4b67cabc6 Mon Sep 17 00:00:00 2001 From: gdemu13 Date: Sun, 10 May 2020 20:41:07 +0400 Subject: [PATCH] Resources with Hooks API --- client/app/startup/registration.js | 2 +- .../Resources/__tests__/Resources.spec.jsx | 26 +- client/app/widgets/Resources/index.jsx | 254 +++++++++--------- 3 files changed, 134 insertions(+), 148 deletions(-) diff --git a/client/app/startup/registration.js b/client/app/startup/registration.js index f7d1d3e092..1d833a897b 100644 --- a/client/app/startup/registration.js +++ b/client/app/startup/registration.js @@ -18,7 +18,7 @@ import { Logo } from '../components/Logo'; import { Modal } from '../components/Modal'; import { Notifications } from '../widgets/Notifications'; import { Resource } from '../components/Resource'; -import { Resources } from '../widgets/Resources'; +import Resources from '../widgets/Resources'; import { Story } from '../components/Story'; import { StoryDraft } from '../components/Story/StoryDraft'; import { StoryActions } from '../components/Story/StoryActions'; diff --git a/client/app/widgets/Resources/__tests__/Resources.spec.jsx b/client/app/widgets/Resources/__tests__/Resources.spec.jsx index a2e2ef0e14..b528f64807 100644 --- a/client/app/widgets/Resources/__tests__/Resources.spec.jsx +++ b/client/app/widgets/Resources/__tests__/Resources.spec.jsx @@ -2,7 +2,7 @@ import { mount } from 'enzyme'; import React from 'react'; import { act } from 'react-dom/test-utils'; -import { Resources } from '../index'; +import Resources from '../index'; // eslint-disable-next-line react/prop-types const getComponent = ({ history } = {}) => ( @@ -181,28 +181,26 @@ describe('Resources', () => { it('sends the selected tags to the URL', () => { const wrapper = mount(getComponent({ history })); - wrapper.setState({ - checkboxes: [ - { checked: true, value: 'alfredo', label: 'Alfredo' }, - { checked: true, value: 'batman', label: 'Batman' }, - { checked: false, value: 'vitor', label: 'Vitor' }, - ], - }); + wrapper + .find('.tag') + .at(8) + .simulate('click'); + + wrapper + .find('.tag') + .at(2) + .simulate('click'); expect(historyMock).toHaveBeenCalledWith({ pathname: '/resources', - search: '?filter[]=alfredo&filter[]=batman', + search: '?filter[]=ios&filter[]=therapy', }); }); }); describe('and there is no filters selected', () => { it('resets the search query parameter', () => { - const wrapper = mount(getComponent({ history })); - - wrapper.setState({ - checkboxes: [], - }); + mount(getComponent({ history })); expect(historyMock).toHaveBeenCalledWith({ pathname: '/resources', diff --git a/client/app/widgets/Resources/index.jsx b/client/app/widgets/Resources/index.jsx index f9208c4642..9f691ee407 100644 --- a/client/app/widgets/Resources/index.jsx +++ b/client/app/widgets/Resources/index.jsx @@ -1,5 +1,5 @@ // @flow -import React from 'react'; +import React, { useState, useEffect } from 'react'; import css from './Resources.scss'; import { Resource } from '../../components/Resource'; import { Utils } from '../../utils'; @@ -55,21 +55,58 @@ const infoDescription = ( ); -export class Resources extends React.Component { - // eslint-disable-next-line react/static-property-placement - static defaultProps = { - // eslint-disable-next-line react/default-props-match-prop-types - history: HistoryLib, - }; +const createCheckboxes = (resources: ResourceProp[], keywords: string[]) => { + const tagsList = [ + ...new Set( + resources + .map((resource: ResourceProp) => resource.tags.concat(resource.languages)) + .reduce((acc, val) => acc.concat(val), []), + ), + ]; + return sortAlpha( + tagsList.map((tag: string) => ({ + id: tag, + key: tag, + value: tag, + label: tag, + checked: keywords.some( + (keyword) => keyword.toLowerCase() === tag.toLowerCase(), + ), + })), + ); +}; - constructor(props: Props) { - super(props); - this.state = this.stateWhenFiltered(this.createCheckboxes()); - } +const filterList = ( + checkboxes: Checkbox[], + resources: ResourceProp[], +): ResourceProp[] => { + const selectedCheckboxes: Checkbox[] = checkboxes.filter( + (checkbox: Checkbox) => !!checkbox.checked, + ); + return resources.filter((resource: ResourceProp) => { + const tagCheck = selectedCheckboxes.map((checkbox: Checkbox) => + // eslint-disable-next-line implicit-arrow-linebreak + resource.tags.concat(resource.languages).includes(checkbox.id)); + return selectedCheckboxes.length > 0 ? tagCheck.includes(true) : true; + }); +}; - componentDidUpdate() { - const { checkboxes } = this.state; - const { history } = this.props; +const Resources = ({ resources, keywords, history = HistoryLib }: Props) => { + const [checkboxes, setCheckboxes] = useState( + createCheckboxes(resources, keywords), + ); + const [filteredResources, setFilteredResources] = useState( + filterList(checkboxes, resources), + ); + const [resourcesDisplayed, setResourcesDisplayed] = useState( + Math.min(RESOURCES_PER_PAGE, filteredResources.length), + ); + const [lastPage, setLastPage] = useState( + filteredResources.length <= RESOURCES_PER_PAGE, + ); + + useEffect(() => { + setFilteredResources(filterList(checkboxes, resources)); const checkedCheckboxes = checkboxes.filter((checkbox) => checkbox.checked); if (checkedCheckboxes.length > 0) { @@ -82,134 +119,85 @@ export class Resources extends React.Component { } else { history.replace({ pathname: '/resources', search: '' }); } - } - - stateWhenFiltered = (checkboxes: Checkbox[]) => { - const filteredResources = this.filterList(checkboxes); - return { - checkboxes, - filteredResources, - lastPage: filteredResources.length <= RESOURCES_PER_PAGE, - resourcesDisplayed: Math.min( - RESOURCES_PER_PAGE, - filteredResources.length, - ), - }; - }; + }, [checkboxes]); - createCheckboxes = () => { - const { resources, keywords } = this.props; - const tagsList = [ - ...new Set( - resources - .map((resource: ResourceProp) => resource.tags.concat(resource.languages)) - .reduce((acc, val) => acc.concat(val), []), - ), - ]; - return sortAlpha( - tagsList.map((tag: string) => ({ - id: tag, - key: tag, - value: tag, - label: tag, - checked: keywords.some( - (keyword) => keyword.toLowerCase() === tag.toLowerCase(), - ), - })), + useEffect(() => { + setLastPage(filteredResources.length <= RESOURCES_PER_PAGE); + setResourcesDisplayed( + Math.min(RESOURCES_PER_PAGE, filteredResources.length), ); - }; + }, [filteredResources]); - checkboxChange = (box: Checkbox) => { - this.setState(({ checkboxes }: State) => this.stateWhenFiltered( + const checkboxChange = (box: Checkbox) => { + setCheckboxes( checkboxes.filter((checkbox) => checkbox.id !== box.id).concat(box), - )); - }; - - filterList = (checkboxes: Checkbox[]): ResourceProp[] => { - const { resources } = this.props; - const selectedCheckboxes: Checkbox[] = checkboxes.filter( - (checkbox: Checkbox) => !!checkbox.checked, ); - return resources.filter((resource: ResourceProp) => { - const tagCheck = selectedCheckboxes.map((checkbox: Checkbox) => - // eslint-disable-next-line implicit-arrow-linebreak - resource.tags.concat(resource.languages).includes(checkbox.id)); - return selectedCheckboxes.length > 0 ? tagCheck.includes(true) : true; - }); }; - updateTagFilter = (tagLabel: String) => { - this.setState(({ checkboxes }: State) => { - const updatedBoxes = checkboxes.map((box) => - // eslint-disable-next-line implicit-arrow-linebreak - (box.label === tagLabel ? { ...box, checked: true } : box)); - return this.stateWhenFiltered(updatedBoxes); - }); + const updateTagFilter = (tagLabel: String) => { + const updatedBoxes = checkboxes.map((box) => + // eslint-disable-next-line implicit-arrow-linebreak + (box.label === tagLabel ? { ...box, checked: true } : box)); + setCheckboxes(updatedBoxes); }; - onClick = () => { - this.setState(({ resourcesDisplayed, filteredResources }: State) => { - const updatedResourcesDisplayed = Math.min( - filteredResources.length - resourcesDisplayed, - RESOURCES_PER_PAGE, - ) + resourcesDisplayed; - return { - resourcesDisplayed: updatedResourcesDisplayed, - lastPage: filteredResources.length === updatedResourcesDisplayed, - }; - }); - }; + const onClick = () => { + const updatedResourcesDisplayed = Math.min( + filteredResources.length - resourcesDisplayed, + RESOURCES_PER_PAGE, + ) + resourcesDisplayed; - displayTags = () => { - const { resourcesDisplayed, lastPage, filteredResources } = this.state; - const { resources } = this.props; - return ( - <> -
- {`${Math.min(resourcesDisplayed, resources.length)} ${I18n.t('of')} ${ - filteredResources.length - } ${I18n.t('navigation.resources').toLowerCase()}`} -
-
- {filteredResources - .slice(0, resourcesDisplayed) - .map((resource: ResourceProp) => ( -
- { - this.updateTagFilter(tagLabel); - }} - /> -
- ))} -
- {!lastPage && } - - ); + setResourcesDisplayed(updatedResourcesDisplayed); + setLastPage(filteredResources.length === updatedResourcesDisplayed); }; - render() { - const { checkboxes } = this.state; - return ( - <> - {infoDescription} - this.checkboxChange(box)} - /> - {this.displayTags()} - - ); - } -} + const displayTags = () => ( + <> +
+ {`${Math.min(resourcesDisplayed, resources.length)} ${I18n.t('of')} ${ + filteredResources.length + } ${I18n.t('navigation.resources').toLowerCase()}`} +
+
+ {filteredResources + .slice(0, resourcesDisplayed) + .map((resource: ResourceProp) => ( +
+ { + updateTagFilter(tagLabel); + }} + /> +
+ ))} +
+ {!lastPage && } + + ); + + return ( + <> + {infoDescription} + checkboxChange(box)} + /> + {displayTags()} + + ); +}; + +export default ({ resources, keywords, history }: Props) => ( + +);