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

Reactivity to state changes #37

Open
josephguillaume opened this issue Jun 29, 2024 · 8 comments
Open

Reactivity to state changes #37

josephguillaume opened this issue Jun 29, 2024 · 8 comments
Labels
elaboration An issue that elaborates and collects ideas on a topic

Comments

@josephguillaume
Copy link
Contributor

Opening this issue to document opportunities for components to react to state changes, building on the rxjs decision:

- RxJS might come in handy in other places where applications need to react to state changes, like when new data for a resource gets fetched

By state changes, I firstly have in mind cases where the triples affecting a subject or object change, either because of another component on the same page or due to data changes.
The primary use case is to perform reactive rendering.
It will likely be desirable to standardise how this is dealt with across solid web components. See https://github.com/solid-contrib/web-components/wiki/Rendering

e.g.
pos-label is displaying data from the triple

:subject rdfs:label "subject label".

This triple is then replaced in the store by

:subject rdfs:label "new subject label".

pos-label needs to rerender with the new value

@josephguillaume
Copy link
Contributor Author

Because PodOs uses rdflib.js, at the moment it is possible for custom components to achieve reactive rendering using callbacks on the graph store.

os.store.graph.addDataCallback(doUiCallbacks);
os.store.graph.rdfArrayRemove = (a, x) => {
    for (var i = 0; i < a.length; i++) {
      if (
        a[i].subject.equals(x.subject) &&
        a[i].predicate.equals(x.predicate) &&
        a[i].object.equals(x.object) &&
        a[i].why.equals(x.why)
      ) {
        doUiCallbacks(x);
        a.splice(i, 1);
        return;
      }
    }
    throw new Error("RDFArrayRemove: Array did not contain " + x + " " + x.why);
  };

doUiCallbacks is called on every triple change. It therefore needs to efficiently manage a set of callbacks to individual custom components as well as throttle repeat callbacks in some way.

I have a basic buggy implementation of this.

@josephguillaume
Copy link
Contributor Author

Given the decision to use rxjs, it makes sense to revisit this callback architecture and identify a solution better suited to PodOs.

PodOs core encapsulates rdflib.js so that elements do not depend on it 1

Building on the idea that rxjs provides lazy push collections of multiple values, a possible solution is that the properties of Thing 2 become observables

e.g. label is no longer a pull function that returns a value, but is instead a push observable that pos-label reacts to.
The construction/destruction of a Thing would need to handle subscription to observables, and manage the callbacks from rdflib.js

This would be a major architectural change, which could either be approached as a breaking change, or by PodOS core offering both push and pull architectures for accessing data.

However, for devx reasons, I believe PodOs elements should either all be individually reactive to state or not, and I currently believe that making every web component state-reactive is the preferred option.

Whatever final architecture is used, I have found that retaining low level access to rdflib.js is often needed given PodOs does not fully cover the rdflib.js api. It would therefore be beneficial for the callback management mechanism to be accessible from custom components, even if direct use is discouraged.

There may be other solutions, which is why I've framed this issue as documenting opportunities rather than proposing this as the definitive solution.

Footnotes

  1. https://github.com/pod-os/PodOS/blob/7d2693a3b47cc3bc837060bc761d5aabf42c2b27/docs/decisions/0003-handle-data-with-rdflibjs.md

  2. https://github.com/pod-os/PodOS/blob/7d2693a3b47cc3bc837060bc761d5aabf42c2b27/core/src/thing/Thing.ts

@josephguillaume
Copy link
Contributor Author

As a point of comparison, semantic-ko used observables through the knockoutjs library, though I think PodOS would want to avoid defining an explicit view model.

https://web.archive.org/web/20111118042156/http://antoniogarrote.com/semantic_ko/

@josephguillaume
Copy link
Contributor Author

Noting that while external components will use rxjs, reactivity to logged in state (and Webid, and profile) is still managed using a stenciljs store for image, navigation bar, resource and document, with pos-app providing the bridge between rxjs and the store.

https://github.com/pod-os/PodOS/blob/7d2693a3b47cc3bc837060bc761d5aabf42c2b27/elements/src/store/session.ts

session.state.isLoggedIn = sessionInfo.isLoggedIn;

@josephguillaume
Copy link
Contributor Author

If I understand correctly, here's an untested proof of concept for an observable rxjs label() used as part of https://github.com/pod-os/PodOS/blob/main/elements/src/components/pos-label/pos-label.tsx

@State() label = null;

receiveResource = (resource: Thing) => {
    this.resource = resource;
    this.resource.label()
      .pipe(takeUntil(this.disconnected$))
      .subscribe(label=>this.label=label)
};

render() {
    return this.label
}

However, we also need to deal with destruction of the component similar to

os.observeSession()
.pipe(takeUntil(this.disconnected$))
.subscribe(sessionInfo => {
this.sessionInfo = { ...sessionInfo };
});
}
disconnectedCallback() {
this.disconnected$.next();
this.disconnected$.unsubscribe();
}

@State() label = null;

receiveResource = (resource: Thing) => {
    this.resource = resource;
    this.resource.label()
      .pipe(takeUntil(this.disconnected$))
      .subscribe(label=>this.label=label)
  };

render() {
    return this.label
}

private readonly disconnected$ = new Subject<void>();

disconnectedCallback() {
    this.disconnected$.next();
    this.disconnected$.unsubscribe();
}

This seems a bit unwieldy and probably would need to be encapsulated somehow?

@josephguillaume
Copy link
Contributor Author

A common framework-specific solution is to proxy object properties to perform dependency-tracking and change-notification when properties are accessed or modified 1

Using stencil-store, we could create a ReactiveThing in PodOS elements, which turns an observable label() into a reactive property, something like (untested):

class ReactiveThing {
  constructor(thing){
   this.disconnected$ = new Subject();
   this.state = createStore({
     label: null
   });
   thing.label()
      .pipe(takeUntil(this.disconnected$))
      .subscribe(label=>this.state.label=label)
  };
  dispose(){
    this.disconnected$.next();
    this.disconnected$.unsubscribe();
  }
}

Which would be used:

receiveResource = (resource: Thing) => {
    this.resource = new ReactiveThing(resource);
  };

render() {
    return this.resource.state.label
}

disconnectedCallback() {
    this.resource.dispose()
}

Footnotes

  1. https://v2.vuejs.org/v2/guide/reactivity.html

@josephguillaume
Copy link
Contributor Author

SolidOS uses rdflib.js' store.updater.addDownstreamChangeListener. When the remote resource is changed a notification is received via Websocket, the resource is reloaded and a rerender is triggered.

https://github.com/search?q=org%3ASolidOS%20addDownstreamChangeListener&type=code

@josephguillaume
Copy link
Contributor Author

Thing's interface is reminiscent of https://ldo.js.org

It uses a subscribable RDF dataset that emits events for specific quad matches, which triggers rerender of the react component, apparently by updating a forceUpdateCounter state variable.

useSubject appears to be an equivalent of Thing/ReactiveThing
https://ldo.js.org/api/solid-react/useSubject/

@angelo-v angelo-v added the elaboration An issue that elaborates and collects ideas on a topic label Dec 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
elaboration An issue that elaborates and collects ideas on a topic
Projects
None yet
Development

No branches or pull requests

2 participants