DEV Community

Discussion on: What happened to Components being just a visual thing?

peerreynders profile image
peerreynders • Edited on

See my comment here

Expressed roughly in JavaScript-like syntax an Erlang process is at its simplest

function loop(state) {
  const msgFrom = receive();

  const nextState = (lastState, { type }) => {
    switch type {
      case 'a':
        send(msgTypeA);
        return toStateA(lastState)

      case 'b':
        send(msgTypeB);
        return toStateB(lastState)

     default:
        return lastState
    }
  }(state, msg);

  loop(nextState)
}
Enter fullscreen mode Exit fullscreen mode

Of course JavaScript

  • doesn't implement tail(/last) call optimization
  • isn't pre-emptively scheduled. Typically a process is blocked at a receive() (or any other blocking call) waiting for the next message and even if it isn't the scheduler will suspend it after it consumes it's reductions.

None of this is feasible in single threaded/event loop based JavaScript but it points to the possibility of tying process scheduling to message delivery:

  • A "processing scope" (replacing "actor") only gets processing control when it receives a message (with some exceptions; the message would likely be arrive via a subscription). To be well behaved it has to break up large tasks into phases which it initiates by sending a message to itself at the end of the current phase.
  • To interact with other "processing scopes" is has to send a message. However there is no immediate response (the message goes to the end of the delivery queue). If there is a response it will be received later at which point in time it can be processed (so the processing scope state has to reflect this "in progress interaction" - the equivalent to a "function call (returning a value)" to a separate processing scope).

So the basic functionality needed for a processing scope is a way to receive messages so it has something to do, and a way to send messages so it can collaborate with other processing scopes. For that to work the message router/scheduler needs to support a few things:

  • The entire system is built around postMessage(). For the purpose of discussion lets call a Window or Worker a "processing node".
  • Each node
    • routes messages (i.e. has to manage routing data)
      • messages to the local processing scopes are put on the delivery queue
      • messages to processing scopes managed by another node are immediately posted to the nearest node.
    • schedules processing by
      • delivering a single message to the local processing scope from the head of the delivery queue provided there is enough time left in the current processing slice
      • returning control to the event loop when the current processing slice is exhausted.
        • if the delivery queue isn't empty the next processing slice needs to be scheduled (requestIdleCallback(), setTimeout()).
        • when the delivery queue is empty use queueMicrotask() to start the next processing slice when a message is placed on the delivery queue arriving from another node or a general send (e.g. caused by an input event).

Now a system like this is inherently asynchronous—so in my judgement has little to no chance to be adopted by the JavaScript community given how relieved everybody was when async/await was introduced.

In terms of the PolymerLabs MessageStore I would be concerned that forcing messages through IndexedDB would slow things down unnecessarily.

Is postMessage slow?

But thanks for asking …


it would be nice with Niladic Components (no props)

Note what is going on in his niladic solution:

He is failing to extract the variation of two near identical components in order to preserve their "niladic" property

import './styles.css';
import { useEffect, useState, useCallback } from 'react';
import { external } from './External';

function Value({ update }) {
  const [value, setValue] = useState();
  const onChange = useCallback(
    ({ target: { value } }) => {
      setValue(value);
      update(value);
      external.doFinalCall();
    },
    [update]
  );

  return (
    <span>
      <input value={value} onChange={onChange} />
    </span>
  );
}

const left = (value) => external.left = value;
const right = (value) => external.right = value;

export default function App() {
  const [total, setTotal] = useState(0);

  useEffect(() => {
    external.registerFinalCall(setTotal);
  }, [total]);

  return (
    <div className="App">
      <h1>Totals</h1>
      <Value update={left} />
      <Value update={right} />
      total is {total}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

props will be involved whenever you use a component more than once on the same page to represent separate business entities.

Thread Thread
3shain profile image
3Shain • Edited on

props will be involved whenever you use a component more than once on the same page to represent separate business entities.

As I said before

I need to mention that the identity of Component is not always the same as the identity of Instance of Component. That's why in some framework you need to specify the key.

although there are no real key here but different update props are passed to identify different entities. In certain degree it's the mismatch between UI and application proved again.

and the solution is fairly simple... encapsulate High Order Component

although it's not necessary to involve any architecture decision, I'm placing the kairo's solution here

const [External, ExternalImpl] = createConcreteConcern('External', function*() {
    const [leftValue, setLeft] = yield* State(0);
    const [rightValue, setRight] = yield* State(0);
    return {
        leftValue, rightValue,
        setLeft, setRight
    }
}); // a shortcut of `createConcern` with a default implementation

const Value = UI(function*({value, setValue}:{value:Cell<number>, setValue:Setter<number>}) {
    const onChange = e => setValue(e.target.value);
    return Component((_,$)=>(<span><input value={$(value)} onChange={onChange}/></span>));
});

const App = UI(function*() {
    const {leftValue, rightValue, setLeft, setRight} = yield* External;
    const Left = yield* Include(Value.withProps({value:leftValue, setValue:setLeft}));
    const Right = yield* Include(Value.withProps({value:rightValue, setValue:setRight}));
    return Component((_,$)=>(
    <div className="App">
      <h1>Totals</h1>
      <Left />
      <Right />
      total is {$(leftValue) + $(rightValue)}
    </div>));
});
Enter fullscreen mode Exit fullscreen mode

notably I made Value fully controlled because it's unnecessary to have a local state as there is already a source of truth.