Handling callbacks between Components regardless of loading mode #3
Replies: 4 comments 1 reply
-
I think our goal should be to make things look and feel as much like vanilla React as possible. In this situation, I would lean towards imposing constraints instead of inventing new patterns.
While slightly awkward, this is very predictable and it's an easy to remember rule that when passing functions as props in BOS they must always be written (explicitly) as
I am not a big fan of this because it's like a hook but not a hook, and will be unfamiliar to developers. If we implemented it in such a way that it was written and felt like a hook then I would be more open to it.
Agreed, we should not allow top-level await. I'm not clear on why making |
Beta Was this translation helpful? Give feedback.
-
Top-level It's been a while since I've looked into bringing in Preact hooks, but that's probably the sanest approach here. Then we can look at implementing a hook on top of that specifically for working with |
Beta Was this translation helpful? Give feedback.
-
I revisited adding Preact hooks but there are some challenges with how Preact core and hooks packages work together. It's going to take some more work to get them supported within Components.
In the interim, I'm leaning more towards something like this - a function useComponentCallback(cb: Function, args: any) {
const [value, setValue] = useState<any>(undefined);
useEffect(() => {
(async () => {
setValue(await cb(args));
})();
}, []);
return () => value;
} The big advantage here being that we'd be adhering to React conventions. There are a few constraints though:
For a simple Component: // legacy VM
return <>{props.renderItems(items)}</>;
// current `async` implementation
return <>{await props.renderItems(items)}</>;
// proposed hook
const placeholder = "loading..."; // this is an option now since the render isn't blocking
const renderItems = useComponentCallback(props.renderItems, items);
return <>{renderItems() || placeholder}</>; For a more complex Component: // legacy VM
return <>{items.map((i) => <Item item={props.getItem(i)} />}</>;
// current `async` implementation
return <>{await Promise.all(items.map(async (i) => <Item item={await props.getItem(i)} />))}</>;
// proposed hook
const placeholder = "loading..."; // this is an option now since the render isn't blocking
// no `args` passed into useComponentCallback since it encapsulates a set of invocations with different parameters
const renderItems = useComponentCallback(() => Promise.all(items.map(async (i) => <Item item={await props.getItem(i)} />)));
return <>{renderItems() || placeholder}</>; |
Beta Was this translation helpful? Give feedback.
-
TL;DR - how can we always call methods on
props
the same way when they may be asynchronous based on how the Component was loaded?This has some wide implications for the Viewer, in particular how it will treat the concept of trust. For background, there are two modes for loading a Component tree:
window.postMessage
. This is more secure but incurs heavy performance overhead for nested Components.props
callbacks are defined in the same iframe context.For these to be effectively utilized, I'm of the opinion that trust should be decided at render/run time, e.g. a potential implementation might look like:
The consequence of this approach is that the behavior between the two should be as seamless as possible; a Component loaded as sandboxed should be as close as possible to functionally identical with that same Component loaded in a trusted context. One fundamental difference between the approaches is that callbacks between sandboxed Components (i.e. methods passed on
props
from parent to child) are inherently asynchronous because of their reliance onwindow.postMessage
. Trusted Components do not have this issue, as the callback would be defined within the same iframe context.The current implementation for sandboxed Components solves this by treating
props
methods as beingasync
, which has the side effect of enabling top-levelawait
within Component definitions (which is really more of an anti-pattern). This is pretty awkward for trusted Components, which would now need toawait
every method invocation onprops
in order to function the same when it is loaded as sandboxed.One solution I've been thinking about involves emulating the behavior of the
Near
andSocial
methods making RPC requests:undefined
on the first invocationprops
method invocation to returnEffectively the only change for Component developers is to account for the case in which the result of a
props
method invocation is undefined, i.e.before:
after:
Curious to hear what others think. Is this a viable solution? Is there a better way to abstract this away from the mode of Component loading?
Beta Was this translation helpful? Give feedback.
All reactions