Hyperapp: What documentation would you like to see here?

Created on 2 Jan 2018  ยท  61Comments  ยท  Source: jorgebucaran/hyperapp

The goal of this issue is to collect feedback about what stuff should be covered in the official documentation.

  • What do you think about the current documentation?
  • What's missing in the documentation?

    • What topics would you like to see covered?

  • If it was up to you, how would you structure/layout the documentation?
  • What makes great documentation?
  • How can we keep the documentation minimal and still useful?
Docs

Most helpful comment

Since the library is so small and I understand a major goal is to keep it that way I would like to see annotated source code like for underscore.js. This will greatly help new users/contributors dive into the codebase and figure out how things work or identify bugs.

All 61 comments

First of all I think it is important to have short installation instruction (getting started) and browser support list on the front page (the main README.md I mean). Also could be useful to have documentation TOC there too, make introduction for users as useful as possible, you still may keep readme short by using [more] links to docs.

Few uncovered things in docs I can imagine right now:

  • How to display the same thing twice (reusable views/components/apps). For example counter could be used twice in the header and footer of the page.
    ```js
    import { h, app } from 'hyperapp'
    import {
    state as counterState,
    actions as counterActions,
    view as counterView
    } from './counter' // some crazy naming conventions are needed?

app(
{ // state
counterA: counterState,
counterB: { ...counterState }, // must be a deep copy? what the best way to clone
},
{ // actions
counterA: counterActions,
counterB: counterActions,
},
(state, actions) => ( // view


{counterView(state.counterA, actions.counterA)}


{counterView(state.counterB, actions.counterB)}
{/*
or it is better to pass props manually?

does not work because of jsx naming requirement?
*/}

),
document.body
)
```

  • How to access the parent state keys in slices. For example to calculate a new state in the action it may be required to know a value from the parent state.
  • How to get access to some key in the state from children views without passing them through the whole tree. For example some deeply nested view may require access to state.location to highlight a button based on current location.
  • Code splitting best practices

Having recently read several projects docs as a newbie I must say Vue have been the quickest to digest and left me with the least number of open questions.

How to create reusable modules, how to share, how to make ensure it doesn't break someone else's app if they use your module.

The open questions I have for Hyper App having read around and looked at the samples are

  • How to structure / scale across separate modules, especially state
  • A clear explanations of the functional curried style actions and state slicing
  • How to best do Forms (simple and dynamic) and submission - this can get a little complex in React, less so in vue
  • Better docs for the router - the PR seemed to have more examples than the docs
  • the awesome/hyperapp examples up to date - or at least clearly specify version

Hope that helps

I would like a more detailed explanation of hydration, the current docs don't make it clear how you should set up html to be used by hyperapp.

Since the library is so small and I understand a major goal is to keep it that way I would like to see annotated source code like for underscore.js. This will greatly help new users/contributors dive into the codebase and figure out how things work or identify bugs.

@timjacobi That's really cool! I never knew that was a thing haha ๐ŸŽ‰

@Ch4s3 How much clear the hydration docs would need to be? The process is extremely simple, hence the extremely simple docs. What (part) wasn't clear?

@JorgeBucaran Yeah, I think I get the idea, but in practice it didn't seem to work when I tried it. The example html in the docs is presently:

<html>
<head>
  <script defer src="bundle.js"></script>
</head>

<body>
  <main>
    <h1>0</h1>
    <button>โ€“</button>
    <button>+</button>
  </main>
</body>
</html>

Should the contents of bundle.js be exactly what's in the Hello World example?

import { h, app } from "hyperapp"

const state = {
  count: 0
}

const actions = {
  down: () => state => ({ count: state.count - 1 }),
  up: () => state => ({ count: state.count + 1 })
}

const view = (state, actions) => (
  <main>
    <h1>{state.count}</h1>
    <button onclick={actions.down}>-</button>
    <button onclick={actions.up}>+</button>
  </main>
)

export const main = app(state, actions, view, document.body)

Can I render to just a div that contains <main></main> and not replace the whole page body? Say for example my hyperapp is just a component of an otherwise mostly plain SSR html page, and I want to prerender the html and have HyperApp take over that div, will it work?

@Ch4s3 Should the contents of bundle.js be exactly what's in the Hello World example?

Yes.

Can I render to just a div that contains <main></main> and not replace the whole page body?

