Last active
December 17, 2023 09:41
-
-
Save NazariiShvets/977cfa53e70e5965be6839aefb249ae8 to your computer and use it in GitHub Desktop.
effector-listen
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
/** | |
* Utility for calling imperative effects | |
* without need of creating one | |
* | |
* Awaiting promise instead of effect would lose scope safety | |
* @see https://effector.dev/docs/api/effector/scope/#imperative-effects-calls-with-scope | |
*/ | |
const callFx = createEffect({ | |
name: 'callFx', | |
handler: async <T extends (arg: any) => any>({ | |
fn, | |
params | |
}: { | |
fn: T; | |
params: Parameters<T>[0]; | |
}): Promise<ReturnType<T>> => fn(params) | |
}); | |
/** | |
* Calls provided function with provided params inside effect | |
* | |
* @param fn | |
* @param params | |
*/ | |
const call = <T extends (...args: any[]) => any>( | |
fn: T, | |
params: Parameters<T>[0] | |
): ReturnType<T> => { | |
const result = callFx({ fn, params }); | |
return result as ReturnType<T>; | |
}; | |
export { call } |
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
const delayFx = createEffect({ | |
name: 'delayFx', | |
handler: async (ms: number) => | |
new Promise((resolve) => setTimeout(resolve, ms)) | |
}); | |
export { delay } |
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
const name = config?.name ?? 'unnamed.$$foo' | |
listen({ | |
name: `${name}.onInit`, | |
clock: init, | |
source: { | |
ctx: config.$ctx, | |
paymentMethod: config.$selectedPaymentMethod | |
}, | |
handler: async (_, { ctx, paymentMethod }) => { | |
try { | |
invariant.error(paymentMethod, 'Payment method is not selected'); | |
if (!paymentMethod.isPrefill) { | |
$$requisiteForm.$$form.put( | |
Object.fromEntries( | |
Object.keys(paymentMethod.fields).map((key) => [key, '']) | |
) | |
) | |
return; | |
} | |
const prefillValues = await config.getAssetPaymentMethodPrefillFx({ | |
asset: paymentMethod.Asset.code, | |
code: paymentMethod.code, | |
type: paymentMethod.transferType | |
}); | |
const values = await getState($$requisiteForm.$$form.$values); | |
$$requisiteForm.$$form.put({ ...values, ...prefillValues }); | |
} catch (e) { | |
ctx.notify.warning( | |
ctx.i18n.t('model.requisite-create.get-prefill.failed') | |
); | |
//re-throw for debugging | |
throw e; | |
} | |
}, | |
debug: import.meta.env.DEV | |
}) |
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
listen({ | |
clock: Gate.open, | |
handler: () => { | |
$$table.$$load.start(); | |
} | |
}); | |
listen({ | |
name: 'Poll reports while in progress', | |
clock: $$table.$$load.done, | |
handler: (rows) => { | |
const boundRemoveIntervalId = scopeBind(removeIntervalId, { | |
safe: true | |
}); | |
let inProgressReportIds = rows | |
.filter((row) => row.status === ReportStatus.InProgress) | |
.map((report) => report.id); | |
if (inProgressReportIds.length === 0) return; | |
const intervalId = setInterval(async () => { | |
const results = await call(findManyReport(SELECT), { | |
take: inProgressReportIds.length, | |
where: { | |
type: { | |
equals: ReportType.Statement | |
}, | |
id: { | |
in: inProgressReportIds | |
}, | |
createdAt: { | |
gte: subDays(startOfToday(), 7) | |
} | |
} | |
}); | |
results | |
.filter((row) => row.status !== ReportStatus.InProgress) | |
.forEach((report) => { | |
$$table.$$row.replaceRow({ | |
comparator: (row) => row.id === report.id, | |
data: report | |
}); | |
}); | |
inProgressReportIds = results | |
.filter((row) => row.status === ReportStatus.InProgress) | |
.map((report) => report.id); | |
if (inProgressReportIds.length === 0) { | |
clearInterval(intervalId); | |
boundRemoveIntervalId(intervalId); | |
} | |
}, minutesToMilliseconds(1)); | |
addIntervalId(intervalId); | |
} | |
}); | |
listen({ | |
clock: Gate.close, | |
source: $intervalIds, | |
handler: (_, intervals) => { | |
intervals.forEach((interval) => clearInterval(interval)); | |
setState($intervalIds, []); | |
$$table.reset(); | |
} | |
}); |
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
/** | |
* Operator for listening events to run imperative effects | |
* | |
* @see https://redux-toolkit.js.org/api/createListenerMiddleware#startlistening | |
*/ | |
function listen<TClock, TSource>(config: ListenConfig<TClock, TSource>) { | |
const $source = | |
config.source ?? | |
createStore(null, { | |
name: `${config.name}.$source`, | |
serialize: 'ignore' | |
}); | |
sample({ | |
clock: config.clock, | |
target: attach({ | |
name: config.name, | |
source: $source, | |
effect: async (source, clock: TClock) => { | |
if (config.debug) { | |
console.log(`[listen](start) ${config.name}`, { clock, source }); | |
} | |
const result = config.handler( | |
clock, | |
source as ListenConfigEffectData<TSource> | |
); | |
if (config.debug) { | |
if (result instanceof Promise) { | |
result | |
.then((value) => | |
console.log(`[listen](done) ${config.name}`, { | |
clock, | |
source, | |
result: value | |
}) | |
) | |
.catch((error) => | |
console.log(`[listen](error) ${config.name}`, { | |
clock, | |
source, | |
error | |
}) | |
); | |
} else { | |
console.log(`[listen](done) ${config.name}`, { | |
clock, | |
source, | |
result | |
}); | |
} | |
} | |
} | |
}) | |
}); | |
} | |
export { listen } |
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
type ListenConfig<TClock, TSource> = { | |
/** | |
* Name of the handler | |
*/ | |
name?: string; | |
/** | |
* When this event is triggered, the effect will be run | |
*/ | |
clock: Event<TClock>; | |
/** | |
* Custom data for the effect | |
*/ | |
source?: TSource; | |
/** | |
* Effect to run | |
* | |
* @note Name `handler` is used to avoid name collision with `effect` because effector-babel-plugin goes nuts | |
*/ | |
handler: ( | |
clock: TClock, | |
data: ListenConfigEffectData<TSource> | |
) => void | Promise<void>; | |
debug?: boolean; | |
}; | |
type ListenConfigEffectData<TSource> = TSource extends Record< | |
string, | |
Store<any> | |
> | |
? GetShapeValue<TSource> | |
: TSource extends Store<any> | |
? GetShapeValue<TSource> | |
: void; | |
export { ListenConfig, ListenConfigEffectData } |
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
/** | |
* Handy utility for imperative setting store state | |
*/ | |
const setState = <TValue>(store: Store<TValue>, value: TValue) => { | |
launch({ target: store, params: value }); | |
}; | |
const getStateFx = createEffect(<T>(store: Store<T>) => store.getState()) | |
async function getState<TValue>(store: Store<TValue>): Promise<TValue> { | |
const value = await getStateFx(store); | |
return value | |
} | |
export { setState, getState } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment