United States/Tennessee
BlogJanuary 12, 2026

Every Conversation Saved, But Where's My Code? Transparency in MyFinance Refactoring

Doruk Kocausta
Every Conversation Saved, But Where's My Code? Transparency in MyFinance Refactoring
I’m really excited about this one—because, once again, I genuinely feel that I love what I’m doing. Getting to this stage in my project didn’t just happen overnight. It’s a testament to my dedication, the effort I’ve poured in, and how much I want to grow in this field and make a real difference. I didn’t just start where I am now—there was an entire planning phase, wireframing, designing the UI in Figma, and tons of trial and error along the way. If you’re curious, you can see this journey unfold in my earlier articles. The point is, I’m committed to consistently improving myself, my skills, and my knowledge for something bigger. I can genuinely feel that growth happening. I had separate context logic for incomes, outcomes, and investments, but they all did the same thing: fetched from JSON, offered CRUD, exposed state. It was clean, but very repetitive. So I refactored into a single, generic context generator, making my app DRY and scalable.
Typescript
'use client';
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';

type DataKey = 'incomes' | 'outcomes' | 'investments';

// Types import
import { FinanceSource } from '@/types/finance';
import { InvestmentSource } from '@/types/investments';

type ContextDataMap = {
  incomes: FinanceSource[];
  outcomes: FinanceSource[];
  investments: InvestmentSource[];
};

type FinanceGenericContextType<K extends DataKey> = {
  data: ContextDataMap[K];
  setData: React.Dispatch<React.SetStateAction<ContextDataMap[K]>>;
  addSource: (source: ContextDataMap[K][number]) => void;
  updateSource: (source: ContextDataMap[K][number]) => void;
  removeSource: (sourceId: string) => void;
  loading: boolean;
  error: string | null;
};

function createGenericContext<K extends DataKey>(file: K) {
  const Ctx = createContext<FinanceGenericContextType<K> | undefined>(undefined);

  function Provider({ children }: { children: ReactNode }) {
    const [data, setData] = useState<ContextDataMap[K]>([] as ContextDataMap[K]);
    const [loading, setLoading] = useState<boolean>(true);
    const [error, setError] = useState<string | null>(null);

    useEffect(() => {
      fetch(`/data/${file}.json`)
        .then((res) => {
          if (!res.ok) throw new Error(`Could not fetch /data/${file}.json`);
          return res.json();
        })
        .then((data) => {
          setData(data);
          setLoading(false);
        })
        .catch((err) => {
          setError(err.message || `Could not fetch /data/${file}.json`);
          setData([] as ContextDataMap[K]);
          setLoading(false);
        });
    }, [file]);

    const addSource = (source: ContextDataMap[K][number]) =>
      setData((prev) => [...prev, source] as ContextDataMap[K]);
    const updateSource = (updated: ContextDataMap[K][number]) =>
      setData((prev) => prev.map((s) => (s.id === updated.id ? updated : s)) as ContextDataMap[K]);
    const removeSource = (sourceId: string) =>
      setData((prev) => prev.filter((s) => s.id !== sourceId) as ContextDataMap[K]);

    return (
      <Ctx.Provider
        value={{ data, setData, addSource, updateSource, removeSource, loading, error }}
      >
        {children}
      </Ctx.Provider>
    );
  }

  function useGeneric() {
    const ctx = useContext(Ctx);
    if (!ctx) throw new Error('Must be used inside Provider');
    return ctx;
  }

  return [Provider, useGeneric] as const;
}