Yes. The given example replaces the page body because the given container is document.body, but you can target a different container, that's why there isn't a default one.

// This is your server side rendered HTML. 
document.body.innerHTML = `
  <div id="app"><main><p>foo</p></main></div>
`

// And this is in your bundle.js
app(
  state,
  actions,
  state => h("main", {}, [h("p", {}, "foo")]),
  document.getElementById("app") // <= Any container you want!
)

I want to prerender the html and have ~HyperApp~ Hyperapp take over that div, will it work?

Yes. Imagine your SSR HTML looks like this:

<div>
  <div id="reactapp">
    ...
  </div>
  <div id="vueapp">
    ...
  </div>

  <div id="hyperapp">
    <main>
      <h1>0</h1>
      <button>โ€“</button>
      <button>+</button>
    </main>
  </div>
</div>

All you need is pass document.getElementById("hyperapp") as a container to the app() call.

Awesome, thanks @JorgeBucaran.

Hey @Ch4s3 how about a PR adding that info to the docs? :)

@SteveALee If it can be better re-worded and re-written, that would be ๐Ÿ’ฏ

Working examples with playground like Try Elm which is centrally/regularly updated, because examples allover the internet are mostly non-working...
Architecture diagrams, tutorials, video tutorials.
Yes I know how much resources this requires...
Just saying ๐Ÿ˜„

@kosirm Thanks for your feedback. Do you have something more specific in mind? The question is what kind of documentation would you like to see on this repo, but your suggestion sounds more like the kind of learning resources you wish were available, which is fine, but not the kind of feedback I am trying to collect.

Oh, I see Higher Order Apps (wrappers?) are a thing. Need documenting

@JorgeBucaran

your suggestion sounds more like the kind of learning resources you wish were available

sorry for misunderstanding topic... Thanks for understanding ๐Ÿ‘

I have been using hyperapp for a while now, and I find the current documentation to be more than good enough. It's simple and to the point, as it should be. So thanks @JorgeBucaran and the rest.

What I would like to see, is a "cookbook" style of document. What I mean by that is e.g the usage of a helper getState function in actions documented. Such minor real-life applications can be most helpful, especially to newcomers. I am not certain about instructing the separation of modules in a certain way for scalability, as that's very dependent upon the domain itself.

So my suggestion basically boils down to the addition of a separate document, where all common tasks related to various use-cases can be appended incrementally. Thus, alleviating the pain of developers coming from other frameworks, as well as those accustomed to non-functional programming paradigms.

BTW, state mutation is bad, should it be allowed in the awesome list?

Thanks @kenOfYugen! ๐Ÿ˜Œ

They caught you @marcusasplund! ๐Ÿš” ๐Ÿ˜‰

@kenOfYugen Thx for the heads up, please review

Maybe something on the approach to node checking (#378)? I imagine something small in the Virtual Nodes or Component section would suffice.

When no validation is needed, I might as well use jQuery events for forms.

I think it would be helpful to mention that the return value of app() is the wiredActions object. I didn't want to pass all the actions down the tree and in the code I found that I can just use the result of app() to export this object and use these wired actions in my components via importing them.

Also an example for this would be helpful. At least I couldn't find that in the docs.

@mightyplow I admit it could be much better, but it's there. :)

@JorgeBucaran sorry, my bad. Well done! ;)

Explain the different type of actions, why are async actions wrapped in an extra function?

because actions are usually just event handlers like onclick={action} and we don't know with which amount of arguments it will be called, maybe action(event) or maybe action(a, b, c), where the state argument should go? - @frenzzy

@Siilwyn This should be better documented, but API symmetry is one of the reasons. I want to call actions the same way that I define them.

const actions: {
  myAction: data => ...
}

so

actions.myAction(data)

But then why do actions take only one argument? What if one were to pass extra parameters?

@infinnie, to pass multiple values to an action currently, you will need to pass them as an object.

@infinnie I'm not sure if you're familiar with Redux, but there's some similarities between the actions from Redux and ours.

Hyperapp actions don't require a type property since they are referenced to by the name of the property they are attached to within the actions object.

However to someone who is not a Redux user that would just seem unintuitive.

But then why do actions take only one argument? What if one were to pass extra parameters?

Just pass an object.

I once tried coming up with a description for the two types of actions when models were around:

  • Static: like set which has the signature payload => model
  • Dynamic: like sum which has the signature payload => model => model

