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.
-
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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
TL;DR - how can we always call methods on
propsthe 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.propscallbacks 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
propsfrom 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
propsmethods as beingasync, which has the side effect of enabling top-levelawaitwithin Component definitions (which is really more of an anti-pattern). This is pretty awkward for trusted Components, which would now need toawaitevery method invocation onpropsin order to function the same when it is loaded as sandboxed.One solution I've been thinking about involves emulating the behavior of the
NearandSocialmethods making RPC requests:undefinedon the first invocationpropsmethod invocation to returnEffectively the only change for Component developers is to account for the case in which the result of a
propsmethod 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