Closed TheTushar2696 closed 2 years ago
Can you provide your code? Especially the onDragEvent callback.
It seems the DB is not being properly updated in onDragEvent
. The expected behavior: user drops the event --> the box snaps and aligns with the day
const showAlertOnUpdate = (event, newStartDate, newEndDate, objectToUpdate) => {
Alert.alert(t('task.saveChangesFor'), undefined, [
{
text: t('task.onlyThisEvent'),
onPress: () => {
roundOffTimeWhenDraggedLessThan15Minutes(event, newStartDate, newEndDate, false)
},
},
{
text: t('task.allEvents'),
onPress: () => {
roundOffTimeWhenDraggedLessThan15Minutes(event, newStartDate, newEndDate, true)
},
},
{
text: t('cancel'),
onPress: () =>
updateLocalState(event, event.startDate, event.endDate, objectToUpdate, false, false),
},
])
return
}
const updateLocalState = (
event,
newStartDate,
newEndDate,
objectToUpdate,
allEvents,
callApi,
) => {
let updatedWeekViewTask = []
setCalendarLoading(true)
if (allEvents) {
// let recurringTasksInLocalState = weekViewTasks.filter(recurringTask => {
// return event.recurringTaskId === recurringTask.recurringTaskId
// })
let timeDiff = event.startDate - newStartDate
console.log(timeDiff, 'trtr')
for (let i = 0; i < weekViewTasks.length; i++) {
if (event.recurringTaskId === weekViewTasks[i].recurringTaskId) {
weekViewTasks[i].startDate = new Date(weekViewTasks[i].startDate.getTime() + timeDiff)
weekViewTasks[i].endDate = new Date(
weekViewTasks[i].endDate.getTime() - weekViewTasks[i].startDate.getTime() + timeDiff,
)
}
updatedWeekViewTask.push(weekViewTasks[i])
}
} else {
for (let i = 0; i < weekViewTasks.length; i++) {
if (event.id === weekViewTasks[i].id) {
weekViewTasks[i].startDate = newStartDate
weekViewTasks[i].endDate = newEndDate
}
updatedWeekViewTask.push(weekViewTasks[i])
}
}
setWeekViewTasks(updatedWeekViewTask)
if (callApi) {
setCalendarLoading(true)
updateEvent(event, newStartDate, newEndDate, allEvents)
}
setCalendarLoading(false)
}
const updateEvent = async (event, newStartDate, newEndDate, allEvents) => {
let tempTask = {
startAtUtc: newStartDate,
endAtUtc: newEndDate,
notifyAtUtc: newStartDate,
}
console.log('klkl', newStartDate, newEndDate)
// setLoading(true)
await updateTaskById(event.id, event.recurringTaskId, tempTask, allEvents, true)
.then(() => {
setCalendarLoading(false)
})
.catch(e => {
setCalendarLoading(false)
setCalendarLoading(false)
handleApiFailure(e)
})
}
const roundOffTimeWhenDraggedLessThan15Minutes = (event, newStartDate, newEndDate, allEvents) => {
// console.log('klkl')
let finalNewStartDate = newStartDate
let finalNewEndDate = newEndDate
// const diffMs = event.startDate - newStartDate
// const diffDays = Math.floor(diffMs / 86400000)
// const diffMins = ((diffMs % 86400000) % 3600000) / 60000
// if (Math.abs(diffDays) === 0) {
// if (Math.abs(diffMins) < 15) {
// let minutesToAdd = Math.round(diffMins)
// finalNewStartDate = new Date(event.startDate.getTime() + minutesToAdd * 60000)
// finalNewEndDate = new Date(event.startDate.getTime() + minutesToAdd * 60000)
// }
// }
updateLocalState(event, finalNewStartDate, finalNewEndDate, {}, allEvents, true)
// updateEvent(event, finalNewStartDate, finalNewEndDate, allEvents)
}
const onDragEvent = (event, newStartDate, newEndDate) => {
if (newEndDate === newStartDate) {
return
}
console.log(event, newStartDate, newEndDate, 'koko')
var objectToUpdate = originalTasks.filter(obj => {
return obj.id === event.id
})
if (objectToUpdate.length) {
if (objectToUpdate[0].recurringTaskId) {
showAlertOnUpdate(event, newStartDate, newEndDate, objectToUpdate)
} else {
updateLocalState(event, newStartDate, newEndDate, objectToUpdate, false, true)
}
}
}
These are the functions, starting from onDrag event
@pdpino Actually what I want to achieve is,let the user drag the event wherever he wants and update the local states and silently call the APis and update the DB whereas if the api fails, restore the position of the event with an error message, and all these calculations leading to this, please suggest a better way to do it, Thanks in advance
@TheTushar2696 what do you provide as events
prop to <WeekView>
?
Is not clear to me by checking the code when that prop gets updated
(Sorry for the long answer)
There are two possible options/scenarios to update the events
prop:
If you are updating a DB locally in the mobile device (e.g. redux, or other store), you can choose to do this syncly. For this scenario, check this code:
const useEvents = (initialEvents = []) => {
const [events, dispatch] = useReducer(
(prevEvents, action) => {
switch (action.type) {
case 'updateEvent':
const {event, newStartDate, newEndDate} = action.payload;
return [
// not the most efficient, probably you will use a library with a more efficient update method
...prevEvents.filter(e => e.id !== event.id),
{
...event,
startDate: newStartDate,
endDate: newEndDate,
},
];
default:
break;
}
return prevEvents;
},
initialEvents,
);
const updateEvent = ({ event, newStartDate, newEndDate }) => dispatch({
type: 'updateEvent',
payload: { event, newStartDate, newEndDate },
});
return {
events,
updateEvent,
};
};
const MyComponent = () => {
const {events, updateEvent} = useEvents();
return (
<WeekView
events={events}
onDragEvent={(event, newStartDate, newEndDate) => {
// Here you must update the event in your DB with the new date and time
updateEvent({ event, newStartDate, newEndDate }) // Sync call that triggers a re-render --> ok
}}
/>
);
}
The interaction looks like this:
updateEvent()
is called syncly --> the event is updated in the DB --> <WeekView>
is re-rendered --> the event is snapped to its actual position (width and left position adjusted accordingly)If you need to call a remote API with a http request, you'll need an async update. In other use cases an async call may also work best for you. (We have mainly tested the component using the sync scenario, so I'm not sure what will work best here)
Ideally, the solution would look something like this:
const useRemoteEvents = (initialEvents = []) => {
const [{events, isLoading}, dispatch] = useReducer(
(prevState, action) => {
const {events: prevEvents} = prevState;
switch (action.type) {
case 'updateEvent':
const {event, newStartDate, newEndDate} = action.payload;
return {
events: [
// again, you probably want to use something lighter
...prevEvents.filter(e => e.id !== event.id),
{
...event,
startDate: newStartDate,
endDate: newEndDate,
},
],
isLoading: false,
};
case 'startLoading':
return {events: prevEvents, isLoading: true};
case 'stopLoading':
return {events: prevEvents, isLoading: false};
default:
break;
}
return prevState;
},
evts => ({events: evts, isLoading: false}),
initialEvents,
);
const updateEvent = ({event, newStartDate, newEndDate}) =>
dispatch({
type: 'updateEvent',
payload: {event, newStartDate, newEndDate},
});
const startLoading = () => dispatch({type: 'startLoading'});
const stopLoading = () => dispatch({type: 'stopLoading'});
return {
events,
isLoading,
startLoading,
stopLoading,
updateEvent,
};
};
const MyComponent = () => {
const {events, isLoading, startLoading, stopLoading, updateEvent: updateLocalDB} = useRemoteEvents();
const onDragEvent = (event, newStartDate, newEndDate) => InteractionManager.runAfterInteractions(async () => {
startLoading(); // let the user know is loading // UI looks jumpy: see problem below
// Here you want to make the async call before updating the DB locally
const result = await updateRemoteDBWithHTTPRequest();
if (result.success) {
updateLocalDB({event, newStartDate, newEndDate});
} else {
stopLoading();
}
});
return (
<>
<WeekView
events={events}
onDragEvent={onDragEvent}
isRefreshing={isLoading} // default feedback
/>
// or provide your own user feedback:
<LoadingSpinner isLoading={isLoading} />
</>
);
}
Problem:
startLoading()
is called: a re-render is triggered --> the event comes back to its original position (loses the drag state)updateLocalDB()
is called --> the event snaps to the new positionAs a workaround, you can apply the update locally, and then roll it back if there is an error with the API
const MyComponent = () => {
const {events, updateEvent: updateLocalDB} = useRemoteEvents();
const onDragEvent = (event, newStartDate, newEndDate) => {
InteractionManager.runAfterInteractions(async () => {
const result = await updateRemoteDBWithHTTPRequest(...);
if (!result.success) {
// rollback change
updateLocalDB({ event, newStartDate: event.startDate, newEndDate: event.endDate });
Alert.alert(`Event ${event.id} cannot be moved`);
}
// or do something more robust: upsert in the local DB the event from the API
});
// for now make the change locally, assume it will be accepted
updateLocalDB({event, newStartDate, newEndDate});
};
return (
<WeekView
events={events}
onDragEvent={onDragEvent}
/>
);
}
Looks like this:
Actually what I want to achieve is,let the user drag the event wherever he wants and update the local states and silently call the APis and update the DB whereas if the api fails, restore the position of the event with an error message
You probably want the second scenario
Lmk if you have any feedback or suggestions, we want to fully support the async scenario as well
Hi @pdpino ,
VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. {"contentLength": 1890, "dt": 13866, "prevDt": 1050}
@TheTushar2696 performance is out of scope for this issue, but I can comment this:
<WeekView>
(see #209)
...prevEvents.filter(...)
and the actual implementation of updateEvent(...)
:
I'm moving the async discussion to #248 and marking the original question as solved (feel free to re-open if needed)
When the user is placing the event in between the lines and releases it the event becomes unresponsive from the right side of the line, Sharing a video of the same.
https://user-images.githubusercontent.com/40525668/179201612-7b04dcd9-c30c-465a-a1e3-f5b99788fc8e.mov