Use _static_ actions to insert new data directly into the model. Use _dynamic_ actions when updating the existing model or performing other (potentially async) operations before calling another action.

Slightly off topic, I would love to see docs linked to a specific version using an existing well working system like Read the Docs. (example docs)

If any help is needed I'd be happy to help setting that up.

Would "principles" or "best practices" be an appropriate way to frame some notes or a collection of links to address stuff you should and shouldn't do?

For example, @kenOfYugen said "state mutation is bad". Ok. Why?

And what else should I know to be a good hyperapp developer?

In my first encounters with hyperapp, it occurs to me that I could probably make a mess for myself by putting the wrong things in the wrong places - putting stuff that should go in actions in state, etc.

@rhbecker state mutation is in general a potential source of errors. For hyperapp check this out #501. Practically what you are saying is what my proposal was about.

For the time being, I suggest reading all the current documentation, and replicating the examples on your own. You' ll be fine! There is no such thing as a good hyperapp developer. Because the API is minimal there is nothing new to learn, the complete opposite of e.g Angular. Just study JS from a functional language point of view, learn a new programming language and become a good programmer ๐Ÿ˜„!

I also keep grabbing the following code snippet for reference, would be good to see in the actions documentation:

const actions = {
  setter: data => data,
  getter: () => state => state,
  reducer: () => state => state.count + 1,
  notmutating: () => { ... },
  async: () => (_, actions) => { actions.done() }
}

by @Swizz

@lukejacksonn Use static actions to insert new data directly into the model. Use dynamic actions when updating the existing model or performing other (potentially async) operations before calling another action.

I do think we need to document the two different action signatures a _lot_ better. I'm not sure I agree with static vs dynamic for the naming since both take a dynamic data argument. IMHO static would be either () => ({ ...newState }) or () => () => ({ ...newState }) where the results really are static and don't depend on any of the inputs.

We also need to make more than a single casual reference to state immutability in the actions section:

Every state update is immutable and produces a new object

I believe this should be _super_ prominent and mentioned as part of state.

@kenOfYugen: Sorry if this was unclear from my last post. I wasn't literally asking why state mutation is bad. The point I was trying to make is that there are presumably some principles behind hyperapp - the arrangement of implementation into state, action, view, what ideally belongs in each of these, and what does not, etc.

So far, existing docs seem light on the _why_.

If I arrive here from some other framework or library that uses a different paradigm, and I don't understand the _why_ of hyperapp, I might end up doing things I don't realize are incompatible with that _why_.

I am working on a couple of articles to improve the documentation ecosystem and maybe some of that will find its way into the official documentation as well. I was also thinking it would be nice to have a User's Guide, that teaches you only how to use Hyperapp without bogging down on details, like what is a VDOM, etc.

@rhbecker Hyperapp is probably one of the least intrusive "frameworks" out there, and this is not accidental but by design! ๐Ÿ˜‰

Iโ€™ve always been unenthusiastic to learning a big framework. Not because they arenโ€™t great, but on the fact that I like to know how things work "beneath the surface" and that I'm coding my own JavaScript, not the JavaScript \

Especially when that framework may be replaced in a few of years (if not months ๐Ÿ˜ฎ). I want transferable skills, not obsolete skills.

I'm not certain how much of @JorgeBucaran's last reply was in response to what I've said. FWIW, I'm reading everything after mention of me as such.

It's great that hyperapp is "minimal", "simple", "non-intrusive", "light on opinion" and all the rest. You already do a great job marketing that aspect. It's what piqued my interest, and why I'm here trying to answer the original question of this thread.

But you know what's smaller than 1 KB? 0 KB. And what's even less opinionated? No opinion at all.

There's a reason to use hyperapp, instead of nothing, right? There's a reason you've chosen to arrange your applications into state, actions, and views. You whittled it down to the smallest amount of opinion you felt was necessary - so why is what is still there still there?

There's a reason @kenOfYugen said:

BTW, state mutation is bad, should it be allowed in the awesome list?

And that @JorgeBucaran followed that with:

They caught you @marcusasplund!

Are you expecting @marcusasplund to be the last hyperapp user to make such a mistake? Providing some insight for how to deal with state isn't intrusive. It helps users of hyperapp understand why they are bothering with hyperapp, and helps them use it more effectively - helps them get the most value out it.

The README says:

Hyperapp's design is inspired by The Elm Architecture. Create scalable browser-based applications using a functional paradigm.

