evento-react
TypeScript icon, indicating that this package has built-in type declarations

0.2.2 • Public • Published

component events for React

  • Dispatch custom component events with a payload, or forward React synthetic events.
  • Replicate Svelte's createEventDispatcher and Vue's $emit behavior in React.
  • Hook + HOC for class components.
  • TypeScript support.

NPM MadeWithReactJs.com shield

Evento

Install

npm install --save evento-react

A new event paradigm

Inspired by Svelte's createEventDispatcher() and Vue's $emit, Evento brings component event to the world of React.

With Evento, a React component can dispatch a custom event with an optional payload, or forward a React event, to the component's consumer.

const handleClick = () => evento('message', 'yo Mario World!')

The parent component will be able to listen to the event as it would listen to a React event: by using on + the capitalized name of the component event. The data will be stored in event.detail.

<Child onMessage={e => console.log(e.detail)}> // will log 'yo Mario World!'

The event dispatcher, named by convention evento(not to conflict with useReducer's dispatch, or with emit from other libraries), can be either created by using one of the two provided hooks (useCreateEvento and useExpCreateEvento), or found in the props if you have wrapped your component with the withEvento HOC.

Evento comes with full TypeScript support, and will suggest all of the available event for a given component, based on the component's props type, as well as the payload type for the chosen event. It will also take into account whether the event-listener, or the event, is nullable or not.

evento, the event dispatcher/emitter

The event dispatcher/emitter takes two parameters:

  1. The event name, to be written in lower camel case (e.g. myCoolEvent). Evento will look for the 'on' + upper camel case name in the props (onMyCoolEvent). If you are forwarding an event, you don't have to use the same name (although it's advised to do so, in order to avoid confusion).
  2. The payload, optional, which can be any type of data, and will be stored in event.detail, or a React synthetic event, which will be forwarded as it is to its parent.

Main Hook

