mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 23:19:09 +00:00
event API refract
using optimistic updates
This commit is contained in:
@@ -1,8 +1,29 @@
|
|||||||
|
import axios from 'axios';
|
||||||
import { serverURL } from './apiConstants';
|
import { serverURL } from './apiConstants';
|
||||||
export const eventsURL = serverURL + 'events';
|
export const eventsNamespace = 'events';
|
||||||
|
export const eventsURL = serverURL + eventsNamespace;
|
||||||
|
|
||||||
export const fetchAllEvents = async () => {
|
export const fetchAllEvents = async () => {
|
||||||
const res = await fetch(eventsURL);
|
const res = await axios.get(eventsURL);
|
||||||
// TODO: Safe json convert
|
return res.data;
|
||||||
return res.json();
|
};
|
||||||
};
|
|
||||||
|
export const requestPost = async (data) => {
|
||||||
|
const res = await axios.post(eventsURL, data);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const requestPut = async (data) => {
|
||||||
|
const res = await axios.put(eventsURL, data);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const requestPatch = async (data) => {
|
||||||
|
const res = await axios.patch(eventsURL, data);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const requestDelete = async (eventId) => {
|
||||||
|
const res = await axios.delete(eventsURL + '/' + eventId);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export default function EditableTimer(props) {
|
|||||||
|
|
||||||
// prepare time fields
|
// prepare time fields
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (time == null) return;
|
||||||
setValue(addAndFormat(time, delay));
|
setValue(addAndFormat(time, delay));
|
||||||
}, [time, delay]);
|
}, [time, delay]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,121 @@
|
|||||||
import { useMutation, useQuery } from 'react-query';
|
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { fetchAllEvents } from '../../../app/api/eventsApi.js';
|
import {
|
||||||
|
eventsNamespace,
|
||||||
|
fetchAllEvents,
|
||||||
|
requestPatch,
|
||||||
|
requestPost,
|
||||||
|
requestPut,
|
||||||
|
requestDelete,
|
||||||
|
} from '../../../app/api/eventsApi.js';
|
||||||
import EventList from './EventList';
|
import EventList from './EventList';
|
||||||
import EventListMenu from '../../menu/EventListMenu.jsx';
|
import EventListMenu from '../../menu/EventListMenu.jsx';
|
||||||
import axios from 'axios';
|
|
||||||
import { showErrorToast } from '../../../common/helpers/toastManager';
|
import { showErrorToast } from '../../../common/helpers/toastManager';
|
||||||
import { eventsURL } from '../../../app/api/eventsApi';
|
|
||||||
import { Skeleton } from '@chakra-ui/skeleton';
|
import { Skeleton } from '@chakra-ui/skeleton';
|
||||||
import style from './List.module.css';
|
import style from './List.module.css';
|
||||||
|
|
||||||
export default function EventListWrapper() {
|
export default function EventListWrapper() {
|
||||||
const { data, status, isError, refetch } = useQuery('events', fetchAllEvents);
|
const queryClient = useQueryClient();
|
||||||
const addEvent = useMutation((data) => axios.post(eventsURL, data));
|
const { data, status, isError, refetch } = useQuery(
|
||||||
const updateEvent = useMutation((data) => axios.put(eventsURL, data));
|
eventsNamespace,
|
||||||
const patchEvent = useMutation((data) => axios.patch(eventsURL, data));
|
fetchAllEvents
|
||||||
const deleteEvent = useMutation((eventId) =>
|
|
||||||
axios.delete(eventsURL + '/' + eventId)
|
|
||||||
);
|
);
|
||||||
|
const addEvent = useMutation(requestPost, {
|
||||||
|
// we optimistically update here
|
||||||
|
onMutate: async (newEvent) => {
|
||||||
|
// cancel ongoing queries
|
||||||
|
queryClient.cancelQueries(eventsNamespace);
|
||||||
|
|
||||||
|
// Snapshot the previous value
|
||||||
|
const previousEvents = queryClient.getQueryData(eventsNamespace);
|
||||||
|
|
||||||
|
// optimistically update object, temp ID until refetch
|
||||||
|
queryClient.setQueryData(eventsNamespace, (old) => [
|
||||||
|
...old,
|
||||||
|
{ ...newEvent, id: new Date().toISOString() },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Return a context with the previous and new todo
|
||||||
|
return { previousEvents };
|
||||||
|
},
|
||||||
|
|
||||||
|
// Mutation fails, rollback undos optimist update
|
||||||
|
onError: (error, newEvent, context) => {
|
||||||
|
queryClient.setQueryData(eventsNamespace, context.previousEvents);
|
||||||
|
},
|
||||||
|
// Mutation finished, failed or successful
|
||||||
|
// Fetch anyway, just to be sure
|
||||||
|
onSettled: () => {
|
||||||
|
queryClient.invalidateQueries(eventsNamespace);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const updateEvent = useMutation(requestPut, {
|
||||||
|
// we optimistically update here
|
||||||
|
onMutate: async (newEvent) => {
|
||||||
|
// cancel ongoing queries
|
||||||
|
queryClient.cancelQueries([eventsNamespace, newEvent.id]);
|
||||||
|
|
||||||
|
// Snapshot the previous value
|
||||||
|
const previousEvent = queryClient.getQueryData([
|
||||||
|
eventsNamespace,
|
||||||
|
newEvent.id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// optimistically update object
|
||||||
|
queryClient.setQueryData([eventsNamespace, newEvent.id], newEvent);
|
||||||
|
|
||||||
|
// Return a context with the previous and new todo
|
||||||
|
return { previousEvent, newEvent };
|
||||||
|
},
|
||||||
|
|
||||||
|
// Mutation fails, rollback undos optimist update
|
||||||
|
onError: (error, newEvent, context) => {
|
||||||
|
queryClient.setQueryData(
|
||||||
|
[eventsNamespace, context.newEvent.id],
|
||||||
|
context.previousEvent
|
||||||
|
);
|
||||||
|
},
|
||||||
|
// Mutation finished, failed or successful
|
||||||
|
// Fetch anyway, just to be sure
|
||||||
|
onSettled: (newEvent) => {
|
||||||
|
queryClient.invalidateQueries([eventsNamespace, newEvent.id]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const patchEvent = useMutation(requestPatch, {
|
||||||
|
// we optimistically update here
|
||||||
|
onMutate: async (newEvent) => {
|
||||||
|
// cancel ongoing queries
|
||||||
|
queryClient.cancelQueries([eventsNamespace, newEvent.id]);
|
||||||
|
|
||||||
|
// Snapshot the previous value
|
||||||
|
const previousEvent = queryClient.getQueryData([
|
||||||
|
eventsNamespace,
|
||||||
|
newEvent.id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// optimistically update object
|
||||||
|
queryClient.setQueryData([eventsNamespace, newEvent.id], newEvent);
|
||||||
|
|
||||||
|
// Return a context with the previous and new todo
|
||||||
|
return { previousEvent, newEvent };
|
||||||
|
},
|
||||||
|
|
||||||
|
// Mutation fails, rollback undos optimist update
|
||||||
|
onError: (error, newEvent, context) => {
|
||||||
|
queryClient.setQueryData(
|
||||||
|
[eventsNamespace, context.newEvent.id],
|
||||||
|
context.previousEvent
|
||||||
|
);
|
||||||
|
},
|
||||||
|
// Mutation finished, failed or successful
|
||||||
|
// Fetch anyway, just to be sure
|
||||||
|
onSettled: (newEvent) => {
|
||||||
|
queryClient.invalidateQueries([eventsNamespace, newEvent.id]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteEvent = useMutation(requestDelete);
|
||||||
|
|
||||||
// Show toasts on errors
|
// Show toasts on errors
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -31,31 +130,29 @@ export default function EventListWrapper() {
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case 'add':
|
case 'add':
|
||||||
try {
|
try {
|
||||||
await addEvent.mutateAsync(payload).then((needsRefetch = true));
|
await addEvent.mutateAsync(payload);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error creating event', error.message);
|
showErrorToast('Error creating event', error.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'update':
|
case 'update':
|
||||||
try {
|
try {
|
||||||
await updateEvent.mutateAsync(payload).then((needsRefetch = true));
|
await updateEvent.mutateAsync(payload);
|
||||||
// TODO: instead of refetching, update the item here
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error updating event', error.message);
|
showErrorToast('Error updating event', error.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'patch':
|
case 'patch':
|
||||||
try {
|
try {
|
||||||
await patchEvent.mutateAsync(payload).then((needsRefetch = true));
|
await patchEvent.mutateAsync(payload);
|
||||||
// TODO: instead of refetching, update the item here
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error updating event', error.message);
|
showErrorToast('Error updating event', error.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
|
// TODO: could do optimistic update here?
|
||||||
try {
|
try {
|
||||||
await deleteEvent.mutateAsync(payload).then((needsRefetch = true));
|
await deleteEvent.mutateAsync(payload).then((needsRefetch = true));
|
||||||
needsRefetch = true;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast('Error deleting event', error.message);
|
showErrorToast('Error deleting event', error.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ exports.eventsPatch = async (req, res) => {
|
|||||||
const newEvents = replaceAt(events, itemIndex, updatedEvent);
|
const newEvents = replaceAt(events, itemIndex, updatedEvent);
|
||||||
|
|
||||||
events = [...newEvents];
|
events = [...newEvents];
|
||||||
res.sendStatus(200);
|
res.send(updatedEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for DELETE request to '/events/'
|
// Create controller for DELETE request to '/events/'
|
||||||
@@ -128,8 +128,7 @@ exports.eventsDelete = async (req, res) => {
|
|||||||
if (itemIndex === -1) {
|
if (itemIndex === -1) {
|
||||||
res.sendStatus(400);
|
res.sendStatus(400);
|
||||||
return;
|
return;
|
||||||
}
|
} else if (itemIndex === 0) events.shift();
|
||||||
else if (itemIndex === 0) events.shift();
|
|
||||||
else events.splice(itemIndex, 1);
|
else events.splice(itemIndex, 1);
|
||||||
|
|
||||||
// Update events
|
// Update events
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ const event = {
|
|||||||
title: '',
|
title: '',
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
presenter: '',
|
presenter: '',
|
||||||
timeStart: new Date(),
|
timeStart: null,
|
||||||
timeEnd: new Date(),
|
timeEnd: null,
|
||||||
clockStarted: null,
|
clockStarted: null,
|
||||||
type: 'event',
|
type: 'event',
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user