Home > Mobile >  How to dynamically filter posts fetched in a API using Next.js and Typescript?
How to dynamically filter posts fetched in a API using Next.js and Typescript?

Time:04-12

I am developing a portfolio website with filtering work. so I'm currently struggling with using useStates, useEffect, and unsure with how they work. I have been working on it for the 5 days and have watch/read different concepts and how-tos/other's answers but still struggling with how to pass useState to fetch and filter api data.

The idea is when the user clicks on a link in index.tsx like an apple, orange, or banana, the website filters posts according to what they clicked on, but I can only currently filter posts by hardcoding it into the function getPosts('tag:apple').

index.tsx

import {  getPosts,  PostType } from "./api/ghostCMS"
import moment from 'moment'
//import { useState, useEffect } from 'react'

export const getServerSideProps = async ({params}) => {
    const posts = await getPosts('tag:apple')
    return {
        props: { 
            revalidate: 10, 
            posts }
    }
}

const Home: React.FC<{ posts: PostType[] }> = (props) => {
    const { posts } = props

    return (
    <div>
        <div> {/* links to filter posts */}
                <button onClick={() => getPosts('tag:apple')}>apple</button> 
                <button>orange</button>
                <button>banana</button>
            </div>

            <div className={styles.gridContainer}>
                {posts.map((post, index) => {
                    return (
                        <li className={styles.postitem} key={post.slug}>
                            <Link href="/post/[slug]" as={`/post/${post.slug}`}>
                                <a>{post.title}</a>                             
                            </Link>
                            <p>{moment(post.created_at).format('MMMM D, YYYY [●] h:mm a')}                                                       
                            </p>
                            <p>{post.custom_excerpt}</p>
                            {!!post.feature_image && <img src={post.feature_image} />}
                        </li>
                    )
                })}
            </div> {/*End of Post Container */}
        </div>

Then for this ghostCMS.tsx, this is where I fetch the api data and add filter strings in function getPost.

./api/ghostCMS.tsx

const { BLOG_URL, CONTENT_API_KEY } = process.env

export interface PostType {
    title: string
    slug: string
    custom_excerpt: string
    feature_image: string
    created_at: string
    tag: string
} 
export async function getPosts(setFilter?: string) {
    
    if (typeof setFilter !== 'undefined' ) {
        const res = await fetch(
            `${BLOG_URL}/ghost/api/v4/content/posts/?key=${CONTENT_API_KEY}
            &fields=title,slug,custom_excerpt,feature_image,created_at,tag
            &limit=all&filter=${setFilter}`
        ).then((res) => res.json())
        const posts = res.posts
        return posts

    } else {
        const res = await fetch(
            `${BLOG_URL}/ghost/api/v4/content/posts/?key=${CONTENT_API_KEY}
            &fields=title,slug,custom_excerpt,feature_image,created_at,tag
            &limit=all`
        ).then((res) => res.json())
        const posts = res.posts
        return posts
    }
} 
export default getPosts

I'm not sure if I got my logic down right but in summary, I'm still learning how to filter according to what the user wants to see. If you have any suggestions or resources, please feel free tell me and explain. Sorry if it sounds like a dumb question, trying my best to learn as I do this portfolio website and your help is greatly appreciated. Thank you.

EDITED: After receiving advise from @wongz, I changed my anchors to buttons, but an error shows up saying: Unhandled runtime Error ReferenceError: process is not defined process is not defined

Is the error showing up because I called the wrong function? I used getPosts(), so I tried replacing it with setServerSideProps(setFilter) but shows a Server Error Error: Error serializing .posts returned from getServerSideProps in "/". Reason: undefined cannot be serialized as JSON. Please use null or omit this value. so I just reverted back to just placing the buttons down without functions...

I was thinking if its possible to place the const getServerSideProps inside the const Home in the index.tsx? Or will useEffect be possible? If so, how can I do it properly? Thank you for your time.

CodePudding user response:

There are a number of things to switch up a bit.

1: Conceptually think of your index.tsx as the main component, so I removed passing params into it because it will keep changing Home every time param updates

2: The buttons should have a value. When you click the button, it goes to a handleClick function. That handleClick function then determines which fruit value is desired via e?.currentTarget?.value. The question marks means if it doesn't exist, the whole thing will be undefined - just a safer way to call items in objects. We then call your getPosts function, passing in the filter tag and then set it to the posts variable via the setPosts function.

The way useState works is it provides you the value and the setter function to set the value of that variable. So posts is the variable name and setPosts is a function where you pass in a new value to determine the value of posts.

So here we're setting the posts as what is returned from the api call. We're also using .then().catch() syntax because it's asynchronous.

Change the res.data as needed based on how the data looks when received.

3: The useEffect hook is added here because we need to run posts one time when the page loads. The empty square brackets in the useEffect is where you put variables where the useEffect function will run whenever one of those variables changes. So in this case, we want it to only run once when this component loads, so we leave it empty so that it only runs once when the component loads.

What we have inside the useEffect hook is the getPosts function that is called, and then we call setPosts to update the posts variable. Then your JSX where it says {posts && } will be true so the posts will appear. This should happen in a second or so.

4: In the posts section, you need to only render it when posts are available otherwise the browser will error since posts is undefined. So that's where {posts && <div>,<div>} is needed.

import React from 'react';
import type {PostType} from "./ghostCMS";
import { getPosts } from './ghostCMS';

const Home:React.FC = () => {
    const [posts, setPosts] = React.useState<PostType[] | null>(null);

  React.useEffect(()=> {
    getPosts().then(res=>{
      setPosts(res.data);
    }).catch(err=>console.log(err));
  },[]);

    const handleClick = (e:React.MouseEvent<HTMLButtonElement>) => {
      const fruit = e?.currentTarget.value;
      getPosts("tag:" fruit).then(res=>setPosts(res.data)).catch(err=>console.log(err));
    }

    return (
    <div>
        <div>
            <button value="apple" onClick={(e) => handleClick(e)}>apple</button> 
            <button value="orange" onClick={(e) => handleClick(e)}>orange</button>
            <button value="banana" onClick={(e) => handleClick(e)}>banana</button>
        </div>

          {posts && <div>
              {posts?.map((post, index) => {
                  return (
                    <li key={post?.slug}>
                        <p>{post?.custom_excerpt}</p>
                    </li>
                  )
              })}
          </div>}
        </div>
    );
};

export default Home;

5: In terms of your config, the error is because it doesn't know what process is. In React, in order to get the variables you need to have REACT_APP_ before them. So add REACT_APP_ in your variable names in your .env file and call them as such. Along with this, based on the error you'll likely need to download the package dotenv and then calling it before you get the environment variables as per below. So npm install dotenv and then add the code below.

import dotenv from 'dotenv';

dotenv.config();

const BLOG_URL = process.env.REACT_APP_BLOG_URL;
const CONTENT_API_KEY = process.env.REACT_APP_CONTENT_API_KEY;

// your other code

Your .env file will now look like this

REACT_APP_BLOG_URL="example.com"
REACT_APP_API_KEY="apiKeyExample"

Based on making your app work, I would do #5 first to fix your issue and then update the rest bit by bit for your own understanding.

  • Related