Hyperapp: local state for a select box

Created on 9 Mar 2017  ·  15Comments  ·  Source: jorgebucaran/hyperapp

There was a lot of discussion regarding sub-components in #2 and #73 already.

I was wondering how should one go about creating a reusable select box. There is a lot of state involved that does not necessary need to be stored in the root model:

  • filteredSuggestions - Filtered suggestion displayed in the dropdown menu
  • open - Is the dropdown menu open?
  • value - Current value of the select input box

In React, I created two components StatefulSelect and Select, the first maintaining it's local state and the latter a stateless functional component representing it's visual appearance.

const Select = () => { /* ... */ }

class StatefulSelect extends Component {
  constructor (props) {
    super(props)

    this.state = {
      value: '',
      open: false,
      filteredSuggestions: props.suggestions,
      selected: null
    }
  }

  // ... state update methods ...

  render () {
    return <Select open={this.state.open} />
  }
}

So in hyperapp there is no concept of local state, right? I need to attach the <Select /> state directly to the root model. Wouldn't that be a use case for nested app()'s? And if not, I would love to see an implementation of a select box in hyperapp

Discussion

Most helpful comment

@jbucaran 😆 indeed it is. Not saying anyone should do this. Just that you could 😉

All 15 comments

I'd really love to see a solution for this problem. Right now I'm always avoiding it.

@queckezz So in hyperapp there is no concept of local state, right?

That's right. Just like in Elm.

I would love to see an implementation of a select box in hyperapp

Me too. This sounds like a good first non-trivial challenge to tackle the idea of reusable functions as opposed to reusable components.

From Scaling The Elm Architecture:

If you are coming from JavaScript, you are probably wondering “where are my reusable components?” and “how do I do parent-child communication between them?” A great deal of time and effort is spent on these questions in JavaScript, but it just works different in Elm. We do not think in terms of reusable components. Instead, we focus on reusable functions. It is a functional language after all!

...

In the end, I think we end up with something far more flexible and reliable than “reusable components” and there is no real trick. We will just be using the fundamental tools of functional programming!

Related:

I agree this is a problem.

The way I see it: yes, the hyperapp approach is the same as the elm approach with a single global state, and no "local state". Not that this approach is necessarily better in all cases, but the hyperapp-solution can't be to add encapsulated local state to view components, because then we'd lose the main thing that differentiates hyperapp from Vue.js, React, et c. (IMO).

When you think about it, a React component consists of local state, actions which operate only on the local state, plus the view that renders the local state. In hyperapp, we could still have stateful components minus the views. (Since the views are stateless). I mean, we could have reusable "bundles" of state + actions which are bound to operate specifically on that state...

I'm thinking a lot about this, and will probably be playing around with some approaches. But I'm nowhere near a PR yet, so I'm happy to see the discussion continue :)

If there are any examples of "full scale" complex apps built with Elm, I'd be very interested in seeing them. Their reasoning about reusable, stateless view-functions sounds good, but I have the creeping suspicion that it gets very messy and difficult work with in practice, when apps get big.

I think one of best examples of full scale complex apps built with Elm is the work by the folks at NoRedInk and @rtfeldman has spoken a lot about this.

thanks @jbucaran. I'm considering getting his book "Elm in Action". But in the mean time -- do you by chance know of any repos where I can peruse some source-code, or perhaps a blog where design-patterns in Elm are discussed? (Richard Feldman didn't have a blog that I could find)

@zaceno this is probably as good a resource list as any https://github.com/isRuslan/awesome-elm

@queckezz Here's a simple little pattern for stateful components in hyperapp:

function component ({state, actions, view}) {
  return {
    tag: 'div',
    children: [],
    data: {
      oncreate: el => setTimeout(_ => {
        app({state, actions, view, root: el.parentNode})
        el.parentNode.removeChild(el)
      })
    }
  }
}

usage demo here: https://codepen.io/zaceno/pen/EXmXPY?editors=0010

EDIT: I just realized, I don't know what happens when the component needs to be removed (if it even can be removed).... consider this a starting point, but it probably needs a little more hacking to be memory-safe

EDIT 2: Also, using components correctly requires them to be the single child of a parent tag (or they will be inserted out of order in the tree. It could be made to work, with a small fix to hyperapp's core (allow app to take not only "root" but also an element it is meant to replace within root)

Update: THIS pattern works with plain unpatched vanilla hyperapp (and works with keys, which you will need if you plan to reorder/add/remove your stateful components)

function embed (opts) {
  return {
    tag: 'div',
    children: [],
    data: {
      key: opts.key,
      oncreate: el => setTimeout(_ => {
        const fakeRoot = document.createElement('div')
        opts.root = fakeRoot
        app(opts)
        el.parentNode.replaceChild(fakeRoot.childNodes[0], el)
      })
    }
  }
}

A demo of it working here: https://codepen.io/zaceno/pen/yXXPJx

@zaceno This is dark magic!

@jbucaran 😆 indeed it is. Not saying anyone should do this. Just that you could 😉

@queckezz Any ideas what to do with this issue?

Stateful components are a no-no around here, but the good news is that we have discovered patterns that let you organize your views more cleverly and provide many of the benefits (if not all) that components give.

See

@zaceno is there a working version of this with the current hyperapp version?

@congwenma If you're still in need:

let component = ({ state, actions, view }) => {
  let id = Math.random().toString(36).substr(2, 9);
  setTimeout(() => {
    let el = document.getElementById(id);
    app(state, actions, view, el);
  });
  return { nodeName: "div", attributes: { id }, children: [], key: null };
};

It's just as hacky as the other version but works, lol.

@congwenma yup https://github.com/zaceno/hyperapp-nestable should work for the most recent hyperapp. If not, open an issue :)

If you work a lot with stateful components, you may need a way for them to share state with child components, for which you need something like React context. I also have a library for that: https://github.com/zaceno/hyperapp-context

Please note that using context with hyperapp-nestable doesn’t work. For this reason, hyperapp-context includes a version of hyperapp-nestable which is exactly the same except it also supports context.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jbrodriguez picture jbrodriguez  ·  4Comments

jorgebucaran picture jorgebucaran  ·  3Comments

icylace picture icylace  ·  3Comments

Mytrill picture Mytrill  ·  4Comments

jamen picture jamen  ·  4Comments