@rematch/updated
Version:
Rematch plugin for maintaining timestamps when effects are called
116 lines (102 loc) • 2.86 kB
text/typescript
import {
ExtractRematchDispatchersFromEffects,
Models,
Plugin,
Model,
} from '@rematch/core'
export interface UpdatedConfig<T = Date> {
name?: string
blacklist?: string[]
dateCreator?(): T
}
type UpdatedState<TModels extends Models<TModels>, T = Date> = {
[modelName in keyof TModels]: {
[effectName in keyof ExtractRematchDispatchersFromEffects<
TModels[modelName]['effects'],
TModels
>]: T
}
}
interface UpdatedModel<TModels extends Models<TModels>, T = Date>
extends Model<TModels, UpdatedState<TModels, T>> {
reducers: {
onUpdate(
state: UpdatedState<TModels, T>,
payload: { name: string; action: string }
): UpdatedState<TModels, T>
}
}
export interface ExtraModelsFromUpdated<
TModels extends Models<TModels>,
T = Date
> extends Models<TModels> {
updated: UpdatedModel<TModels, T>
}
const updatedPlugin = <
TModels extends Models<TModels>,
TExtraModels extends Models<TModels> = Record<string, never>,
T = Date
>(
config: UpdatedConfig<T> = {}
): Plugin<TModels, TExtraModels, ExtraModelsFromUpdated<TModels, T>> => {
const updatedModelName = config.name || 'updated'
const updated = {
name: updatedModelName,
state: {} as Record<string, any>,
reducers: {
onUpdate: (
state: UpdatedState<TModels, T>,
payload: { name: string; action: string }
): UpdatedState<TModels, T> => ({
...state,
[payload.name]: {
...state[payload.name],
[payload.action]: config.dateCreator
? config.dateCreator()
: new Date(),
},
}),
},
}
const avoidModels = [...(config.blacklist || []), updatedModelName]
return {
config: {
models: {
updated: updated as UpdatedModel<TModels, T>,
},
},
onModel({ name }, rematch): void {
// do not run dispatch on updated model and blacklisted models
if (avoidModels.includes(name)) {
return
}
const modelActions = rematch.dispatch[name]
// add empty object for effects
updated.state[name] = {}
// map over effects within models
for (const action of Object.keys(modelActions)) {
if (rematch.dispatch[name][action].isEffect) {
// copy function
const originalDispatcher = rematch.dispatch[name][action]
// replace existing effect with new dispatch
rematch.dispatch[name][action] = (...props: any): any => {
const effectResult = originalDispatcher(...props)
// check if result is a promise
if (effectResult?.then) {
effectResult.then((result: any) => {
// set updated when promise finishes
rematch.dispatch[updatedModelName].onUpdate({ name, action })
return result
})
} else {
// no need to wait for the result, as it's not a promise
rematch.dispatch[updatedModelName].onUpdate({ name, action })
}
return effectResult
}
}
}
},
}
}
export default updatedPlugin