Created
October 7, 2021 19:05
-
-
Save matiasfha/f4c1f2c1c8f15a38b86a4fa74feaf8eb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { toast, ToastTypes } from 'components/Toast'; | |
import { | |
useCancel, | |
useStart | |
} from 'hooks'; | |
import { | |
ActionFunctionMap, | |
ActionObject, | |
assign, | |
ConditionPredicate, | |
createMachine, | |
InvokeConfig, | |
MachineConfig, | |
ServiceConfig, | |
StateNodeConfig, | |
} from 'xstate'; | |
import { Context, Events, Statuses } from './types'; | |
const getProcessingState = (): StateNodeConfig<Context, any, Events, ActionObject<Context, Events>> => { | |
const invoke: InvokeConfig<Context, Events> = { | |
src: 'processing', | |
onDone: { | |
target: '#status.unknown', //After processing/mutating the state with RQ jump into the transient state | |
actions: assign({ | |
job: (_, event) => { | |
return event.data; | |
}, | |
}), | |
}, | |
onError: { | |
target: '#status.failed', | |
actions: assign({ | |
error: (_, event) => { | |
return event.data.response.errors[0].message; | |
}, | |
}), | |
}, | |
}; | |
return { | |
invoke, | |
}; | |
}; | |
// A hierarchical machine to the cancellation state and actions | |
const getCancelledMachine = ({ prevState }: { prevState: string }) => { | |
return { | |
setCancelled: getProcessingState(), | |
cancelModalShow: { | |
on: { | |
TOCANCELLED: 'setCancelled', | |
BACK: prevState, | |
}, | |
}, | |
}; | |
}; | |
const preProdMachine: MachineConfig<Context, any, Events> = { | |
id: 'preProdMachine', | |
initial: 'preProd', | |
states: { | |
preProd: { | |
on: { | |
GET_DATE: 'getDate', | |
TOCANCELLED: 'cancelModalShow', | |
}, | |
}, | |
getDate: { | |
on: { | |
BACK: 'preProd', | |
TOINPROGRESS: 'setStartDate', | |
}, | |
}, | |
setStartDate: getProcessingState(), | |
...getCancelledMachine({ prevState: 'preProd' }), | |
}, | |
}; | |
export const getMachineGuards = (): Record<string, ConditionPredicate<Context, Events>> => { | |
return { | |
isQueued: (context) => context.job?.status === Statuses.QUEUE, | |
isCanceled: (context) => context.job?.status === Statuses.CANCELLED, | |
isPreprod: (context) => { | |
return context.job?.status === Statuses.PREPROD; | |
}, | |
isInProgress: (context) => context.job?.status === Statuses.PROGRESS, | |
}; | |
}; | |
export const getMachineServices = (id: string): Record<string, ServiceConfig<Context, Events>> => { | |
const { mutateAsync: cancel } = useCancel(id); | |
const { mutateAsync: start } = useStart(id); | |
const services = { | |
processing: (context: Context, event: Events) => { | |
if (event.type === 'TOCANCELLED') { | |
return cancel({ jobId: context.entity.id }); | |
} | |
if (event.type === 'TOINPROGRESS') { | |
return start({ jobId: context.entity.id, startDate: event.date }); | |
} | |
return Promise.reject('Not implemented'); | |
}, | |
}; | |
return services; | |
}; | |
export const getStatusMachine = (entity: Entity): MachineConfig<Context, any, Events> => ({ | |
id: 'status', | |
initial: 'unknown', | |
context: { | |
entity, | |
error: null, | |
}, | |
/* This is a "global" event, meaning that, no matter in what state the machine is, the UPDATE event can be trigger | |
every time this event is sent, it will update the machine state to `unknown` target and will update the context through the action*/ | |
on: { | |
UPDATE: { | |
target: 'unknown', | |
actions: ['updateContext'], | |
}, | |
}, | |
states: { | |
/* This is the default state, at start (or refresh) the machine don't know in what state start | |
it depends on the Job entity, so this state will `always` move to another state immediately based on the `guards` described | |
*/ | |
unknown: { | |
always: [ | |
{ target: Statuses.CANCELLED, cond: 'isCanceled' }, | |
{ target: Statuses.PREPROD, cond: 'isPreprod' }, | |
{ target: Statuses.PROGRESS, cond: 'isInProgress' }, | |
], | |
}, | |
/**Triggering promises is an state it self */ | |
failed: { | |
entry: ['errorNotification'], // trigger an action on entry | |
}, | |
[Statuses.PREPROD]: { | |
...preProdMachine, | |
}, | |
[Statuses.CANCELLED]: { | |
type: 'final', | |
}, | |
[Statuses.COMPLETE]: { | |
type: 'final', | |
}, | |
}, | |
}); | |
export const getMachineActions = (): ActionFunctionMap<Context, Events, ActionObject<Context, Events>> => { | |
return { | |
errorNotification: (context) => { | |
alert(context.error as string); | |
}, | |
updateContext: assign({ | |
entity: (_, event) => { | |
if (event.type === 'UPDATE') { | |
return event.entity; | |
} | |
}, | |
}), | |
}; | |
}; | |
export const getCreatedMachine = ({ | |
job, | |
...rest | |
}: { | |
job: Entity; | |
services?: Record<string, ServiceConfig<Context, Events>>; | |
guards?: Record<string, ConditionPredicate<Context, Events>>; | |
actions?: ActionFunctionMap<Context, Events, ActionObject<Context, Events>>; | |
}) => { | |
const { | |
services = getMachineServices(entity.id), | |
guards = getMachineGuards(), | |
actions = getMachineActions(), | |
} = rest; | |
const machine = createMachine<Context, Events>(getStatusMachine(job), { | |
actions, | |
guards, | |
services, | |
}).withContext({ entity, error: null }); | |
return machine; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
import { send } from 'xstate/lib/actionTypes'; | |
import { Info } from './components.styled'; | |
import { StatusMachineContext } from './StatusMachineProvider'; | |
import { useSelectors } from './useSelectors'; | |
export const InfoComponent = () => { | |
const { isQueue, isInProgress, isCancelled, isBid } = useSelectors(); | |
const { statusService } = React.useContext(StatusMachineContext); | |
const { send } = statusService; | |
if (isQueue) { | |
return ( | |
<Info> | |
<h2>Status Info</h2> | |
<h1>Set Start Date</h1> | |
</Info> | |
); | |
} | |
if (isInProgress) { | |
return ( | |
<Info> | |
<h2>Status Info</h2> | |
<h1>In progress</h1> | |
<button onClick={() => send('TOCOMPLETE')}>Complete</button> </Info> | |
); | |
} | |
if (isCancelled) { | |
return ( | |
<Info> | |
<h2>Status Info</h2> | |
<h1>Cancelled</h1> | |
</Info> | |
); | |
} | |
return null; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { assign, createMachine, Interpreter } from 'xstate'; | |
import { useInterpret } from '@xstate/react'; | |
import { getCreatedMachine } from './machineConfig'; | |
import { useLayoutEffect } from 'react'; | |
import { Context, Events, Statuses, Entity } from './types'; | |
import React from 'react'; | |
type StatusService = Interpreter<Context, any, Events, { value: any; context: Context }>; | |
type ContextMachine = { | |
statuses: Statuses[]; | |
statusService: StatusService; | |
}; | |
export const StatusMachineContext = React.createContext<ContextMachine>({ | |
statuses: [], | |
statusService: {} as StatusService, | |
}); | |
export const StatusMachineProvider = ({ entity, children }: { entity: Entity | undefined; children: React.ReactNode }) => { | |
const statusService = useInterpret(getCreatedMachine({ job })); | |
// Refresh machine context when the data change because of RQ | |
useLayoutEffect(() => { | |
if (entity && !statusService.state.done) { | |
statusService.send('UPDATE', { entity }); | |
} | |
}, [entity?.status]); | |
if (!statusService.initialized && !statusService.state?.done) { | |
return null; | |
} | |
const statuses = getStatusesPath(true); | |
return ( | |
<StatusMachineContext.Provider value={{ statusService, statuses }}>{children}</StatusMachineContext.Provider> | |
); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useActor, useSelector } from '@xstate/react'; | |
import { Context, Events, Statuses } from './types'; | |
import { EventObject, State, Typestate } from 'xstate'; | |
import React from 'react'; | |
import { StatusMachineContext } from './StatusMachineProvider'; | |
type StatusState = State<Context, EventObject, Typestate<Context>>; | |
const preprodSelector = (state: StatusState) => state.matches(Statuses.PREPROD); | |
const inProgressSelector = (state: StatusState) => state.matches(Statuses.PROGRESS); | |
const cancelledSelector = (state: StatusState) => state.matches(Statuses.CANCELLED); | |
const preProdGetDateSelector = (state: StatusState) => state.matches({ [Statuses.PREPROD]: 'getDate' }); | |
const progressGetDateSelector = (state: StatusState) => state.matches({ [Statuses.PROGRESS]: 'getDate' }); | |
const cancelledModalSelector = (state: StatusState) => { | |
const inProgress = state.matches({ [Statuses.PROGRESS]: 'cancelModalShow' }); | |
const inQueue = state.matches({ [Statuses.QUEUE]: 'cancelModalShow' }); | |
const inPreprod = state.matches({ [Statuses.PREPROD]: 'cancelModalShow' }); | |
return inProgress || inQueue || inPreprod; | |
}; | |
export const useSelectors = () => { | |
const { statusService } = React.useContext(StatusMachineContext); | |
const isPreprod = useSelector(statusService, preprodSelector); | |
const isInProgress = useSelector(statusService, inProgressSelector); | |
const isProgressGetDate = useSelector(statusService, progressGetDateSelector); | |
const isCancelled = useSelector(statusService, cancelledSelector); | |
const isCancelledModalShow = useSelector(statusService, cancelledModalSelector); | |
return { | |
isPreprod, | |
isInProgress, | |
isProgressGetDate, | |
isCancelledModalShow, | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment