Closed rolas978 closed 2 years ago
Here's the code for the full ArticleListing file:
import React from 'react';
import { Page } from '../../components/Page';
import { Section } from '../../components/Section';
import { Seo } from '../../components/Seo';
import { Slider } from '../../components/Slider';
import { ArticleCard } from '../../components/ArticleCard';
import { Button, ButtonType } from '../../components/Button';
import ArticleTemplateData from '../Article/data';
import * as classes from './style.module.css';
interface ArticleListingTemplateProps {
pageContext: {
articles: ArticleTemplateData[];
};
}
interface FilterOption {
label: string;
selected: boolean;
relatedArticleIds: string[];
}
export default function ArticleListingTemplate(props: ArticleListingTemplateProps): React.ReactElement {
const ARTICLES_PER_PAGE = 9;
const articles = props.pageContext.articles;
const [filterOptions, setFilterOptions] = React.useState<FilterOption[]>(extractFilterOptions(articles));
const [shownArticlesNumber, setShownArticlesNumber] = React.useState<number>(ARTICLES_PER_PAGE);
function handleFilterOptionClick(optionLabel: string): void {
const updatedFilterOptions = [...filterOptions];
const selectedOptionIndex = updatedFilterOptions.map((o) => o.label).indexOf(optionLabel);
updatedFilterOptions[selectedOptionIndex].selected = !updatedFilterOptions[selectedOptionIndex].selected;
setFilterOptions(updatedFilterOptions);
}
function handleLoadMoreButtonClick(articlesNumber: number, selectedArticlesNumber?: number): void {
const incrementedArticleNumber = shownArticlesNumber + 3;
if (selectedArticlesNumber && selectedArticlesNumber >= incrementedArticleNumber) {
setShownArticlesNumber(incrementedArticleNumber);
} else if (!selectedArticlesNumber && articlesNumber >= incrementedArticleNumber) {
setShownArticlesNumber(incrementedArticleNumber);
}
}
// Check if at least one filter option is selected. If so, create an array of all article ids that
// are selected based on the current filter option selection. We use this later on to easily check
// which articles to show.
let selectedArticleIds: string[] = [];
const filterSelected = filterOptions.map((o) => o.selected).indexOf(true) !== -1;
if (filterSelected) {
selectedArticleIds = filterOptions
.filter((option) => option.selected) // Filter only for selected options
.map((option) => option.relatedArticleIds) // Create an array of article ids arrays
.flat(1) // Flatten the array to a string[]
.filter((id, index, arr) => arr.indexOf(id) === index); // Remove duplicate article ids
}
return (
<>
<Seo title="NFT Collection" useTitleTemplate={true} />
<Page>
<Section anchor="articleListing" heading="Explore Collections">
<div className={classes.Filter}>
Sort by type
<Slider additionalClasses={[classes.Options]}>
{filterOptions.map((option, key) => {
return (
<div
key={key}
role="button"
onClick={() => handleFilterOptionClick(option.label)}
className={[
classes.Option,
option.selected === true ? classes.Selected : null,
].join(' ')}
>
{option.label} ({option.relatedArticleIds.length})
</div>
);
})}
</Slider>
</div>
<div className={classes.Listing}>
{articles
.filter((article) => !filterSelected || selectedArticleIds.includes(article.id))
.slice(0, shownArticlesNumber)
.map((article, key) => {
return (
<ArticleCard
key={key}
showBanner={true}
data={{
title: article.title,
image: article.banner,
link: article.slug,
}}
/>
);
})}
</div>
</Section>
</Page>
</>
);
}
// Helper function to calculate a sorted array of filter options based on the given articles
// We use the helper function before we initialize the state so that it can happen on the server.
function extractFilterOptions(articles: ArticleTemplateData[]): FilterOption[] {
const filterOptions: FilterOption[] = [];
const categoryList: string[] = [];
articles.forEach((article) => {
article.categories.forEach((category) => {
if (!categoryList.includes(category)) {
filterOptions.push({ label: category, selected: false, relatedArticleIds: [article.id] });
categoryList.push(category);
} else {
const optionIndex = filterOptions.map((o) => o.label).indexOf(category);
filterOptions[optionIndex].relatedArticleIds.push(article.id);
}
});
});
return filterOptions.sort((a, b) => (a.relatedArticleIds.length > b.relatedArticleIds.length ? -1 : 1));
}
Hi @rolas978! Usually, the ArticleListingTemplate
is used to automatically generate the blog listing page. You find the code here.
If you want to reuse the template for a custom section, you need to pass in the required props which you find here. So basically, your index.js
should look like:
export default function IndexPage() {
const pageContext = { articles: YourArrayOfArticles };
return (
<>
<Page>
<Seo title="Gatsby Theme Portfolio Minimal" />
<HeroSection sectionId="hero" />
<ArticleListingTemplate heading="Explore Collections" pageContext={pageContext} />
</Page>
</>
);
}
But your YourArrayOfArticles
needs to have the correct shape, otherwise it won't work. Perhaps, you can also look into the latest version I released since it contains the Section
and Animation
component. This allows you building better custom sections.
Let me know if this helps you 😄
Hi there! I've been using this theme for some time now and love it. One issue I'm running into while tweaking it comes when I attempt to create a new section using ArticlesListingTemplate. I have this in my index.js:
but end up with this error:
articles is defined earlier in the file through an interface, so not sure why I'm getting this error. What might be going wrong here? Appreciate any help!