Skip to content
This repository has been archived by the owner on Nov 11, 2024. It is now read-only.
/ ople Public archive

Event-driven, observable data flow for React ๐Ÿ’ฅ๐Ÿ‘€

Notifications You must be signed in to change notification settings

alloc/ople

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

60 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

ople

npm Build status codecov Bundle size Code style: Prettier Donate

Event-driven, observable data flow for React ๐Ÿ’ฅ๐Ÿ‘€

Features

  • Transparent observability for seamless, reactive data flow
  • Automatic disposal of event listeners and auto reactions
  • Compatible with all wana functions
  • Mixins for sharable behavior (inspired by React hooks)
  • Strict type safety with TypeScript
  • Objects are readonly outside their initializer
  • Objects have built-in event emitting
  • Highly minifiable code
  • Concise, distilled API

ย 

Usage

The createOple function constructs an Ople object, which is both observable and readonly to React components. Expose methods for React components to call, and expose events for React components to subscribe to.

import {createOple, auto} from 'ople'

// Pass an initializer function to receive a state object,
// a set function, and an emit function.
const state = createOple((self, set, emit) => {
  // The state is mutable.
  self.a = 1

  // The state is observable.
  auto(() => {
    console.log('a:', self.a)
  })

  // The set function is a shortcut for `Object.assign(self, {...})`
  set({ b: 1, c: 1 })

  // The set function converts every getter into an observable getter (unless a setter exists).
  set({
    get sum() {
      return self.a + self.b + self.c
    }
  })
  auto(() => {
    console.log('sum:', self.sum)
  })

  // The set function is the recommended way of declaring methods.
  set({
    // Methods declared with `set` are wrapped to disable implicit observation
    // and to set the Ople context until the method returns.
    add(key: string, n: number) {
      self[key] += n

      // The emit function is a shortcut for `self.emit(...)`
      emit('add', key, n)
    }
  })

  // Subscribe to your own events or the events of another Ople object.
  // Your listeners are removed when the built-in `dispose` method is called.
  self.on({
    add(key, n) {
      console.log('add:', key, n)
    }
  })
})

// The state is observable outside the initializer, too.
auto(() => {
  console.log('b:', state.b)
})

state.add('b', 2)

// Clean up any side effects.
state.dispose()

ย 

Classes

If you like creating objects with new syntax or you prefer storing your methods on a prototype for efficiency, you can create an Ople subclass. For more info, see the Classes page.

ย 

Mixins

As an alternative to classes (which limit you to a single superclass), you can use "mixin functions" to share behavior between your Ople objects. Mixins should be very familiar to anyone who uses React hooks. Just remember, mixins are NOT subject to the same rules as React hooks. For example, mixins can be called from within if blocks. The only rule of mixins is that you should only ever call them from within an Ople context (eg: inside a createOple initializer).

For more info, see the Mixins page.

ย 

FAQ

These questions are common for beginners to ask. If you have a question not yet listed here, feel free to open a pull request, and I'll add an answer to it.

  • Why are Ople objects readonly outside their initializer?

    To keep mutations local to the state they are acting upon. This makes it easy to remember which module contains the code you are concerned with. It also encourages you to declare reusable methods instead of duplicating logic across your project. Your methods will be implicitly bound to their local state, which means you won't have to manually .bind them before using them as React element props (eg: onClick).