I am developing a habit-tracking application using React for the frontend and Prisma with a PostgreSQL database for the backend. My app includes the functionality to log daily habits and calculate consecutive days for each habit based on the status of the logs.
Problem:
The consecutive days calculation works correctly when logs are added or updated sequentially without refreshing the page. However, after refreshing the page, when additional logs are added or updated, the consecutive days count does not persist correctly.
For example i add 3 consecutive logs, i refresh the page and then when i try to add the 4th, it says 1.
- Prisma schema:
model DailyLog {
id String @id @default(cuid())
habitId String
date DateTime
status String
consecutiveDays Int
Habit Habit @relation(fields: [habitId], references: [id])
}
model Habit {
id String @id @default(cuid())
userId String
name String
dailyLogs DailyLog[]
}
async function revalidateLogs(habitId) {
const logs = await db.dailyLog.findMany({
where: { habitId },
orderBy: { date: "asc" },
});
let counter = 0;
let previousLogDate = null;
const updates = [];
for (let log of logs) {
const currentDate = new Date(log.date);
if (
previousLogDate &&
(currentDate - previousLogDate) / (1000 * 60 * 60 * 24) > 1
) {
counter = 0;
}
if (log.status === "COMPLETE") {
counter++;
}
updates.push(
db.dailyLog.update({
where: { id: log.id },
data: { consecutiveDays: counter },
})
);
previousLogDate = currentDate;
}
await db.$transaction(updates);
}
The user can add / edit / delete logs from here, and this is also the actual component that renders the logs.
import { nextStatus, styleStatus } from "@/utils/statusHelpers";
import { updateLog, addLog, deleteLog } from "@/utils/habitsApi";
// Component to render each date column
export const DateColumn = ({ dateObj, habits, setHabits, setError }) => {
// TODO [] implement optimistic updates here!
const handleStatusClick = async (habitId, logId, currentStatus) => {
if (!logId) {
addLog(
{
habitId: habitId,
date: dateObj.fullDate,
status: "COMPLETE",
},
setHabits,
setError
);
return;
}
const newStatus = nextStatus(currentStatus);
if (newStatus === "SKIPPED") {
deleteLog(logId, setHabits, setError);
} else {
updateLog({ id: logId, status: newStatus }, setHabits, setError);
}
};
return (
<div className={`flex flex-col text-center flex-shrink-0 w-16`}>
<div
className={`h-16 flex flex-col justify-center ${
dateObj.isToday &&
"bg-gradient-to-t from-transparent from-35% to-[#3b82f630] to-100% rounded-t-full"
}`}
>
<div className="font-light text-sm">{dateObj.day}</div>
<div
className={`font-medium text-sm mx-2 ${
dateObj.isToday ? "text-white bg-blue-600 rounded-full" : ""
}`}
>
{dateObj.date}
</div>
</div>
{habits.map((habit) => {
const log = habit.dailyLogs.find(
(log) => log.date.split("T")[0] === dateObj.fullDate.split("T")[0]
);
return (
<div
key={habit.id}
className="text-xs h-16 grid place-content-center cursor-pointer"
onClick={() => handleStatusClick(habit.id, log?.id, log?.status)}
>
<div
style={styleStatus(log?.status, habit.color)}
className="size-8"
>
{log?.status === "COMPLETE" && log?.consecutiveDays}
</div>
</div>
);
})}
</div>
);
};
export default DateColumn;
Thanks in advance!
Yiannis Morfos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.