Ok, but why? What about The Elm Architecture is commendable? Why should using a functional paradigm be appealing to me, as someone surveying my options?

It might be the case that you're expecting your audience to find you after already coming to the same realizations you did when you decided to make hyperapp. In that case, maybe the _why_ is superfluous. But at least some folks are gonna happen upon hyperapp without understanding _why_ any of this is good. In my opinion, the current state of the documentation does not get into that enough to satisfy that sort of audience.

Does that make any sense?

Are you expecting @marcusasplund to be the last hyperapp user to make such a mistake?

I was just playing with Marcus. Don't take it so seriously. ๐Ÿ˜‰

In my opinion, the current state of the documentation does not get into that enough to satisfy that sort of audience.

IMO the current documentation is not very good, that's why I created this issue, and I appreciate your feedback.

Why should using a functional paradigm be appealing to me?

Telling you why FP is nice is out of the scope of Hyperapp's documentation. Talking about that is better suited for an opinionated blog post.

What about The Elm Architecture (TEA) is commendable?

Telling you what parts of TEA we embrace and what not would be a good idea. In the future, however, I want to stop marketing this so heavily, since we have diverged considerably from TEA. Right now I see Hyperapp as a "pragmatic TEA-inspired approach to state management + built-in VDOM engine".

But to answer your question, TEA inspired the very successful Redux as well as many similar spinoffs architectures, e.g., Vuex. Aso, from Elm's guide:

The Elm Architecture is a simple pattern for architecting webapps. It is great for modularity, code reuse, and testing. Ultimately, it makes it easy to create complex web apps that stay healthy as you refactor and add features.

Even so knowing the design decision is very interesting to know and can be educational when writing our own code using ha.

But I agree it should not be part of the core doc. Part of the extras or a linked blog post

@JorgeBucaran I came to Hyperapp after reading your post on SitePoint. It came at just the right time because I had been learning React and just been reading about Redux and why it was useful. The Redux site introduction did a good job about explaining why having a state object as a single source of truth and dispatching actions being the only way of updating that state. It made sense, but wiring Redux up to React seemed like quite a bit of work, especially for a simple app.

Then I read your blog post and saw a tiny JS library that seemed to do what React + Redux did in less code and a simple to use API. I'm not sure I would have appreciated what you had done if I hadn't read the Redux pages though.

Given this - I'd second what @rhbecker said - it would be nice to have something similar to the Redux pages for Hyperapp that explains the motivation behind the principles used by Hyperapp and some common use cases.

Havent read the whole thread, so don't know if it's been mentioned already, but:
Actions are such a core concept of the API, there really should be a main section dedicated to them, like we have for Virtual DOM and Lifecycle events.

Hi, I would like to see more explanations about actions. Sometimes I don't understand how they work. For example with this action

const myaction = () => async state => //blahblah...

page does not refresh and nothing happens, no error, just nothing. I'm looking for a reason for an hour and then spontaneously remove async keyword -- now it works. If you would have some more documentation and rules about implementationof actions it would help.

