Skip to content

Commit

Permalink
Resources with Hooks API
Browse files Browse the repository at this point in the history
  • Loading branch information
gdemu13 authored and julianguyen committed May 10, 2020
1 parent 17e7f37 commit daf3461
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 148 deletions.
2 changes: 1 addition & 1 deletion client/app/startup/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
26 changes: 12 additions & 14 deletions client/app/widgets/Resources/__tests__/Resources.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 } = {}) => (
Expand Down Expand Up @@ -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',
Expand Down
254 changes: 121 additions & 133 deletions client/app/widgets/Resources/index.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -55,21 +55,58 @@ const infoDescription = (
</center>
);

export class Resources extends React.Component<Props, State> {
// 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<Checkbox[]>(
createCheckboxes(resources, keywords),
);
const [filteredResources, setFilteredResources] = useState<ResourceProp[]>(
filterList(checkboxes, resources),
);
const [resourcesDisplayed, setResourcesDisplayed] = useState<number>(
Math.min(RESOURCES_PER_PAGE, filteredResources.length),
);
const [lastPage, setLastPage] = useState<boolean>(
filteredResources.length <= RESOURCES_PER_PAGE,
);

useEffect(() => {
setFilteredResources(filterList(checkboxes, resources));
const checkedCheckboxes = checkboxes.filter((checkbox) => checkbox.checked);

if (checkedCheckboxes.length > 0) {
Expand All @@ -82,134 +119,85 @@ export class Resources extends React.Component<Props, State> {
} 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 (
<>
<center className={css.marginTop} aria-live="polite">
{`${Math.min(resourcesDisplayed, resources.length)} ${I18n.t('of')} ${
filteredResources.length
} ${I18n.t('navigation.resources').toLowerCase()}`}
</center>
<section className={`${css.gridThree} ${css.marginTop}`}>
{filteredResources
.slice(0, resourcesDisplayed)
.map((resource: ResourceProp) => (
<article
className={`Resource ${css.gridThreeItem}`}
key={Utils.randomString()}
>
<Resource
tagged
tags={resource.languages.concat(resource.tags)}
title={resource.name}
link={resource.link}
updateTagFilter={(tagLabel) => {
this.updateTagFilter(tagLabel);
}}
/>
</article>
))}
</section>
{!lastPage && <LoadMoreButton onClick={this.onClick} />}
</>
);
setResourcesDisplayed(updatedResourcesDisplayed);
setLastPage(filteredResources.length === updatedResourcesDisplayed);
};

render() {
const { checkboxes } = this.state;
return (
<>
{infoDescription}
<InputTag
key={Utils.randomString()}
id="resourceTags"
name="resourceTags"
placeholder={I18n.t('common.form.search_by_keywords')}
checkboxes={checkboxes}
onCheckboxChange={(box) => this.checkboxChange(box)}
/>
{this.displayTags()}
</>
);
}
}
const displayTags = () => (
<>
<center className={css.marginTop} aria-live="polite">
{`${Math.min(resourcesDisplayed, resources.length)} ${I18n.t('of')} ${
filteredResources.length
} ${I18n.t('navigation.resources').toLowerCase()}`}
</center>
<section className={`${css.gridThree} ${css.marginTop}`}>
{filteredResources
.slice(0, resourcesDisplayed)
.map((resource: ResourceProp) => (
<article
className={`Resource ${css.gridThreeItem}`}
key={Utils.randomString()}
>
<Resource
tagged
tags={resource.languages.concat(resource.tags)}
title={resource.name}
link={resource.link}
updateTagFilter={(tagLabel) => {
updateTagFilter(tagLabel);
}}
/>
</article>
))}
</section>
{!lastPage && <LoadMoreButton onClick={onClick} />}
</>
);

return (
<>
{infoDescription}
<InputTag
key={Utils.randomString()}
id="resourceTags"
name="resourceTags"
placeholder={I18n.t('common.form.search_by_keywords')}
checkboxes={checkboxes}
onCheckboxChange={(box) => checkboxChange(box)}
/>
{displayTags()}
</>
);
};

export default ({ resources, keywords, history }: Props) => (
<Resources resources={resources} keywords={keywords} history={history} />
);

0 comments on commit daf3461

Please sign in to comment.