Closed liming closed 6 years ago
would this example explain ?
const {hyper} = hyperHTML;
class FirstComponent extends hyper.Component {
// it's important to always return the result
render(){ return this.html
`<span>first ${Math.random()}</span>`
}
}
class SecondComponent extends hyper.Component {
render(){ return this.html
`<span>second ${Math.random()}</span>`
}
}
class CombinedComponent extends hyper.Component {
constructor() {
super();
this.first = new FirstComponent;
this.second = new SecondComponent;
}
render(){ return this.html
`<p>hi, this is ${this.first} and ${this.second}<p>`
}
}
hyper(document.body)
`${new CombinedComponent}`;
it's live on codepen
Thank you for the explain. But how to manage the state of child component?
Usually you would like to set parents state and update children’s state as well.
const {hyper} = hyperHTML;
class FirstComponent extends hyper.Component {
update(state) {
this.setState(state);
return this.render();
}
render(){ return this.html
`<span>first ${this.state.i}</span>`
}
}
class SecondComponent extends hyper.Component {
update(state) {
this.setState(state);
return this.render();
}
render(){ return this.html
`<span>second ${this.state.i}</span>`
}
}
class CombinedComponent extends hyper.Component {
constructor() {
super();
this.first = new FirstComponent;
this.second = new SecondComponent;
}
render(){ return this.html
`<p>hi, this is ${this.first.update({i: 1})}
and ${this.second.update({i: 2})}<p>`
}
}
hyper(document.body)
`${new CombinedComponent}`;
It's not working well.
Say if we have a component named as MenuItem, another component named Menu. Normally we would like to create MenuItem at runtime.
class Menu extends BaseComponent {
constructor(props) {
super(props);
this.menuItem = new MenuItem();
}
render() {
return this.html`
<ul>
${this.state.items.map(item => this.menuItem.update(item))}
</ul>
`;
}
}
But it's not working. We can only create the item in constructor and that's only one item. This way is not going to manage complex nested components.
By the way, the question should not be closed until the solutions are accepted. Someone else might have good solution but they are not be able to see a closed question.
Normally we would like to create MenuItem at runtime.
See this demo.
It creates a list of nodes and it append it later on. You don't need to necessarily create within the template, you can have a method and keep the template clean.
class Menu extends BaseComponent {
constructor(props) {
super(props);
this._menuItems = new WeakMap;
}
get items() {
return this.state.items.map(item => {
let view = this._menuItems.get(item);
if (!view) {
view = new MenuItem(item);
this._menuItems.set(item, view);
}
return view;
});
}
render() {
return this.html`
<ul>
${this.items}
</ul>
`;
}
}
Of course things work more seamlessly with Custom Elements and HyperHTMLElement and while there is an attempt to automate that logic via JSX, currently you need to compose your component the way you want.
Other simple examples in here: https://viperhtml.js.org/hyperhtml/examples/#!fw=React&example=Combined%20Components
the question should not be closed until the solutions are accepted
that's Stack Overflow :smile: ... here I've closed the ticket because it's not a bug or anything I can work on. I didn't close the conversation though, and I'm sill here to help/suggest, if needed.
Thank you for the idea.
I tried to wrap hyper.Component to make it easier to use. The main problem of hyper.Component is the child components are not easy managed. Another problem is, this.state can be fragile so it would keep rerender the DOM if developer broke the state. I can see the Component is trying to avoid breaking this.state here but it's not a compete solution.
To solve the problem, I introduced a base component inherited from hyper.Component. This base component would keep an "virtual state" to patch this.state. Patching data is far more easier than patching the DOM.
import {Component} from 'hyperhtml/esm';
import diff from 'deep-diff';
class BaseComponent extends Component {
constructor(props) {
super(props);
this.innerState = {};
this.children = new WeakMap();
this.apply(props);
}
apply(props) {
// convert the props to states
const state = this.toState(props);
// diff the original state and the new state
const changes = diff.diff(this.innerState, state);
if (changes) {
// apply the change to the state object. This would keep the state as the same
changes.forEach(c => diff.applyChange(this.innerState, state, c));
}
this.setState(this.innerState);
}
/**
* toState is the method you can map the properties to inner state
*/
toState(props) {
return Object.assign({}, props);
}
child(ChildComponent, props) {
let childComponent = this.children.get(props);
if (!childComponent) {
childComponent = new ChildComponent(props);
this.children.set(props, childComponent);
} else {
childComponent.update(props);
}
return childComponent;
}
/**
* update component properties. It would trigger render method
*/
update(props) {
this.apply(props);
}
};
With this BaseComponent, I can use it like this:
class MenuItem extends BaseComponent {
constructor(props) {
super(props);
}
render() {
return this.html`
<li>${this.state.name}</li>
`;
}
}
class Menu extends BaseComponent {
constructor(props) {
super(props);
}
render() {
return this.html`
<div>A simple menu</div>
<ul>
${this.state.items.map(item => this.child(MenuItem, item))}
</ul>
`;
}
}
Do you think it's a good idea to incorporate the idea into hyper.Component?
For example:
The project hypercomponent can do this but I prefer using hyperHTML.Component.
Thank you!