The standard way to create an event dispatcher is by using the useCreateEvento hook. The hook takes the component props as the parameter and will observe changes in the props (so don't forget to watch for props changes if you wrap the event handler in useCallback ). It will return the event dispather/emitter, which can be used for multiple event.

// child, hook consumer
const Mario = (props: Props) => {
  const evento = useCreateEvento<Props>(props)
  const handleClickA = () => evento("jump", "it's a me...")
  const handleClickB = () => evento("shoot", "Mariooo")

  return (
    <>
      <button onClick={handleClickA}>A</button>
      <button onClick={handleClickB}>B</button>
    </>
  )
}

// parent
const Level = () => 
  <>
    <Mario
      onJump={e => console.log(e.detail)} // will log "it's a me..."
      onShoot={e => console.log(e.detail)} // will log "Mariooo"
    />
  </>

Experimental Hook

useExpCreateEvento hook is an experimental hook, similar to Svelte'e createEventDispatcher, as it doesn't take any arguments when called (in TypeScript though, you have to pass the Props type as Generic type); it will behave exactly like useCreateEvento, producing an event dispatcher. Unfortunately it is not stable yet, and should only be used in development mode. Also we cannot grant that useExpCreateEvento will work with future React verisons. This particular hook is mostly a proof of concept. Nonetheless, it may come in handy in some cases, for instance if you pass destructured props to your component.

export const Peach = ({ isBowserNear, shouldSaveMario }: PeachProps) => {
  const evento = useExpCreateEvento<PeachProps>()
  const handleClick = () => {
    if (isBowserNear && shouldSaveMario) {
      evento('saveMario', 'Yet again, I am saving Mario...')
    }
  return (
    <>
      <button onClick={handleClick}>Save</button>
    </>
  )
}

HOC

You can wrap your compoenent in the HOC withEvento: it'll inject the event emitter in the props (props.evento) of the wrapped component. In TypeScript, the props of the component need special typing: follow the guidelines in the appropriate paragraph, below.

type ShroomProps = {
  onEatMe: any,
}

const ShroomContainer = (props: HOCProps<ShroomProps>) => {
  const { evento } = props
  const handleClick = () => evento("eatMe", "I'll give you super-powers!")
  return (
    <div>
      <button onClick={handleClick}>A</button>
    </div>
  )
}

const Shroom = withEvento<ShroomProps>(ShroomContainer)

.then: Promises and event-chaining

To parallel the DOM's EventTarget.dispatchEvent (here), Evento returns a Promise that resolve in false if no handler was run, or in true when the handler has returned (or, if the handler is asyncronous, the returned Promises has resolved).

const Box = (props: BoxProps) => {
const evento = useCreateEvento(props)

return (
  <div className={`color-${props.color}`}>
    <button 
      onClick={
        () => evento('break', { shouldBreak: true })
          .then(res => res ? evento('releaseShroom') : null)
      }>
      ?
    </button>
  </div>
)
}

event-forwarding

If you need to forward an event to the parent component, you can do it by passing said event to as second parameter to the event dispatcher. Evento will recognize and forward it as it is, instead of storing it in the detail of a custom event. As of now, you can forward only React synthetic events (and not HTML native events, nor custom event created with Evento). You don't have to name the event as the synthetic event itself, although we advise you to keep the same name, to hint that the event that the handler is going to receive is not a custom one.

// you can do this
onChange={e => evento('textChange', e)}

// but it's better to do this
onChange={e => evento('change', e)}

Typing with TypeScript

In order to benefit from TypeScript Intellisense and error checking, you have to declare the event handlers in the component props. For instance, if you dispatch a 'jump' event, your props should look something like this :

type LuigiProps = {
  onJump: () => void,
  hasMustache: boolean,
}

With regards to the payload, if you are forwarding an event, you just have to type it as the forwarded React event.

type MarioProps = {
  onClick: (e: React.MouseEvent<HTMLButtonElement>) => void,
}

Otherwise, if you are creating a custom component event, so you will have to wrap the payload type into a CustomEvent type, as such :

type MarioProps = {
  onEat: (e: CustomEvent<string>) => void,
}
// the event will be dispatched as such
const handleClick = () => evento('eat', 'shroom')

Props and events can be optional, and TypeScript will take it into account when suggesting types signaling errors :

type PeachProps = {
  onSaveMario: (e: CustomEvent<boolean>) => void,
  onDriveKart: (e?: CustomEvent<DrivingLicence>) => void,
} 

evento('saveMario') // will throw typescript error because the event is not-nullable
evento('driveKart') // won't throw typescript error because the event is optional

When you are working with the experimental hook, you still have to pass the props type (but not the props themselves) to it :

const evento = useExpCreateEvento<BowserProps>()

Finally, if you are working with the HOC, you should import HOCProps from the library, declare the props type and then type as such:

type WarioProps = {
  isStrange: boolean,
  onStrangeMoustaches: (e: CustomEvent<string>) => void,
}

const WarioContainer = (props: HOCProps<WarioProps>) => { // will add evento dispatcher to the props type
  // your component's logic here
}

const Wario = withEvento<WarioProps>(WarioContainer)

Example

This CodeSanbox shows all the different uses you can make of the Evento library.

Why component events?

There are three main advantages in using component events, instead of passing down the callback as a props and leaving it to the child to deal with it :

  1. Reduced prop-drilling: Although technically you are still passing the callbacks as props from the parent down to its child, the way you'll think the data flow will be from the child up, and not vice-versa. This will improve the developer experience.
  2. Independent & agnostic components: Each component can focus on its own role, without having to adapt its internal functioning to the callback passed by their parent; also if the logic of the passed callback changes, this will not entail changing the code of the event dispatcher.
  3. Standardized event-listeners: In JavaScript, and in most of JavaScript frameworks/libraries, the on suffix is associated to listening to events. With React it's difficult for the developer to know in advance what type of data the callback is expecting, or what data the child component is going to pass to the callback; with evento on the other hand, any event-listener will always receive an event (or nothing at all), as a parameter, making the codebase more homogeneous, and meeting the developer expectations of working with events when encountering a on-suffixed prop.

Next

Working on adding an event-dispatcher creator for class-based components. Will keep you posted.

Package Sidebar

Install

npm i evento-react

Weekly Downloads

8

Version

0.2.2

License

MIT

Unpacked Size

59.4 kB

Total Files

16

Last publish

Collaborators

  • aylo