npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

pojo-observer

v1.1.9

Published

A minimalist object observer that works with React hooks.

Downloads

351

Readme

Maintainability Test Coverage npm NPM

POJO Observer

What?

A minimalist object observer that works with React hooks.

Why?

Because you you can separate presentation logic from interaction logic.

How?

Create a POJO subject (Plain Old Javascript Object - POTO if using Typescript?), and have your React component update whenever that subject changes through an useObserver hook.

Example

Say you have this Gallery component. An ultra thin UI component with presentation logic only:

import useObserver from 'pojo-observer'
 
// It's up to you how the inject the subject. Perhaps use depedency injection + a composite root  
export default function GalleryUI({gallery}) {

  // Place the hook at the top of your component just like any other React hook
  useObserver(gallery)
  
  return (
    <>
      <h5>Component</h5>
      // Changes in the gallery object will be updated here whenever the subject changes
      <p>Image = [{gallery.currentImage()}]</p>
      // act directly on the subject
      <button onClick={gallery.previousImage}>Previous Image</button> 
      <button onClick={gallery.nextImage}>Next Image</button>
    </>
  )
}

And this POJO:

export default class Gallery {
  constructor() {
    this._images = []
    this._selectedImage = 0
  }

  nextImage() {
    if (this._selectedImage < this.images.length - 1) {
      this._selectedImage++
    }
  }

  previousImage() {
    if (this._selectedImage > 0) {
      this._selectedImage--
    }
  }

  addImage(image) {
    this._images.push(image)
  }

  currentImage() {
    return this._images[this._selectedImage]
  }
}

And now any time a value inside the POJO changes, the useObserver hook will re-render the component. Sweet!

Bonus: You can test the heck out of the interaction now without having to mess with any UI testing libraries.

Asynchrony

Now let's assume we have some async function on that object happening.

  // ... truncated for brevity 
  constructor() {   
    // ... truncated for brevity
    
    setInterval(this.nextImage, 1000)
    
    // ... truncated for brevity

Yes yes, never put a setInterval in a constructor. But say you have an external event that updates the model, well, the React component will update. Sweet!

Using Other Hooks

You can also add as many other hooks like useEffect as you like as follows:

  // ...

  // You can have effet react to specific queries
  useEffect(() => {
    console.log('effect currentImage()')
    // since you have commands, you no longer need to dispatch events with reducers.
    // You can work with the POJO directly and handle all complexities there
    // gallery.doSomething(...)
  }, [gallery.currentImage()]) 

  useEffect(() => {
    console.log('effect images')
    // gallery.doSomethingElse(...)
  }, [gallery._images]) // you can also access member variables directly since the command will trigger a rerender, though it's advised you don't do this as it couples your view to your POJO. It could be useful for debugging. 
  
  // ...

How about nested objects, arrays, and arrays of objects?

They work :)

Check out the pureObserver.spec.tsx file for the cases we've thought of, and please report any issues you find as a test if possible and we'll work on it.

How is this different to Redux, Flux and MobX

This library and all the ones mentioned above are ultimately implementations of the Observer Pattern. (Redux is more of a state management library but it also has an observer when using the Connect method).

This library is a minimal observer pattern implementation that takes in a POJO as a subject, instruments it, and performs callbacks when the subject has changed. It's not opinionated at all and allows you to use it however you see fit, like choosing the event library to add (or not).

It's also tiny at around ~4k minified.

Motivation

At Xolv.io we are big fans of BDD (Behaviour Driven Development), DDD (Domain Driven Design) and the Clean Architecture, and we love to make things as simple as possible.

While working with clients and seeing how complex UI's have become, the question of "is it possible to do DDD in the UI?" kept coming up. We've had great success at helping clients do BDD, DDD, and Clean Architecture in the back-end in order to reduce complexity, increase quality, and improve speed and maintainability (shameless plug for our consulting services here), so we wanted to see how to do this in the front-end.

The following inferences were made:

  • Part of the BDD approach is bring people together to collaboratively come up with specifications that articulate the problem/solution domain as rules and examples
  • Part of the DDD approach is to model the problem/solution domain using aggregates and services that enforce the said rules and carry out business logic required for said scenarios
  • Part of the Clean Architecture approach is having concentric-rings layers where the inner layers contain the domain model and use-cases, and the outer layers contain the interfaces and frameworks.

In the world of front-ends, the above inferences result in the following implications:

From BDD:

  • Bring designers, developers and testers together to discover and reason about a UI with a particular focus on user interactions, and record the outcomes as either domain or component specifications (shameless plug, for our XSpecs tool here)
  • Use the behaviour defined in the specifications as examples to drive out the design of the system with a strong focus on automated testing

From DDD

  • Create a domain model from the specifications. In particular, the focus here is on the interaction domain
  • Use aggregates and value objects to encapsulate an abstract interaction model
  • Use domain events to communicate between different interaction aggregates
  • Use services to orchestrate across interaction aggregates and remote systems
  • Use repositories to talk to back-ends

From Clean Architecture

  • UI layer - components that show provided values and invoke actions onto a controller
  • Controller layer - Takes actions and translates them into something the use-case interactor can deal with
  • Use-case Interactor layer - This is basically either an aggregate root or a service from DDD

Side notes:

  • It may make sense to have the UI layer and the Controller live in the same file, even though they are different layers, as long as the separation of concerns is applied.
  • It may not make sense to have a controller at all in some cases and to have the UI layer connect directly to an interaction domain object

But in order to do any of the above, one has to completely decouple the presentation layer from the layers beneath. And while it's possible to do so with the right coding practices, we found there was a lot of boilerplate in binding data to the UI. If we could somehow just focus on the interaction modeling and then plug a UI on top that requires minimal boilerplate code and is highly decoupled, that would allow us to move fast and to have highly testable code. Moreover, it would not lock us in to any framework.

This is why this library was dreamt up.

Why do this?

Having an abstract interaction object has many advantages:

  • The interaction layer is abstract can be used by any view layer like React or Vue, or a speech UI, or even a camera gesture UI. (Though you'd have to bind it yourself as we only support React hooks here)
  • The abstraction makes it easier to reason about the interaction independently of its presentation
  • Changes can be made to the interaction logic without touching the interface components
  • Allows the practice of the Separation of Concerns and the Single Responsibility Principles
  • Makes it easy to perform behaviour driven development and modeling by example