Search code examples
javascriptnext.jsjavascript-objectsnextjs-dynamic-routinggetstaticprops

Returning an array of objects from GetStaticProps in NextJS and using map function to iterate over it


I am building a mdx blog in NextJS. For this I have created a function getPostDataByCategory(category) in posts.js under lib to filtering posts based on category.

getPostDataByCategory(category) {
const fileNames = fs.readdirSync(postsDirectory);
// allPostsData to read all the file contents parsed using gray-matter
const allPostsData = fileNames.map((fileName) => {
...
}
let filteredPosts = [];

  filteredPosts.push(
    allPostsData.filter((post) => post.categories.includes(category))
  );

  return { filteredPosts };
}

I am receiving the filteredPosts in getStaticProps in categories/[categories].js as under:

export async function getStaticProps({ params }) {
let posts = await getPostDataByCategory(params.categories);
  const filteredPosts = JSON.parse(JSON.stringify(posts));

  return {
    props: {
      filteredPosts,
    },
  };
}

Thereafter I receive the filteredPosts in Category as:

export default function Category({ filteredPosts }) {
  
  return (
    <Layout>
      <ul>
        {filteredPosts.map((posts) => (
          <li key={posts.slug}>
            <p>{posts.title}</p>
          </li>
        ))}
      </ul>
    </Layout>
  );
}

However it gives me an error as TypeError: filteredPosts.map is not a function

I understand this error is due to the fact that filteredPosts is not an array and object destructing has to be done or it has to be converted to an array.

Any help is appreciated. Thanks beforehand.

I searched extensively for converting the array of Objects to array of arrays but they all seem complex for my usecase. There must be a simpler way of doing this.


Solution

  • You're mixing up object key names and also pushing nested arrays where it's not needed, causing confusion and issues with your mapping operations. Most of the fixes can be made to getPostDataByCategory to clean up how your date is structured.

    Firstly, the .filter() method returns a new array, so the below code says to push an array (the one returned by your filter()) into the filteredPosts array:

    let filteredPosts = [];
    
    filteredPosts.push(
      allPostsData.filter((post) => post.categories.includes(category)) // push a new array into th `fileredPosts` array
    );
    

    This ends up with you having nested arrays which isn't what you want here. You just need to assign fileredPost directly to the result of .filter():

    const filteredPosts = allPostsData.filter((post) => post.categories.includes(category));
    

    Next, you then return an object with a filteredPosts key:

    return { filteredPosts };
    

    This is equivalent to returning an object like so (see short-hand property names):

    return { filteredPosts: filteredPosts};
    

    So your object has a filteredPosts posts key, that holds the array that's in the filteredPosts variable. Since you're returning just one thing (ie: the filtered posts), you can avoid creating the object so that now your getPostDataByCategory returns an array:

    return filteredPosts;
    

    With these changes made to getPostDataByCategory, you can now update how getStaticProps is used (see code comments):

    export async function getStaticProps({ params }) {
      const filteredPosts = getPostDataByCategory(params.categories); // you don't need to `await` the `getPostDataByCategory()` as it isn't `async` and doesn't return a Promise.
      
      // No need to create a deep copy: const filteredPosts = JSON.parse(JSON.stringify(posts));
    
      return {
        props: {
          filteredPosts, // filteredPosts is now an array of objects
        },
      };
    }
    

    Now you can map your filteredPosts correctly as you were trying to do:

    {filteredPosts.map((posts) => (
      <li key={posts.slug}>
        <p>{posts.title}</p>
      </li>
    ))}