export const [IncomesProvider, useIncomesContext] = createGenericContext('incomes');
export const [OutcomesProvider, useOutcomesContext] = createGenericContext('out
comes');
export const [InvestmentsProvider, useInvestmentsContext] = createGenericContext('investments');
I noticed I was repeating the same flatten/map/sort logic for incomes, outcomes, and investments. I wanted one function to DRY it up.
Typescript
// flattenPayments - for incomes and outcomes
export function flattenPayments<
  S extends { name: string; payments: P[] },
  P extends { date: string | number },
>(sources: S[]): (P & { sourceName: string })[] {
  return sources
    .flatMap((src) =>
      src.payments.map((p) => ({
        ...p,
        sourceName: src.name,
      })),
    )
    .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
}

// flattenInvestments - for investment data
export function flattenInvestments<
  S extends { name: string; type: string; items: I[] },
  I extends { entryDate: string | number; exitDate?: string | null },
>(sources: S[]): (I & { sourceName: string; sourceType: string })[] {
  return sources
    .flatMap((src) =>
      src.items.map((item) => ({
        ...item,
        sourceName: src.name,
        sourceType: src.type,
      })),
    )
    .sort((a, b) => {
      const dateA = new Date(a.exitDate ?? a.entryDate).getTime();
      const dateB = new Date(b.exitDate ?? b.entryDate).getTime();
      return dateB - dateA;
    });
}
My old component had legacy types and a confusing interface left over from old work with. Now, it's crystal clear and straightforward.
Typescript
'use client';
import React from 'react';
import TotalRow from './TotalRow';

type RecentSideInfoItem = {
  name: string;
  amount: number;
  date: string | number;
};

type RecentSideInfoProps = {
  header: string;
  items: RecentSideInfoItem[];
  className?: string;
};

export default function RecentSideInfo({ header, items, className = '' }: RecentSideInfoProps) {
  const total = items.reduce((sum, item) => sum + Number(item.amount), 0);

  return (
    <div className={`flex-1 w-full ${className}`}>
      <div className="w-full bg-[#3A4483]/75 rounded-[16px] p-1 flex flex-col items-center shadow-lg">
        <h3 className="text-white font-bold text-l xs:text-xl mb-2 text-center">{header}</h3>
        <div className="w-full h-1 my-2 bg-[#29388A] rounded" />
        <div className="w-full flex flex-col">
          {items.length === 0 && (
            <div className="w-full text-center text-white opacity-60 py-5">No items</div>
          )}
          {items.map((item, idx) => (
            <React.Fragment key={item.name + '-' + item.date}>
              <div className="w-full flex flex-row justify-between items-center py-2 gap-1">
                <span className="text-white">{item.name}</span>
                <div className="flex flex-col md:flex-row">
                  <span className="mt-0.5 bg-[#29388A] bg-opacity-60 border border-[#29388A] rounded px-2 py-0.5 font-bold text-[#a9deff] text-s xs:text-xl shadow-inner">
                    {Number(item.amount).toLocaleString(undefined, {
                      minimumFractionDigits: 2,
                    })}
                    $
                  </span>
                  <span className="mt-0.5 bg-[#29388A] bg-opacity-60 border border-[#29388A] rounded px-2 py-0.5 font-bold text-[#a9deff] text-s xs:text-xl shadow-inner">
                    {item.date
                      ? new Date(item.date).toLocaleDateString()
                      : ''}
                  </span>
                </div>
              </div>
              {idx < items.length - 1 && (
                <div className="h-0.5 bg-[#29388A] opacity-60 rounded"></div>
              )}
            </React.Fragment>
          ))}
        </div>
        <div className="w-full h-1 my-2 bg-[#29388A] rounded" />
        <TotalRow total={total} />
      </div>
    </div>
  );
}
Lesson learned:
Conversations are important for working through new solutions, but your code shouldn’t live only inside chat threads or sidebar notes. Your context and code snippets should be versioned, documented, and always attached to their explanation right here, in your repo or blog.
That’s why I post all these code explanations step by step under my blog and portfolio.
It helps me (and anyone who peeks in) track my reasoning, solutions, and improvements.
So going forward, I’ll always keep these paired: real conversation, real code, and real documentation—right by each other.
Thanks for following along!
Share this post:
On this page