konstantinmuenster / gatsby-theme-portfolio-minimal

A Gatsby Theme to create modern one-page portfolios with a clean yet expressive design.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cannot read properties of undefined (reading 'articles')

rolas978 opened this issue · comments

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:

import React from 'react';
import {
   HeroSection,
   Page,
   Seo,
} from 'gatsby-theme-portfolio-minimal';
import ArticleListingTemplate from 'gatsby-theme-portfolio-minimal/src/templates/ArticleListing';

export default function IndexPage() {
   return (
       <>
   <Page>
           <Seo title="Gatsby Theme Portfolio Minimal" />
               <HeroSection sectionId="hero" />
   			<ArticleListingTemplate heading="Explore Collections" />
           </Page>
       </>
   );
}

but end up with this error:

Cannot read properties of undefined (reading 'articles')

  23 | export default function ArticleListingTemplate(props: ArticleListingTemplateProps): React.ReactElement {
  24 |     const ARTICLES_PER_PAGE = 9;
> 25 |     const articles = props.pageContext.articles;
     |                                       ^
  26 |     const [filterOptions, setFilterOptions] = React.useState<FilterOption[]>(extractFilterOptions(articles));
  27 |     const [shownArticlesNumber, setShownArticlesNumber] = React.useState<number>(ARTICLES_PER_PAGE);
  28 |

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!

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 😄