• Stars
    star
    105
  • Rank 328,133 (Top 7 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 4 years ago
  • Updated almost 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A minimalist object observer with React hooks support. Allows you to separate concerns between presentation and interaction logic

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.ts 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

More Repositories

1

chimp

Tooling that helps you do quality, faster.
TypeScript
798
star
2

qualityfaster

An example project showing how to create robust and maintainable acceptance tests
JavaScript
262
star
3

typescript-event-sourcing

Domain Driven Design, Event Sourcing & Command Query Responsibility Segregation with Typescript
TypeScript
246
star
4

rtd

DEPRECATED: The Test Runner for Meteor
JavaScript
164
star
5

meteor-rtd-example-project

A template project to use for creating a meteor app with unit and webdriver testing
JavaScript
91
star
6

meteor-cucumber

89
star
7

Letterpress

Sell your written and video content
JavaScript
83
star
8

federation-testing-tool

Test your Apollo GraphQL Gateway / Federation micro services.
JavaScript
65
star
9

md-blog

-- DEPRECATED --
JavaScript
60
star
10

storybook-webpack-federation-plugin

Exposes all the components in your Storybook as Webpack 5 federated components.
JavaScript
57
star
11

whirlwind-old

Reduce your test suite time from hours to minutes on TravisCI, CircleCI, CodeShip and even locally
JavaScript
37
star
12

meteor-webdriver

WebdriverIO for Meteor
JavaScript
28
star
13

meteor-coverage

DEPRECATED - SEE NOTICE BELOW - Code coverage for Meteor testing using Velocity
JavaScript
22
star
14

cleaner

Gives you methods to clear your Mongo database and collections for testing purposes
JavaScript
21
star
15

meteor-webstorm-library

DEPRECATED! Webstorm officially supports meteor Now. A converter that takes the api.json that powers docs.meteor.com and converts it into a stub with jsdocs, for Webstorm to use as a library.
JavaScript
20
star
16

meteor-inspectlet

Inspectlet in an easy to use package
JavaScript
20
star
17

meteor-email-stub

Allows you to inspect sent emails and assert on their content.
JavaScript
18
star
18

inverter

A tiny Inversion of Control (IoC) container for Meteor.
JavaScript
18
star
19

NDK

The Narrative Development Kit - A Narrative Script to Event-Sourced application library
TypeScript
15
star
20

meteor-http-interceptor

Intercepts HTTP calls and allows fake implementations to take over entire domains. Used for testing.
JavaScript
13
star
21

cdk-typescript-tooling

TypeScript
13
star
22

use-complex-state

TypeScript
13
star
23

chimp-widgets

Widget that provides high-level commands for UI testing for use with Xolv.io chimp
CoffeeScript
12
star
24

jasmine-unit

A jasmine-based unit-testing framework designed for the Velocity test runner
JavaScript
9
star
25

meteor-template-isolator

Exposes code from Meteor templates for testing purposes
JavaScript
9
star
26

meteor-backdoor

Meteor method that allows you to run arbitrary code on the server
JavaScript
8
star
27

mongodb-diff

Creates the MongoDB update query by diffing two objects.
JavaScript
7
star
28

newman-to-escape

JavaScript
6
star
29

react-hooks-domain-model

JavaScript
6
star
30

meteor-private-packages

Allows you to add private packages to your app.
JavaScript
6
star
31

whirlwind

Artilary based performance testing that runs on the serverless framework, supporting high-order functions like Stress, Soak, Load, Spike and DDOS attacks
JavaScript
6
star
32

chimp-datasources-generator

Mustache
5
star
33

webpack-federation-storybook-design-systems-demo

TypeScript
4
star
34

meteor-github-stub

A stub for use in testing Meteor apps. Fakes the oauth calls amongst other APIs.
JavaScript
4
star
35

aws-sales-system-example

TypeScript
3
star
36

qualityfaster-old

The code behind the book
JavaScript
3
star
37

dynamodb-toolbox-examples

TypeScript
3
star
38

dynamodb-testing-tool

TypeScript
3
star
39

meteor-twitter-stub

Provides an Twitter OAuth stub for testing and local development
JavaScript
3
star
40

pan-am-supergraph-demo

Demo Federation 2 Router + Subgraphs
JavaScript
3
star
41

cdk-typescript-tooling-example-live

TypeScript
3
star
42

instant-mock

InstantMock allows teams to quickly create mock GraphQL endpoints to eliminate delays and bottlenecks for front-end and QA teams caused by backend API readiness.
TypeScript
2
star
43

chimp-monorepo-example

TypeScript
2
star
44

chimp-graphql-codegen-plugin

TypeScript
2
star
45

meteor-package-boilerplate

A great boilerplate for your awesome new Meteor package
JavaScript
2
star
46

contentful-pipelines

Provides connivence methods for running Contentful migrations.
TypeScript
2
star
47

patcher

Patches Meteor methods and allows you to override "this" attributes for testing purposes
JavaScript
2
star
48

meteor-core-js

ES2015 and ES2016 polyfills in Meteor apps via core-js
JavaScript
2
star
49

enterprise-apollo-graphql

Practice repo of the Enterprise Apollo GraphQL course
TypeScript
2
star
50

chimp-gql-tiny-example

TypeScript
1
star
51

graphql-federation-sandbox

Shell
1
star
52

chimp-widgets-demo

Demo for Chimp Widgets
JavaScript
1
star
53

apollo-composable-with-vue-stories-demo

Apollo Composable with Vue Storybook Stories example
JavaScript
1
star
54

contentful-apollo-federation-example

Example of how to use the contentful-apollo-federation package
TypeScript
1
star
55

meteor-wallaby-sandbox

Sandbox project to get Wallaby working with Meteor.
JavaScript
1
star
56

lerna-dependencies-generator

Move your monolith dependencies into your monorepo packages
JavaScript
1
star
57

babel-async-comment-bug

Reproduce an issue with babel where adding a comment in an async function's arguments list can break it
JavaScript
1
star
58

why-so-loud-jest

JavaScript
1
star
59

swag-shop

The Xolvio Swag Shop is a React application built to demonstrate the power of integrating multiple technologies, including ApolloGraphQL. It is served from three subgraphs and provides an ideal environment for testing, demo purposes, and showcasing interaction with InstantMock.
TypeScript
1
star