@algebraic-brain In the case of your async function, it returns a Promise, and not a partial state object. Actions which return a Promise do not trigger re-renders. or update the state (since Hyperapp doesn't support automatically merging what the Promise resolves). Here is a link to the relevant code in hyperapp.

When you define an async action, you need to call regular, synchronous actions to update the app state & trigger re-draws. The "Gif Search" tutorial in the docs is a good example -- see the downloadGif action.
https://github.com/hyperapp/hyperapp/blob/master/docs/tutorials/gif-search.md

Hope that clears things up for you! :smile:

@SkaterDad Thanks a lot, but my idea is that your explanation should be located directly in the documentation. Of course i've seen the "gif search" example but I was unable to deduce right understanding from that tutorial. Or i missed something?

@SkaterDad Thanks for explaining. @algebraic-brain I added it to the docs and will push this soon with a few other changes. I hope future arrivers will not go through this pain. Thank you, both!

Firstly, I think hyperapp is awesome!

I agree with @zaceno about having a dedicated actions page in the docs. One specific challenge I had, and I think many other users of hyperapp will run into, is how to correctly update deeply nested state in an immutable way.

From hello-world.md:

Actions don't mutate the state directly but return a new fragment of the state. If you try to mutate the state inside an action and then return it, the view will not be re-rendered as you might expect. Immutable state updates allow cheap memoization of the view and components using only a strict === check.

This makes sense, but I didn't fully understand what this implies right away about the process of updating deep state. I think that some example of how this comes into play would improve comprehension of how actions should be created.

From what I understand, the pattern employed (when state slices can't get all the way to the nested level) is to use the spread operator to re-create the entire object sub-structure of a slice of state. For instance, in one case I needed access to a higher-up state for something (currentTeamCollection below, in my case) so I employed the spread-operator to compose the nested levels of the new immutable state-slice (which I learned via browsing hyperapp issues and PRs)

const state = {
  user: {
    currentTeamCollection: 'test',
    teamCollections: {
      'test': {}
    }
  }
}

const actions = {
  user: {
    addTeamToCurrentCollection: team => state => {
      let tc = state.teamCollections[state.currentTeamCollection]
      if(!tc[team.name]) tc[team.name] = team
      return {
        teamCollections: {
          tc,
          ...state.teamCollections
        },
        ...state
      }
    }
  }
}

This solution of updating nested state as shown above took me awhile to figure out, and I think many people would benefit from some sort of guidance of how to update deep state. Maybe to the more experienced, this process is common sense (or there is a better way) but I think many would benefit from an example of this being done.

If the docs had a tutorial that showed an example of deep state, and actions that updated the deep state, I think it would be helpful. Currently all three tutorials on the docs have shallow state only.

I think the actions page should also make clear the function signatures that actions are supposed to comply with. I didn't realize that actions are designed to accept one parameter only, and this confused me for awhile, until I read into the issues and PRs.

I hope these thoughts can help specifically with making actions more quickly understood by new users.

Thanks for reading!

@CanyonTurtle Just confirming, did you know you can do this?

const state = {
  deeply: {
    nested: {
      value: 1
    }
  }
}

const actions = {
  deeply: {
    nested: {
      multiply: by => state({ value: by * state.value })
    }
  }
}

If the docs had a tutorial that showed an example of deep state, and actions that updated the deep state, I think it would be helpful. Currently all three tutorials on the docs have shallow state only.

True, true! ๐Ÿ™‡

I didn't realize that actions are designed to accept one parameter only, and this confused me for awhile, until I read into the issues and PRs.

I'll document this! ๐Ÿ‘

const state = {
  valueHere: 4,
  deeply: {
    nested: {
      value: 1
    }
  }
}

const actions = {
  deeply: {
    nested: {
      // how can I access valueHere in this action?
      multiply: by => state({ value: by * state.value })
    }
  }
}

Thanks for the response time! It's just that I don't know how to use higher-up state.

@CanyonTurtle You can't! There is no way deeply.nested could access valueHere and we don't want to introduce that as it would create implicit dependencies.

What you coulld do is nest all those objects inside a common parent object and have actions under that path manage the objects below.

@JorgeBucaran , Thanks for the clarification. I just think that what the documentation could also include an example of, is where there is a common parent object, managing its deep children. That is the situation similar to the code example I posted above in that long post, and what I am saying is that this case would be one worth explaining briefly.

Specifically,

// ...
 return {
        teamCollections: {
          tc,
          ...state.teamCollections
        },
        ...state
      }
// ...

I found this application of spread operators to update the layered state to be not immediately clear, and I am suggesting this as something to show an example of, or briefly cover in the documentation.

Even though this deep-copying method is a natural conclusion from understanding javascript to a certain level, I think many users would get a running start from an example or two about it.

@CanyonTurtle You are right. ๐Ÿ‘

Thanks for listening! Hyperapp is awesome again.

@JorgeBucaran
hey, I believe it's more important to get clear understanding of how and why things work inside of framework, as result you can use hyperapp more efficient and try to avoid some pitfalls (like proper using async actions, passing props, etc).

Annotated hyperapp source could be really useful for newcomers
(could be generated with docco or something similar)

Where did you guys land on the lazy loading stuff? I tried to follow it through the past issues chain but I got lost in the references and closing... Does anybody have an example?

@brandonros I believe the current option is using a higher order function of the app function (HOA).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

icylace picture icylace  ยท  3Comments

jbrodriguez picture jbrodriguez  ยท  4Comments

VictorWinberg picture VictorWinberg  ยท  3Comments

zaceno picture zaceno  ยท  3Comments

dmitrykurmanov picture dmitrykurmanov  ยท  3Comments