Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
declare module "use-bus" {
export interface EventAction {
type: string;
export interface EventAction<T extends string = string> {
type: T;
[key: string]: any;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about adding a second optional generic type for the value of [key: string] instead of any and default that new generic type to any

}

export function dispatch(name: string): void;
export function dispatch(event: EventAction): void;
interface DispatchFn<T extends EventAction = EventAction> {
(name: keyof T): void;
(event: T): void;
}

type FilterActionType<
A extends EventAction,
ActionType extends A["type"]
> = A extends any ? ActionType extends A['type'] ? A : never : never;

interface UseBus<
T extends EventAction = EventAction
> {
<TName extends T['type'] = T['type']>(name: TName, callback: (event: FilterActionType<T, TName>) => void, deps: any[]): DispatchFn<T>;
<TName extends T['type'] = T['type']>(name: TName[], callback: (event: FilterActionType<T, TName>) => void, deps: any[]): DispatchFn<T>;
(name: RegExp, callback: (event: T) => void, deps: any[]): DispatchFn<T>;
<TEvent extends T>(filter: (event: T) => event is TEvent, callback: (event:TEvent) => void, deps: any[]): DispatchFn<T>;
}

export const dispatch: DispatchFn;

const useBus: UseBus;
export default useBus;

export default function useBus(name: string, callback: (event: EventAction) => void, deps: any[]): typeof dispatch;
export default function useBus(name: string[], callback: (event: EventAction) => void, deps: any[]): typeof dispatch;
export default function useBus(name: RegExp, callback: (event: EventAction) => void, deps: any[]): typeof dispatch;
export default function useBus(filter: (event: EventAction) => boolean, callback: (event: EventAction) => void, deps: any[]): typeof dispatch;
export function createBus<TEvents extends EventAction = EventAction>(): [UseBus<TEvents>, DispatchFn<TEvents>];
}
63 changes: 35 additions & 28 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,50 @@
import { useEffect } from 'react'

let subscribers = []
export const createBus = () => {
let subscribers = []

const subscribe = (filter, callback) => {
if (filter === undefined || filter === null) return undefined
if (callback === undefined || callback === null) return undefined
const subscribe = (filter, callback) => {
if (filter === undefined || filter === null) return undefined
if (callback === undefined || callback === null) return undefined

subscribers = [
...subscribers,
[filter, callback],
]
subscribers = [
...subscribers,
[filter, callback],
]

return () => {
subscribers = subscribers.filter((subscriber) => subscriber[1] !== callback)
return () => {
subscribers = subscribers.filter((subscriber) => subscriber[1] !== callback)
}
}
}

export const dispatch = (event) => {
let { type } = event
if (typeof event === 'string') type = event
const dispatch = (event) => {
let { type } = event
if (typeof event === 'string') type = event

const args = []
if (typeof event === 'string') args.push({ type })
else args.push(event)
const args = []
if (typeof event === 'string') args.push({ type })
else args.push(event)

subscribers.forEach(([filter, callback]) => {
if (typeof filter === 'string' && filter !== type) return
if (Array.isArray(filter) && !filter.includes(type)) return
if (filter instanceof RegExp && !filter.test(type)) return
if (typeof filter === 'function' && !filter(...args)) return
subscribers.forEach(([filter, callback]) => {
if (typeof filter === 'string' && filter !== type) return
if (Array.isArray(filter) && !filter.includes(type)) return
if (filter instanceof RegExp && !filter.test(type)) return
if (typeof filter === 'function' && !filter(...args)) return

callback(...args)
})
}
callback(...args)
})
}

const useBus = (type, callback, deps = []) => {
useEffect(() => subscribe(type, callback), deps)
const useBus = (type, callback, deps = []) => {
useEffect(() => subscribe(type, callback), deps)

return dispatch
return dispatch
}

return [useBus, dispatch]
}

const [useBus, dispatch] = createBus();

export { dispatch }
export default useBus
33 changes: 32 additions & 1 deletion src/index.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-env jest */
import { renderHook } from '@testing-library/react-hooks'
import useBus, { dispatch } from './index'
import useBus, { dispatch, createBus } from './index'

const prepare = (done) => (renderHookResult) => () => {
renderHookResult.unmount()
Expand Down Expand Up @@ -177,3 +177,34 @@ it('should register a RegExp and call the callback', (done) => {

done()
})

describe('multiple buses', () => {
const [useBusA, dispatchA] = createBus()
const [useBusB, dispatchB] = createBus();

it('should not cross publish', () => {
let aCount = 0;
let bCount = 0;

const { unmount: unmountA } = renderHook(() => useBusA('EVENT_TYPE', (evt) => {
aCount += 1;
expect(evt).toEqual({ type: 'EVENT_TYPE' })
}))

const { unmount: unmountB } = renderHook(() => useBusB('EVENT_TYPE', (evt) => {
bCount += 1;
expect(evt).toEqual({ type: 'EVENT_TYPE' })
}))

dispatchA('EVENT_TYPE')
expect(aCount).toEqual(1)
expect(bCount).toEqual(0)

dispatchB('EVENT_TYPE')
expect(aCount).toEqual(1)
expect(bCount).toEqual(1)

unmountA()
unmountB()
})
})