Skip to content

Instantly share code, notes, and snippets.

@codingwithchris
Last active March 27, 2024 19:08
Show Gist options
  • Save codingwithchris/a1ca14c4b58303fdb5a3e0b2ca118ce7 to your computer and use it in GitHub Desktop.
Save codingwithchris/a1ca14c4b58303fdb5a3e0b2ca118ce7 to your computer and use it in GitHub Desktop.

What is MVVM

...

Benefits of MVVM

...

Confusing Things about MVVM and our Impl (open questions)

  • When and where is it appropriate to implement MVVM?
    • features should have MVVM structure
  • Is seeing it at a page level appropriate?
  • Sometimes there are presenters elsewhere not labeled as presenters?
  • Examples of how do we test it?
  • If you're creating a .presenter, a .view then you probably also need the .composed right? You wouldn't sometimes see one of those without the others?
    • For example you could have FeatureA.presenter and FeatureA.view but there might not be a FeatureA.composed. The presenter and view could be imported into PageA and composed there An example of this playing out is smart-form and the smart form presenter. Another example is the DataTable and dataTablePresenter in Cove.

Smell Test

TODOL: We want to create a Smell Test to see if we should use MVVM for something

  • Meeting x out of y criteria means you should use MVVM
export const FeatureReservation (id) => {
return (
<ErrorBoundary fallbackComponent={ReservationError}>
<Suspense loading={ReservationLoading}>
<ReservationStateProvider>
<ReservationComposed id={id} />
<ReservationStateProvider>
</Suspense
</ErrorBoundary>
)
}
export const queryReservationDto = (raw: RawDataFromGql) => {
return {
success: {
title: capitalize(raw.appointmentTitle),
descriotion: raw.appointmentOverview,
startDate: new Date(raw.startDate, ''),
startTime: new Date(raw.startDate, 'aa:mm'),
},
notFound: {
...stuff here
}
}
}
export type ReservationDto = ReturnType<queryReservationDto>
export const ReservationComposed ({id}) => {
const onCancelSuccess = () => {
toast.dispatch('Success!')
};
const {isUrgent, toggleIsUrgent, cancelReservation, reservation} = useReservationComposed(id, onCancelSuccess);
if(reservation.isLoading) {
return <ReservationLoading />
}
if(reservation.error) {
return <ReservationError error={reservation.error} />
}
const presenter = reservationPresenter({
isUrgent,
cancelReservation: {
isLoading: cancelReservation.isLoading,
error: cancelReservation.error,
execute: cancelReservation.execute,
},
reservationData: reservation.data
});
return (
<ReservationView {...presenter} />
)
}
const ReservationError = ({error: Error, reset }) => {
return (
<div>
<h1>error.name</h1>
<p>error.message</p>
<button onClick={reset}>try loading reservation again</button>
</div>
)
}
const ReservationLoading = () => {
return (
<Skeleton ...someProps />
)
}
export const reservationPresenter = (props: ReservationPresenterProps): ReservationViewModel => {
return {
vm: {
title: props.reservationData.name,
description: props.reservationData,
startDate: props.startTime,
displayUrgentText: props.isUrgent,
urgentButtonText: props.isUrgent ? 'Disable urgency' : 'Mark as urgent';
},
actions: {
onCancelClick: props.cancelReservation.execute,
onUrgentClick: props.toggleIsUrgent,
},
}
};
interface ReservationPresenterProps {
isUrgent: boolean;
toggleIsUrgent: () => void;
cancelReservation: {
isLoading: boolean
error: Error;
execute: (id) => void
},
reservationData: DataTypeFromDtoMapper
}
const state = {
isUrgent: boolean;
}
const actions = stateActions<A, B>((set) => ({
toggleIsUrgent: () => set((state) => !state),
}))
export [ReservationStateProvider, useReservationState] = createState('Reservation', state, actions);
export const ReservationView = ({vm, actions}) => {
return (
<section>
<h1>{vm.title}</h1> - {vm.displayUrgentText && (<span>URGENT!</span>)}
<h2>{vm.startDate}</h2>
<p>{vm.description}</p>
<button onClick={actions.onUrgentClick}>{vm.urgentButtonText}</button>
<button onClick={actyons.onCancelClick(vm.id)}>Cancel</button>
</section>
)
}
interface ReservationViewModel {
vm: {
id: number; // server state
title: string; // server state
description: string; // server state
startDate: string; // server state
urgentButtonText: string;
displayUrgentText: boolean; // client state
},
actions: {
onUrgentClick: () => void // client state
onCancelClick: (id: number) => void; // server state
},
}
export const useMutateCancelReservation(onCancelSuccess) => {
return useMutation(MUTATE_CANCEL_RESERVATION, {
onComplete: () => onCancelSuccess()
});
}
export const useQueryReservation(id) => {
const [dto, {loading, error}] useQuery(RESERVATION_QUERY);
const programmersModel = reservationDtoToPm(dto);
return [programmersModel, {loading, error}];
}
export const useReservationComposed = (id: number, onCancelSuccess: () => void) => {
const isUrgent = useReservationState((state) => state.isUrgent);
const toggleIsUrgent = useReservationState((state) => state.toggleIsUrgent);
const [mutateCancelReservation, {data: cancelData, loading: cancelLoading, error: cancelError}] = useMutateCancelReservation(onCancelSuccess);
const [data: reservationData , {loading: reservationLoading, error: reservationError}] = useQueryReservation(id);
return {
isUrgent,
toggleIsUrgent,
cancelReservation: {.
execute: mutateCancelReservation,
data: canceData,
isLoading: cancelLoading,
error: cancelError,
},
reservation: {
data: reservationData,
isLoading: loadingReservation,
error: reservationError,
},
};
};
@codingwithchris
Copy link
Author

codingwithchris commented Mar 26, 2024

Still need clarity on:

  • naming for dto thing
  • container organization and usage
    • .composed, .comp ... other words?
  • how to test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment