Skip to content

[labs] Reactive controller adapters for other frameworks #1682

@justinfagnani

Description

@justinfagnani

We would like to enable a framework-agnostic subset of reactive controller to work across frameworks. To do this we need two things from the frameworks: a way to emulate or map the framework lifecycle to the reactive controller lifecycle, and a native composition API or way to extend the framework component model to make adding controllers possible.

Many frameworks seem to have the basic lifecycle required by reactive controllers: hostConnected, hostDisconnected, hostUpdate, hostUpdated, and host.requestUpdate() - or they can be emulated. Not all have a way of extending the component model though

Lifecycle React hooks Angular Vue Ember Svelte
hostConnected
initial render

ngAfterContentInit

onMounted

init

onMount
hostDisconnected
useLayoutEffect

ngOnDestroy

onUnmounted

didDestroyElement

onDestroy
hostUpdate
hook body

ngOnChanges

onBeforeUpdate

willUpdate

beforeUpdate
hostUpdated
useLayoutEffect

ngAfterContentChecked

onUpdated

didUpdate

afterUpdate
requestUpdate
useState
⚠️
not needed w/ zones?

getCurrentInstance().update()
⚠️ ?
writable store
updateComplete
useLayoutEffect

ngOnChanges w/ Promise

nextTick
⚠️ ? ✅ tick
Composition or
Extendable

custom hook

mixins

composition API
⚠️ ?
Mixin or CoreObject?

lifecycle hooks and stores
PR / Prototype #1532 Prototype Prototype Prototype

✅ = There's a way to emulate
⚠️ ? = Unsure how to implement, but there's probably a way
🆘 = Seems like there isn't a way to emulate

We would like the mapping or controller wrapper to end up being as idiomatic as possible in the host framework. If the framework already has a composition API, like React hooks, we want to wrap reactive controllers into that API. If the framework doesn't have a similar API, then we could try to extend the component model with a subclass or mixin and add a way to declare controllers.

See #1532 for an example of creating a useController() hook for React that emulates and drives the reactive controller lifecycle. After wrapping a reactive controller with useController(), the resulting hook is used like any other hook:

Definition:

import * as React from 'react';
import {useController} from '@lit-labs/react/use-controller.js';
import {MouseController} from '@example/mouse-controller';

export const useMouse = () => {
  const controller = useController(React, (host) => new MouseController(host));
  return controller.position;
};

Idiomatic usage:

import {useMouse} from './use-mouse.js';

const Component = (props) => {
  const mousePosition = useMouse();
  return (
    <pre>
      x: {mousePosition.x}
      y: {mousePosition.y}
    </pre>
  );
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    ✅ Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions