Skip to content

Commit

Permalink
πŸ› Fix: Optimistically render when drag-and-dropping event (#209)
Browse files Browse the repository at this point in the history
* πŸ› Fix: Optimistically render when drag-and-dropping event

Fixes [issue](#203)

In this commit, whenever we drag and drop a someday event to the grid, we now handle:
- Optimistically removing the someday event from the sidebar
- Optimistically inserting the grid event

We also error handle the optimistic flow and revert if needed

* fix: omit someday properties from optimistic event

'order' and 'recurrence' aren't currently needed for a grid event

* refactor (web): add createOptimisticGridEvent function to cleanup saga

splitting things out of the convertSomedayEvent saga will make the saga more readable

* refactor: simplify optimistic event creation

* refactor(web): separate someday and regular event sagas

this is to make it easier to understand sagas work

* refactor(web): create utility saga to use during someday event conversion

* refactor(web): cleanup convertSomedayEvent saga by separating logic into smaller generators

---------

Co-authored-by: Tyler Dane <tyler@switchback.tech>
  • Loading branch information
that-one-arab and tyler-dane authored Jan 3, 2025
1 parent 36ef38a commit 1423ed4
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 129 deletions.
4 changes: 4 additions & 0 deletions packages/web/src/common/types/web.event.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export interface Schema_GridEvent extends Schema_Event {
siblingsCount?: number;
}

export interface Schema_OptimisticEvent extends Schema_Event {
_id: string; // We guarantee that we have an _id for optimistic events, unlike `Schema_Event`
}

export interface Schema_SelectedDates {
startDate: Date;
startTime: SelectOption<string>;
Expand Down
14 changes: 9 additions & 5 deletions packages/web/src/common/utils/event.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import { YEAR_MONTH_DAY_COMPACT_FORMAT } from "@core/constants/date.constants";
import { Categories_Event, Schema_Event } from "@core/types/event.types";
import { Origin, Priorities } from "@core/constants/core.constants";
import { Status } from "@core/errors/status.codes";
import { Response_GetEventsSaga } from "@web/ducks/events/event.types";

import {
Schema_GridEvent,
Schema_OptimisticEvent,
Schema_SomedayEventsColumn,
} from "../types/web.event.types";
import { removeGridFields } from "./grid.util";
import { removeGridProperties } from "./grid.util";
import {
COLUMN_WEEK,
COLUMN_MONTH,
Expand Down Expand Up @@ -191,7 +193,7 @@ export const prepEvtAfterDraftDrop = (
};

export const prepEvtBeforeConvertToSomeday = (draft: Schema_GridEvent) => {
const event = removeGridFields(draft);
const event = removeGridProperties(draft);

if (event.recurrence) {
delete event.recurrence;
Expand All @@ -201,7 +203,7 @@ export const prepEvtBeforeConvertToSomeday = (draft: Schema_GridEvent) => {
};

export const prepEvtBeforeSubmit = (draft: Schema_GridEvent) => {
const _event = removeGridFields({ ...draft });
const _event = removeGridProperties({ ...draft });

const event = {
..._event,
Expand All @@ -211,8 +213,10 @@ export const prepEvtBeforeSubmit = (draft: Schema_GridEvent) => {
return event;
};

export const createOptimisticEvent = (event: Schema_Event) => {
const _event: Schema_Event = {
export const replaceIdWithOptimisticId = (
event: Schema_Event
): Schema_OptimisticEvent => {
const _event: Schema_OptimisticEvent = {
...event,
_id: `${ID_OPTIMISTIC_PREFIX}-${uuidv4()}`,
};
Expand Down
11 changes: 8 additions & 3 deletions packages/web/src/common/utils/grid.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,17 +488,22 @@ const normalizeDayNums = (days: number[]) => {
});
};

export const removeGridFields = (event: Schema_GridEvent): Schema_Event => {
export const removeGridProperties = (event: Schema_GridEvent): Schema_Event => {
const {
isEditing,
importanceIndex,
isOpen,
row,
siblingsCount,
...eventWithoutGridFields
...eventWithoutGridProps
} = event;

return eventWithoutGridFields;
return eventWithoutGridProps;
};

export const removeSomedayProperties = (event: Schema_Event): Schema_Event => {
const { order, recurrence, ...eventWithoutSomedayProps } = event;
return eventWithoutSomedayProps;
};

export const widthMinusPadding = (width: number) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/web/src/ducks/events/event.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Schema_Event,
} from "@core/types/event.types";
import { CompassApi } from "@web/common/apis/compass.api";
import { AxiosPromise } from "axios";

const EventApi = {
create: (event: Schema_Event) => {
Expand All @@ -17,7 +18,7 @@ const EventApi = {
_id: string,
event: Schema_Event,
params: { applyTo?: Categories_Recur }
) => {
): AxiosPromise<Schema_Event> => {
if (params?.applyTo) {
return CompassApi.put(`/event/${_id}?applyTo=${params.applyTo}`, event);
}
Expand Down
124 changes: 8 additions & 116 deletions packages/web/src/ducks/events/sagas/event.sagas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { normalize } from "normalizr";
import dayjs from "dayjs";
import { call, put, takeLatest, select } from "@redux-saga/core/effects";
import { call, put, select } from "@redux-saga/core/effects";
import { Params_Events, Schema_Event } from "@core/types/event.types";
import { YEAR_MONTH_DAY_FORMAT } from "@core/constants/date.constants";
import { RootState } from "@web/store";
Expand All @@ -10,7 +10,7 @@ import { EventApi } from "@web/ducks/events/event.api";
import { selectEventById } from "@web/ducks/events/selectors/event.selectors";
import { selectPaginatedEventsBySectionType } from "@web/ducks/events/selectors/util.selectors";
import {
createOptimisticEvent,
replaceIdWithOptimisticId,
handleError,
} from "@web/common/utils/event.util";
import { Schema_GridEvent } from "@web/common/types/web.event.types";
Expand All @@ -25,7 +25,6 @@ import {
} from "../slices/event.slice";
import { getWeekEventsSlice } from "../slices/week.slice";
import {
Action_ConvertSomedayEvent,
Action_ConvertTimedEvent,
Action_CreateEvent,
Action_DeleteEvent,
Expand All @@ -38,56 +37,13 @@ import {
Entities_Event,
} from "../event.types";
import { getSomedayEventsSlice } from "../slices/someday.slice";
import { Action_Someday_Reorder } from "../slices/someday.slice.types";
import {
insertOptimisticEvent,
normalizedEventsSchema,
replaceOptimisticId,
} from "./event.saga.util";
} from "./saga.util";

function* convertSomedayEvent({ payload }: Action_ConvertSomedayEvent) {
try {
const { _id, updatedFields } = payload;

const currEvent = (yield select((state: RootState) =>
selectEventById(state, _id)
)) as Response_GetEventsSaga;

const updatedEvent = { ...currEvent, ...updatedFields };
delete updatedEvent.order;
delete updatedEvent.recurrence;

const res = yield call(EventApi.edit, _id, updatedEvent);
const event = res.data as Schema_Event;
yield put(getWeekEventsSlice.actions.insert(event._id));

const normalizedEvent = normalize<Schema_Event>(
event,
normalizedEventsSchema()
);
yield put(
eventsEntitiesSlice.actions.insert(normalizedEvent.entities.events)
);

const somedayEvents: Response_GetEventsSaga = (yield select(
(state: RootState) => selectPaginatedEventsBySectionType(state, "someday")
)) as Response_GetEventsSaga;

const remainingSomedayEvents = somedayEvents.data.filter(
(id) => id !== _id
);
yield put(
getSomedayEventsSlice.actions.success({
data: remainingSomedayEvents,
})
);
} catch (error) {
yield put(getSomedayEventsSlice.actions.error());
handleError(error as Error);
}
}

function* convertTimedEvent({ payload }: Action_ConvertTimedEvent) {
export function* convertTimedEvent({ payload }: Action_ConvertTimedEvent) {
try {
const res = yield call(EventApi.edit, payload.event._id, payload.event);
const event = res.data as Schema_Event;
Expand Down Expand Up @@ -118,8 +74,8 @@ function* convertTimedEvent({ payload }: Action_ConvertTimedEvent) {
}
}

function* createEvent({ payload }: Action_CreateEvent) {
const event = createOptimisticEvent(payload);
export function* createEvent({ payload }: Action_CreateEvent) {
const event = replaceIdWithOptimisticId(payload);
const optimisticId = event._id;

try {
Expand Down Expand Up @@ -163,18 +119,6 @@ export function* deleteEvent({ payload }: Action_DeleteEvent) {
}
}

export function* deleteSomedayEvent({ payload }: Action_DeleteEvent) {
try {
yield put(eventsEntitiesSlice.actions.delete(payload));

yield call(EventApi.delete, payload._id);
} catch (error) {
yield put(getSomedayEventsSlice.actions.error());
handleError(error as Error);
yield put(getSomedayEventsSlice.actions.request());
}
}

export function* editEvent({ payload }: Action_EditEvent) {
const { _id, applyTo, event, shouldRemove } = payload;

Expand All @@ -193,7 +137,7 @@ export function* editEvent({ payload }: Action_EditEvent) {
}
}

function* getCurrentMonthEvents({ payload }: Action_GetPaginatedEvents) {
export function* getCurrentMonthEvents({ payload }: Action_GetPaginatedEvents) {
try {
const startDate = dayjs().startOf("month").format(YEAR_MONTH_DAY_FORMAT);
const endDate = dayjs().endOf("month").format(YEAR_MONTH_DAY_FORMAT);
Expand Down Expand Up @@ -238,31 +182,7 @@ function* getEvents(
}
}

export function* getSomedayEvents({ payload }: Action_GetEvents) {
try {
const res = (yield call(EventApi.get, {
someday: true,
startDate: payload.startDate,
endDate: payload.endDate,
})) as Response_GetEventsSuccess;

const normalizedEvents = normalize<Schema_Event>(res.data, [
normalizedEventsSchema(),
]);
yield put(
eventsEntitiesSlice.actions.insert(normalizedEvents.entities.events)
);

const data = {
data: normalizedEvents.result as Payload_NormalizedAsyncAction,
};
yield put(getSomedayEventsSlice.actions.success(data));
} catch (error) {
yield put(getSomedayEventsSlice.actions.error());
}
}

function* getWeekEvents({ payload }: Action_GetEvents) {
export function* getWeekEvents({ payload }: Action_GetEvents) {
try {
const data = (yield call(getEvents, payload)) as Response_GetEventsSaga;
yield put(getWeekEventsSlice.actions.success(data));
Expand All @@ -271,31 +191,3 @@ function* getWeekEvents({ payload }: Action_GetEvents) {
handleError(error as Error);
}
}

function* reorderSomedayEvents({ payload }: Action_Someday_Reorder) {
try {
yield call(EventApi.reorder, payload);
} catch (error) {
yield put(getSomedayEventsSlice.actions.error());
handleError(error as Error);
}
}

/************
* Assemble
***********/
export function* eventsSagas() {
yield takeLatest(getWeekEventsSlice.actions.request, getWeekEvents);
yield takeLatest(getWeekEventsSlice.actions.convert, convertTimedEvent);
yield takeLatest(
getCurrentMonthEventsSlice.actions.request,
getCurrentMonthEvents
);
yield takeLatest(getSomedayEventsSlice.actions.convert, convertSomedayEvent);
yield takeLatest(getSomedayEventsSlice.actions.request, getSomedayEvents);
yield takeLatest(getSomedayEventsSlice.actions.delete, deleteSomedayEvent);
yield takeLatest(getSomedayEventsSlice.actions.reorder, reorderSomedayEvents);
yield takeLatest(createEventSlice.actions.request, createEvent);
yield takeLatest(editEventSlice.actions.request, editEvent);
yield takeLatest(deleteEventSlice.actions.request, deleteEvent);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { schema } from "normalizr";
import { put } from "redux-saga/effects";
import { put, select } from "redux-saga/effects";
import { normalize } from "normalizr";
import { Schema_Event } from "@core/types/event.types";
import { Schema_GridEvent } from "@web/common/types/web.event.types";
import { RootState } from "@web/store";

import { getSomedayEventsSlice } from "../slices/someday.slice";
import { getWeekEventsSlice } from "../slices/week.slice";
import { eventsEntitiesSlice } from "../slices/event.slice";
import { selectEventById } from "../selectors/event.selectors";

export function* getEventById(_id: string) {
const currEvent = (yield select((state: RootState) =>
selectEventById(state, _id)
)) as Schema_Event;
return currEvent;
}

export function* insertOptimisticEvent(
event: Schema_GridEvent,
Expand Down
Loading

0 comments on commit 1423ed4

Please sign in to comment.