Hi there, I just started reading about hyperapp I love the idea, I was wondering if you guys have a concept of being able to render into different areas of an app. e.g. something like a React Portal.
Is this possible? Or if its not, is it something of interest within the community?
Hyperapp itself doesn't have any concept of portals.
My first question would be: What do portals give you that hyperapp can't already do?
Since your entire app state is shared/global, you can already render state in multiple places. I think React needed portals to do this because of local component state?
The party trick of using portals to render content outside of the application's DOM tree is more interesting. I recall an article showing that you can render a React portal into a popup window and maintain the interactivity. I haven't looked into how that's implemented.
@SkaterDad yeah, one of the things I have done is used portals on the server to render contents into the head of a document which is nice for setting things like titles/metadata.
I'm currently putting together a test case, so if you're curious I'll report back on my progress.
@lifeiscontent Interesting! I was just actually thinking about how one would implement something like Vue.js's "slots" and the problem seems very similar to the problem of implementing something like React's portals. (EDIT: assuming I understood what portals are about, which is possible I dont ;) )
I don't have a solution yet, but I think a solution would require something like React's context -- which also doesn't exist in hyperapp. ( I have a package you could add add it with though: https://github.com/zaceno/hyperapp-context)
@zaceno is hyperapp capable of doing things like React.fragment? just to give you an example in pseudo react code.
import Head from './components/Head';
export default () => {
return (
<React.Fragment>
<Head>
<title>My Page</title>
</Head>
<h1>Welcome to my site...</h1>
</React.Fragment>
);
}
the use case here is to make it so you don't have to render a div or something with the page you're rendering.
@lifeiscontent Your components _can_ return arrays of nodes, but there must be a top-level container for the view. So, it will do what you want, but we don't really have proper fragments like React which renders the children to a DocumentFragment which can also be keyed.
@jorgebucaran is this something that would potentially be a feature in the future?
@lifeiscontent Fragments? Possibly. (See related: https://github.com/hyperapp/hyperapp/issues/530). Depends on how much code it involves. I want to.
Portals? I imagine you are new to Hyperapp, but if and when you know more about it, you'll understand that API doesn't make sense in Hyperapp. So, probably not. Maybe something entirely different if a use case for it comes up. We'll see.
@jorgebucaran you guessed right, I am new to it! :) so given my initial issue that I talked about with injecting a title into the head of a document, how would that be done within hyperapp?
@lifeiscontent injecting a title into the head of a document, how would that be done within hyperapp?
In the browser , you could simply set the title with `document.title = "My New Title". If you end up with SSR at some point, another option would have to be thought of.
@SkaterDad doesn't that defeat the purpose of 1 way data flow though? I guess a portal also isn't really 1-way, but... it's closer than just a plain old mutation to the document.
I'm also more interested in knowing how to set meta data for things like facebook/twitter social tags.
@lifeiscontent doesn't that defeat the purpose of 1 way data flow though?
Not really: the DOM (including document.title) is the final stop of a one way flow of data, which originates in the state. Now, if we somehow were storing/reading the state from the DOM, that would be cyclical data-flow, and that would be bad.
@zaceno ok, so in the case of injecting meta information to a page, how would you do that in hyperapp?
@zaceno currently, I'd guess you'd have some array you'd mutate to then write tags into the head so it would work for both client and server. e.g:
headTags = ['meta', {name: "viewport" content: "width=device-width,initial-scale=1,shrink-to-fit=no"];
const Html = ({state, actions}, children) => (
<html>
<head>
{{state.headTags.map(h)}}
</head>
<body>
{{children}}
</body>
</html>
);
I guess I just don't know how you would pass around the state to each component.
@lifeiscontent Frankly I don't think Hyperapp is the right tool for the job of setting metadata. To me that feels like a server-side thing, which I believe is out of scope for Hyperapp.
The example you gave couldn't work because it's trying to describe the DOM of an entire html page, but hyperapp needs to render inside a container, so the DOM already needs to be there for Hyperapp to be able to do anything
I don't know the first thing about server-side rendering with React or Hyperapp, but perhaps you're looking for: https://github.com/hyperapp/render
Preact has a separate project that provides a special tag for adding things link links, meta data, etc. to the document head. https://github.com/Download/preact-helmet
Preact also has another separate project that provides a Portal tag that allows you to render content outside of the current component. https://github.com/developit/preact-portal.
Seems like the same approach would work for Hyperapp. No need to add more code to Hyperapp, leading to unnecessary project bloat. Offer the functionality in separate projects for those that need them.
I agree with @rbiggs. If anyone wants to give this a shot go for it — I've recently relaxed the way I admin the organization. If anyone can commit to maintain their module and want it promoted to "official" status, that is, so it can be installed via npm i @hyperapp/gizmo, you are welcome aboard.
Here is a starting README/LICENSE template to get started.
@lifeiscontent I was wondering if you guys have a concept of being able to render into different areas of an app. e.g. something like a React Portal.
You can render into any area of you app, so I don't think we have a problem here. If I am wrong, please reopen and improve your explanation. Thanks! 👋
My solution there:
- Create an ordinary element and insert it into the DOM using good old jQuery or vanilla JavaScript inside a non-component ordinary function.
- Then initialize a HyperApp application within it.
- When we need to receive information within the displaced element, we could have the element trigger a custom event or jQuery event, and pass the data into that event.
@infinnie So basically two Hyperapp apps running on the same page an a mechanism for them to communicate with each other?
We need an unmount feature in hyperapp to be able to properly implement portals without memory leaks. The main idea of portal is to be able to render for example a confirmation dialog in the same route without the need to modify layout or any parent components.
@frenzzy Could you point the unmount() equivalent of other frameworks?
@frenzzy What about Preact? Vue?
This must be fixed by 2.0, maybe app.unmount().
Or one could reuse the portal container as much as possible to minimize memory leak.
Just for a history, higher-order app with unmount feature:
function withUnmount(nextApp) {
return (initialState, actions, view, container) => {
return nextApp(
initialState,
{
...actions,
unmount: () => {
view = null
return {}
},
},
() => view,
container,
)
}
}
const main = withUnmount(app)(state, actions, view, container)
main.unmount()
Demo: https://codepen.io/frenzzy/pen/KoBKQP/left/?editors=0010
Or return null?
Aren't the main concerns about umount : events and side effects ?
An example using promises:
var showDialog = (function () {
var root = $("<div>").addClass("portal").appendTo("body"), dlg = app({
text: "Default text"
}, {
changeText: function (text) {
return { text: text };
}
}, function (state) {
return (<div class="dialog portal__dialog">
<p class="dialog__text">{state.text}</p>
<div class="dialog__buttons">
<button type="button" class="btn btn--primary" onclick={ function () {
root.trigger("ok"); } }>OK</button>
<button type="button" class="btn btn--secondary" onclick={ function () {
root.trigger("cancel"); } }>Cancel</button>
</div>
</div>);
}, root.get(0));
return function (text) {
dlg.changeText(text);
root.addClass("portal--active");
return new Promise(function (resolve, reject) {
root.on("ok", function () {
root.off("ok").off("cancel");
resolve();
}).on("cancel", function () {
root.off("ok").off("cancel");
reject();
});
});
};
})();
Then we call it in event handlers:
<button type="button" onclick={ function () {
showDialog("Hello").then(function () {
// OK clicked.
}); } }>Show dialog</button>
Also to change the title, just set the document.title property.
@infinnie why do you need hyperapp, if you successfully use jQuery? :)
With a plain hyperapp it may look like this:
Hyperapp Portal
const Portal = (props, children) => (
<div
key={props.key || 'portal'}
oncreate={element => {
const portal = {
view: () => <div class="dialog">{children}</div>,
container: document.body.appendChild(document.createElement('div'))
}
portal.app = app({}, { update: () => ({}) }, () => portal.view, portal.container)
element.portal = portal
}}
onupdate={element => {
const portal = element.portal
portal.view = () => <div class="dialog">{children}</div>
portal.app.update()
}}
ondestroy={element => {
const portal = element.portal
portal.view = () => null
portal.app.update()
portal.container.parentNode.removeChild(portal.container)
element.portal = null
}}
/>
)
Usage:
{state.showDialog && <Portal>Some Content</Portal>}
Yes. It’s like implementing portals in older versions of React. But lifecycle events are hooked on an extra DOM element instead of the component.
What about propagating messages out of the portal? @frenzzy
How about using any of the various tiny pubsub libs on NPM for communication?
@rbiggs that would be like something in my example.
Then in @frenzzy’s implementation it would not be hard to handle events: just pass actions to them and things would be done.
Most helpful comment
Preact has a separate project that provides a special tag for adding things link links, meta data, etc. to the document head. https://github.com/Download/preact-helmet
Preact also has another separate project that provides a Portal tag that allows you to render content outside of the current component. https://github.com/developit/preact-portal.
Seems like the same approach would work for Hyperapp. No need to add more code to Hyperapp, leading to unnecessary project bloat. Offer the functionality in separate projects for those that need them.