Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

local state for a select box #147

Closed
queckezz opened this issue Mar 9, 2017 · 15 comments
Closed

local state for a select box #147

queckezz opened this issue Mar 9, 2017 · 15 comments

Comments

@queckezz
Copy link

queckezz commented Mar 9, 2017

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

@FlorianWendelborn
Copy link

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

@jorgebucaran
Copy link
Owner

@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:

@zaceno
Copy link
Contributor

zaceno commented Apr 19, 2017

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 :)

@zaceno
Copy link
Contributor

zaceno commented Apr 19, 2017

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.

@jorgebucaran
Copy link
Owner

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.

@zaceno
Copy link
Contributor

zaceno commented Apr 19, 2017

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)

@lukejacksonn
Copy link
Contributor

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

@zaceno
Copy link
Contributor

zaceno commented Jun 20, 2017

@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)

@zaceno
Copy link
Contributor

zaceno commented Jun 22, 2017

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

@jorgebucaran
Copy link
Owner

@zaceno This is dark magic!

@zaceno
Copy link
Contributor

zaceno commented Jun 22, 2017

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

@jorgebucaran
Copy link
Owner

jorgebucaran commented Jul 5, 2017

@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

@congwenma
Copy link

congwenma commented May 24, 2018

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

@mini-eggs
Copy link

@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.

@zaceno
Copy link
Contributor

zaceno commented Aug 9, 2018

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants