Skip to content

Commit

Permalink
refactor(occurrences): unify state provider (#109)
Browse files Browse the repository at this point in the history
* refactor(occurrences): unify state provider

* Cosmetic fixes
  • Loading branch information
domhhv authored Oct 22, 2024
1 parent a8f2752 commit e2b14f3
Show file tree
Hide file tree
Showing 17 changed files with 1,163 additions and 100 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"db:diff": "supabase db diff",
"db:migration:up": "supabase migration up",
"db:migration:new": "supabase migration new",
"db:gen-types": "supabase gen types --lang=typescript --schema public auth storage --local > supabase/database.types.ts"
"db:gen-types": "supabase gen types --lang=typescript --schema public --schema auth --schema storage --local > supabase/database.types.ts"
},
"lint-staged": {
"**/*.{ts,tsx}": [
Expand Down
13 changes: 5 additions & 8 deletions src/components/calendar/DayHabitModalDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useOccurrences, useHabits, useSnackbar } from '@context';
import { useOccurrences, useHabits } from '@context';
import {
Button,
Modal,
Expand All @@ -24,7 +24,6 @@ const DayHabitModalDialog = ({
onClose,
date,
}: DayHabitModalDialogProps) => {
const { showSnackbar } = useSnackbar();
const { habits } = useHabits();
const user = useUser();
const { addOccurrence, addingOccurrence } = useOccurrences();
Expand All @@ -37,6 +36,10 @@ const DayHabitModalDialog = ({
const handleSubmit: MouseEventHandler<HTMLButtonElement> = async (event) => {
event.preventDefault();

if (!user) {
return null;
}

const addOccurrences = selectedHabitIds.map((id) => {
return addOccurrence({
day: date.toISOString().split('T')[0],
Expand All @@ -49,12 +52,6 @@ const DayHabitModalDialog = ({

await Promise.all(addOccurrences);

showSnackbar('Habit entries are added to the calendar', {
color: 'success',
dismissible: true,
dismissText: 'Done',
});

handleClose();
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const useAuthSearchParams = () => {
}

setSearchParams({});
}, [showSnackbar, searchParams, setSearchParams]);
}, [showSnackbar, searchParams, setSearchParams]); // eslint-disable-line react-hooks/exhaustive-deps
};

export default useAuthSearchParams;
7 changes: 4 additions & 3 deletions src/context/Habits/HabitsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,9 @@ const HabitsProvider = ({ children }: { children: ReactNode }) => {
await deleteFile(StorageBuckets.HABIT_ICONS, iconPath);
}

const nextHabits = habits.filter((habit) => habit.id !== id);
setHabits(nextHabits);
setHabits((prevHabits) => {
return prevHabits.filter((habit) => habit.id !== id);
});

showSnackbar('Your habit has been deleted.', {
dismissible: true,
Expand All @@ -174,7 +175,7 @@ const HabitsProvider = ({ children }: { children: ReactNode }) => {
setHabitIdBeingDeleted(null);
}
},
[habits, showSnackbar]
[showSnackbar]
);

const value = React.useMemo(() => {
Expand Down
3 changes: 1 addition & 2 deletions src/context/Occurrences/OccurrencesContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { OccurrencesDateMap } from '@models';
import { type OccurrencesInsert } from '@services';
import type { OccurrencesDateMap, OccurrencesInsert } from '@models';
import React from 'react';

import { type OccurrenceFilters } from './OccurrencesProvider';
Expand Down
80 changes: 49 additions & 31 deletions src/context/Occurrences/OccurrencesProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import {
useHabits,
useTraits,
} from '@context';
import { cacheOccurrence, uncacheOccurrence } from '@helpers';
import { useDataFetch } from '@hooks';
import type { Occurrence, OccurrencesDateMap } from '@models';
import type {
Occurrence,
OccurrencesDateMap,
OccurrencesInsert,
} from '@models';
import {
createOccurrence,
destroyOccurrence,
listOccurrences,
type OccurrencesInsert,
} from '@services';
import { cache } from '@utils';
import { getErrorMessage } from '@utils';
import React, { type ReactNode } from 'react';

type Props = {
Expand Down Expand Up @@ -45,11 +49,23 @@ const OccurrencesProvider = ({ children, rangeStart, rangeEnd }: Props) => {
});

const fetchOccurrences = React.useCallback(async () => {
setFetchingOccurrences(true);
const result = await listOccurrences([rangeStart, rangeEnd]);
setFetchingOccurrences(false);
setAllOccurrences(result);
}, [rangeStart, rangeEnd]);
try {
setFetchingOccurrences(true);
setAllOccurrences(await listOccurrences([rangeStart, rangeEnd]));
} catch (error) {
console.error(error);
showSnackbar(
'Something went wrong while fetching your habit entries. Please try reloading the page.',
{
description: `Error details: ${getErrorMessage(error)}`,
color: 'danger',
dismissible: true,
}
);
} finally {
setFetchingOccurrences(false);
}
}, [rangeStart, rangeEnd, showSnackbar]);

const clearOccurrences = React.useCallback(() => {
setOccurrences([]);
Expand Down Expand Up @@ -117,22 +133,29 @@ const OccurrencesProvider = ({ children, rangeStart, rangeEnd }: Props) => {

const nextOccurrence = await createOccurrence(occurrence);

cacheOccurrence([rangeStart, rangeEnd], nextOccurrence);

setAllOccurrences((prevOccurrences) => [
...prevOccurrences,
nextOccurrence,
]);

cache.set([rangeStart, rangeEnd].toString(), [
...cache.get([rangeStart, rangeEnd].toString()),
nextOccurrence,
]);
} catch (e) {
showSnackbar('Something went wrong while adding your habit', {
color: 'danger',
showSnackbar('Habit entry(s) are added to the calendar', {
color: 'success',
dismissible: true,
dismissText: 'Done',
});
} catch (error) {
showSnackbar(
'Something went wrong while adding your habit entry. Please try again.',
{
description: `Error details: ${getErrorMessage(error)}`,
color: 'danger',
dismissible: true,
}
);

console.error(e);
console.error(error);
} finally {
setAddingOccurrence(false);
}
Expand All @@ -153,23 +176,20 @@ const OccurrencesProvider = ({ children, rangeStart, rangeEnd }: Props) => {
});
});

const cachedOccurrences = cache.get([rangeStart, rangeEnd].toString());

cache.set(
[rangeStart, rangeEnd].toString(),
cachedOccurrences.filter((occurrence: Occurrence) => {
return occurrence.id !== id;
})
);
uncacheOccurrence([rangeStart, rangeEnd], id);

showSnackbar('Your habit entry has been deleted from the calendar.', {
dismissible: true,
});
} catch (error) {
showSnackbar('Something went wrong while removing your habit entry', {
color: 'danger',
dismissible: true,
});
showSnackbar(
'Something went wrong while deleting your habit entry. Please try again.',
{
description: `Error details: ${getErrorMessage(error)}`,
color: 'danger',
dismissible: true,
}
);

console.error(error);
} finally {
Expand All @@ -181,11 +201,9 @@ const OccurrencesProvider = ({ children, rangeStart, rangeEnd }: Props) => {

const removeOccurrencesByHabitId = (habitId: number) => {
setOccurrences((prevOccurrences) => {
const nextOccurrences = prevOccurrences.filter((occurrence) => {
return prevOccurrences.filter((occurrence) => {
return occurrence.habitId !== habitId;
});

return nextOccurrences;
});
};

Expand Down
1 change: 1 addition & 0 deletions src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './supabaseClient';
export * from './occurrencesCache';
35 changes: 35 additions & 0 deletions src/helpers/occurrencesCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type Occurrence } from '@models';

export const occurrencesCache: Map<string, Occurrence[]> = new Map();

export const cacheOccurrences = (
range: [number, number],
occurrences: Occurrence[]
) => {
occurrencesCache.set(range.toString(), occurrences);
};

export const cacheOccurrence = (
range: [number, number],
occurrence: Occurrence
) => {
const cachedOccurrences = occurrencesCache.get(range.toString());

if (cachedOccurrences) {
occurrencesCache.set(range.toString(), [...cachedOccurrences, occurrence]);
}
};

export const uncacheOccurrence = (
range: [number, number],
occurrenceId: number
) => {
const cachedOccurrences = occurrencesCache.get(range.toString());

if (cachedOccurrences) {
occurrencesCache.set(
range.toString(),
cachedOccurrences.filter((o) => o.id !== occurrenceId)
);
}
};
4 changes: 2 additions & 2 deletions src/models/habit.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import type {

import { type Trait } from './trait.model';

type RawHabit = CamelCasedPropertiesDeep<Tables<'habits'>>;
type BaseHabit = CamelCasedPropertiesDeep<Tables<'habits'>>;

export type Habit = RawHabit & {
export type Habit = BaseHabit & {
trait: Pick<Trait, 'name' | 'color'> | null;
};

Expand Down
32 changes: 18 additions & 14 deletions src/models/occurrence.model.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import type { CamelCasedPropertiesDeep } from 'type-fest';

import type { TablesInsert, Tables } from '../../supabase/database.types';

import { type Habit } from './habit.model';
import { type Trait } from './trait.model';

export type Occurrence = {
id: number;
createdAt: string;
updatedAt: string | null;
timestamp: number;
day: string;
time: string | null;
userId: string;
habitId: number;
habit:
| (Pick<Habit, 'name' | 'iconPath'> & {
trait: Pick<Trait, 'id' | 'name' | 'color'> | null;
})
| null;
type BaseOccurrence = CamelCasedPropertiesDeep<Tables<'occurrences'>>;

type OccurrenceHabit = Pick<Habit, 'name' | 'iconPath'>;

type HabitWithTrait = OccurrenceHabit & {
trait: Pick<Trait, 'id' | 'name' | 'color'> | null;
};

export type Occurrence = BaseOccurrence & {
habit: HabitWithTrait | null;
};

type OccurrenceDate = string;
export type OccurrencesDateMap = Record<OccurrenceDate, Occurrence[]>;

export type OccurrencesInsert = CamelCasedPropertiesDeep<
TablesInsert<'occurrences'>
>;
Loading

0 comments on commit e2b14f3

Please sign in to comment.