Typing a group by year reduce function with generics
In my most recent rebuild of my personal site here, I decided I wanted to enable strict TS and group content by years on their respective pages.
To start, here is the reducer function that takes in an array of data and groups items by their year.
js: groupByYear.js
const groupByYear = (arr) =>arr.reduce((acc, current) => {const year = new Date(current.date).getFullYear().toString();if (acc[year] !== undefined) {acc[year].push(current);} else {acc[year] = [current];}return acc;});
My site has a few different interfaces for data. For example posts have title
, description
, date
, and tags
where as the feed only has date
and tags
.
So to make use of this function and have types I needed to make use of generic.
ts: groupByYear.ts
export const groupByYear = <// Take in some type of object as T that// we expect to have an included date property.T extends {date: string;},>(// Not we use the generic to define the expected// data being passed in.arr: Array<T>,) =>arr.reduce((acc, current) => {const year = new Date(current.date).getFullYear().toString();if (acc[year] !== undefined) {acc[year].push(current);} else {acc[year] = [current];}return acc;// Record the return type to be an object of keys// as strings, and an array of our generic T.}, {} as Record<string, Array<T>>);
Usage
Lets create some sample data JSON.
json: data.json
[{"date": "2021-01-03","heading": "Heading Sample One"},{"date": "2021-12-24","heading": "Heading Sample Two"},{"date": "2020-04-28","heading": "Heading Sample Three"}]
Will now import that data JSON, create our interface as IData
and use that interface as our generic type for our groupByYear
function.
ts: main.ts
import data from './data.json'import groupByYear from './groupByYear'interface IData {date: stringheading: string}const dataGroupedByYear = groupByYear<IData>(data);console.log(dataGroupedByYear) // {2020: Array(1), 2021: Array(2)}