Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/simpler dispatch #5

Merged
merged 17 commits into from
May 11, 2022
94 changes: 58 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,32 @@
npm i @bit-about/event
```

## Migrations
<details>
<summary>v1 -> v2</summary>

> Events dispatch approach has been changed. There is no longer a functions calling with their names in string.
>
> ✖️ old one:
> ```jsx
> const dispatch = useEvent()
> dispatch('onBobPress', 'hello')
> ```
> ✅ new one:
> ```jsx
> const { onBobPress } = useEvent()
> onBobPress('hello')
> ```
</details>



## Features

- 100% **Idiomatic React**
- 100% Typescript with event types deduction
- Efficient and hook-based
- ...with static listener and dispatcher
- Listen or dispatch events from a hook...
- ...or utilise static access
- No centralized event provider
- Tiny - only **0.6kB**
- **Just works** ™
Expand All @@ -26,18 +46,18 @@ npm i @bit-about/event

## Usage

1️⃣ Define your events set and their payloads
1️⃣ Define *your events* by defining their payload middlewares
```jsx
import { events } from '@bit-about/event'

const [EventProvider, useEvent] = events({
const [EventProvider, useEvents] = events({
buttonClicked: (payload: string) => payload,
userLogged: () => {},
modalClosed: () => {},
})
```

2️⃣ Wrap the tree with the EventProvider
2️⃣ Wrap your components in EventProvider
```jsx
const App = () => (
<EventProvider>
Expand All @@ -46,61 +66,63 @@ const App = () => (
)
```

🗣️ Dispatch your events
🗣️ Dispatch your events in one place...

```jsx
const Button = () => {
const dispatch = useEvent()

const onClick = () => dispatch('buttonClicked', 'Hello')
const { buttonClicked } = useEvents()

return <button onClick={onClick}>Call event</button>
return (
<button onClick={() => buttonClicked('Hello')}>
Call event
</button>
)
}
```

👂 Listen on your events
👂 ...and listen for them in another
```jsx
const Component = () => {
const [message, setMessage] = React.useState('')

useEvent({
useEvents({
buttonClicked: (payload: string) => setMessage(payload)
})

return <p>{message}</p>
return <p>{message}</p> // "Hello"
}
```

## Static access
The third element of the `events()` result tuple is object which provides access in static manner (without hook).
The third result element of `events()` is object providing access in static manner (without hook).

```jsx
const [AppEventProvider, useAppEvent, appEvent] = events(...)
const [AppEventProvider, useAppEvents, { subscribe, dispatcher }] = events(...)
```

and then
```jsx
// 🗣️ Dispatch event
appEvent.dispatch('buttonClicked', 'Hello Allice!')
dispatcher.buttonClicked('Hello Allice!')

// 👂 Subscribe and listen on new events
const subscriber = appEvent.subscribe({
const subscriber = subscribe({
buttonClicked: (payload: string) => console.log(payload)
})

// remember to unsubscribe!
subscriber.unsubscribe()
```

## 👉 Rerendering
Neither listeners nor event dispatching rerender the component.<br />
The component will only be rerendered if its state is explicitly changed (in e.g. `React.useState`).
## 👉 Re-render
Neither listeners nor events dispatch your components render.<br />
A component will only be rerendered if it's state is explicitly changed (in e.g. `React.useState`).

```jsx
const Component = () => {
const [message, setMessage] = React.useState('')

useEvent({
useEvents({
aliceClicked: () => console.log('I DO NOT rerender this component!'),
bobClicked: () => setMessage('I DO rerender this component!')
})
Expand All @@ -110,42 +132,42 @@ const Component = () => {
```

## Event Middlewares
Events in `events()` are actually payload middlewares.
Events in `events()` are payload middlewares. They can transform payload into another.

```jsx
const [EventProvider, useEvent] = events({
buttonClicked: (payload: string) => `Hello ${message}!`, // Transforms string payload to another
avatarClicked: () => `Bob!`, // Provides default payload
const [EventProvider, useEvents] = events({
buttonClicked: (payload) => `Hello ${message}!`, // Transforms string payload to another
avatarClicked: () => `Bob!`, // Provides default payload
})

const dispatch = useEvent({
buttonClicked: (payload: string) => console.log(payload), // "Hello Alice!",
avatarClicked: (payload: string) => console.log(payload), // "Bob!"
const { buttonClicked, avatarClicked } = useEvents({
buttonClicked: (payload) => console.log(payload), // prints "Hello Alice!",
avatarClicked: (payload) => console.log(payload), // prints "Bob!"
})

dispatch('buttonClicked', 'Alice')
dispatch('avatarClicked')
buttonClicked('Alice')
avatarClicked()
```

> NOTE: <br />
> The library is completely type safe so Typescript will inform you when you use wrong payload anywhere
> The library is full type-safe, so Typescript will inform you when you use wrong payload anywhere.

## BitAboutEvent 💛 [BitAboutState](https://github.com/bit-about/state)
Are you tired of sending logic to the related components?<br />
Move your bussiness logic to the hook-based state using `@bit-about/state` + `@bit-about/event`.<br />
Are you tired of sending logic to a related components?<br />
Move your bussiness logic to hook-based state using `@bit-about/state` + `@bit-about/event`.<br />

Now you've got **completely type-safe side-effects**, isn't cool?
Now you've got **completely type-safe side-effects**. Isn't that cool?

```tsx
import { state } from '@bit-about/state'
import { useEvent } from './auth-events' // Hook generated from events()
import { useEvents } from './auth-events' // Hook generated from events()
import User from '../models/user'

const [UserProvider, useUser] = state(
() => {
const [user, setUser] = React.useState<User | null>(null)

useEvent({
useEvents({
userLogged: (user: User) => setUser(user),
userLoggout: () => setUser(null)
})
Expand Down
4 changes: 2 additions & 2 deletions example/src/components/Demo/Demo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ const RenderCounter = () => {
* COMPONENT_1
*/
function ComponentOne() {
const dispatch = useEvent()
const { lightSwitchPressed } = useEvent()

return (
<div className='container column'>
<span className='container-title'>component_1</span>
<RenderCounter />
<button className='button' onClick={() => dispatch('lightSwitchPressed')}>
<button className='button' onClick={() => lightSwitchPressed()}>
Press <strong>light switch</strong>
</button>
</div>
Expand Down
44 changes: 22 additions & 22 deletions src/__tests__/events.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,28 @@ const Counter = ({ role }: { role: string }) => {

test('Basic usage', () => {
const aliceListener = jest.fn()
const [Provider, useEvent] = events({
const [Provider, useEvents] = events({
onAlicePress: (alice: string, bob: number) => `alice:${alice},bob:${bob}`,
onBobPress: () => {}
})

const Buttons = () => {
const dispatch = useEvent()
const { onAlicePress, onBobPress } = useEvents()

return (
<>
<button
role='trigger_alice'
onClick={() => dispatch('onAlicePress', 'alice', 100)}
onClick={() => onAlicePress('alice', 100)}
/>
<button role='trigger_bob' onClick={() => dispatch('onBobPress')} />
<button role='trigger_bob' onClick={() => onBobPress()} />
<Counter role='counter_buttons' />
</>
)
}

const Alice = () => {
useEvent({
useEvents({
onAlicePress: aliceListener
})

Expand All @@ -47,7 +47,7 @@ test('Basic usage', () => {
const Bob = () => {
const [value, setValue] = React.useState(0)

useEvent({
useEvents({
onBobPress: () => setValue((bob) => bob + 1)
})

Expand Down Expand Up @@ -105,24 +105,24 @@ test('Basic usage', () => {

test('Ejecting component', () => {
const aliceListener = jest.fn()
const [Provider, useEvent] = events({
const [Provider, useEvents] = events({
onAlicePress: () => {},
onEject: () => {}
})

const Buttons = () => {
const dispatch = useEvent()
const { onAlicePress, onEject } = useEvents()

return (
<>
<button role='trigger_alice' onClick={() => dispatch('onAlicePress')} />
<button role='trigger_eject' onClick={() => dispatch('onEject')} />
<button role='trigger_alice' onClick={() => onAlicePress()} />
<button role='trigger_eject' onClick={() => onEject()} />
</>
)
}

const Alice = () => {
useEvent({
useEvents({
onAlicePress: aliceListener
})

Expand All @@ -132,7 +132,7 @@ test('Ejecting component', () => {
const Gate = () => {
const [isVisible, setIsVisible] = React.useState(true)

useEvent({
useEvents({
onEject: () => setIsVisible((wasVisible) => !wasVisible)
})

Expand Down Expand Up @@ -172,13 +172,13 @@ test('Static usage', () => {
const staticAliceListener = jest.fn()
const staticBobListener = jest.fn()

const [Provider, useEvent, staticEvent] = events({
const [Provider, useEvents, staticEvents] = events({
onAlicePress: (alice: string, bob: number) => `alice:${alice},bob:${bob}`,
onBobPress: () => {}
})

const Alice = () => {
useEvent({
useEvents({
onAlicePress: aliceListener
})

Expand All @@ -192,7 +192,7 @@ test('Static usage', () => {
const Bob = () => {
const [value, setValue] = React.useState(0)

useEvent({
useEvents({
onBobPress: () => setValue((bob) => bob + 1)
})

Expand All @@ -213,11 +213,11 @@ test('Static usage', () => {
)
const { getByRole } = render(<App />)

const aliceSubscriber = staticEvent.subscribe({
const aliceSubscriber = staticEvents.subscribe({
onAlicePress: staticAliceListener
})

const bobSubscriber = staticEvent.subscribe({
const bobSubscriber = staticEvents.subscribe({
onBobPress: staticBobListener
})

Expand All @@ -229,7 +229,7 @@ test('Static usage', () => {
expect(staticBobListener).toBeCalledTimes(0)

act(() => {
staticEvent.dispatch('onAlicePress', 'alice', 100)
staticEvents.dispatcher.onAlicePress('alice', 100)
})
expect(getByRole('counter_alice').textContent).toEqual('1')
expect(getByRole('counter_bob').textContent).toEqual('1')
Expand All @@ -240,7 +240,7 @@ test('Static usage', () => {
expect(staticBobListener).toBeCalledTimes(0)

act(() => {
staticEvent.dispatch('onAlicePress', 'alice', 100)
staticEvents.dispatcher.onAlicePress('alice', 100)
})
expect(getByRole('counter_alice').textContent).toEqual('1')
expect(getByRole('counter_bob').textContent).toEqual('1')
Expand All @@ -249,7 +249,7 @@ test('Static usage', () => {
expect(staticBobListener).toBeCalledTimes(0)

act(() => {
staticEvent.dispatch('onBobPress')
staticEvents.dispatcher.onBobPress()
})
expect(getByRole('counter_alice').textContent).toEqual('1')
expect(getByRole('counter_bob').textContent).toEqual('2')
Expand All @@ -260,7 +260,7 @@ test('Static usage', () => {

aliceSubscriber.unsubscribe()
act(() => {
staticEvent.dispatch('onAlicePress', 'alice', 100)
staticEvents.dispatcher.onAlicePress('alice', 100)
})
expect(getByRole('counter_alice').textContent).toEqual('1')
expect(getByRole('counter_bob').textContent).toEqual('2')
Expand All @@ -272,7 +272,7 @@ test('Static usage', () => {

bobSubscriber.unsubscribe()
act(() => {
staticEvent.dispatch('onBobPress')
staticEvents.dispatcher.onBobPress()
})
expect(getByRole('counter_alice').textContent).toEqual('1')
expect(getByRole('counter_bob').textContent).toEqual('3')
Expand Down
Loading