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

What documentation would you like to see here? #531

Closed
jorgebucaran opened this issue Jan 2, 2018 · 61 comments
Closed

What documentation would you like to see here? #531

jorgebucaran opened this issue Jan 2, 2018 · 61 comments
Labels
docs You read it here first

Comments

@jorgebucaran
Copy link
Owner

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?
@jorgebucaran jorgebucaran added the docs You read it here first label Jan 2, 2018
@frenzzy
Copy link
Contributor

frenzzy commented Jan 2, 2018

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.
    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
        <main>
          {counterView(state.counterA, actions.counterA)}
          <hr/>
          {counterView(state.counterB, actions.counterB)}
          {/*
            or it is better to pass props manually?
            <CounterView count={state.counterB.count} up={actions.counterB.up} />
            <counterView /> does not work because of jsx naming requirement?
          */}
        </main>
      ),
      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

@SteveALee
Copy link

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.

@rajaraodv
Copy link

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.

@SteveALee
Copy link

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

@Ch4s3
Copy link

Ch4s3 commented Jan 2, 2018

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.

@timjacobi
Copy link

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.

@selfup
Copy link
Contributor

selfup commented Jan 2, 2018

@timjacobi That's really cool! I never knew that was a thing haha 🎉

@jorgebucaran
Copy link
Owner Author

jorgebucaran commented Jan 3, 2018

@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 jorgebucaran changed the title What documentation would you like to see on this repository? What documentation would you like to see here? Jan 3, 2018
@Ch4s3
Copy link

Ch4s3 commented Jan 3, 2018

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

@jorgebucaran
Copy link
Owner Author

jorgebucaran commented Jan 3, 2018

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

@Ch4s3
Copy link

Ch4s3 commented Jan 3, 2018

Awesome, thanks @jorgebucaran.

@SteveALee
Copy link

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

@jorgebucaran
Copy link
Owner Author

@SteveALee If it can be better re-worded and re-written, that would be 💯

@kosirm
Copy link

kosirm commented Jan 3, 2018

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 😄

@jorgebucaran
Copy link
Owner Author

jorgebucaran commented Jan 4, 2018

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

@SteveALee
Copy link

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

@kosirm
Copy link

kosirm commented Jan 6, 2018

@jorgebucaran

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

sorry for misunderstanding topic... Thanks for understanding 👍

@kenOfYugen
Copy link

kenOfYugen commented Jan 6, 2018

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?

@jorgebucaran
Copy link
Owner Author

Thanks @kenOfYugen! 😌

They caught you @marcusasplund! 🚔 😉

@marcusasplund
Copy link

@kenOfYugen Thx for the heads up, please review

@SamP692
Copy link

SamP692 commented Jan 9, 2018

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

@infinnie
Copy link
Contributor

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

@mightyplow
Copy link

mightyplow commented Jan 16, 2018

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.

@jorgebucaran
Copy link
Owner Author

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

@mightyplow
Copy link

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

@Siilwyn
Copy link

Siilwyn commented Jan 19, 2018

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

@jorgebucaran
Copy link
Owner Author

jorgebucaran commented Jan 20, 2018

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

@infinnie
Copy link
Contributor

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

@kenOfYugen
Copy link

kenOfYugen commented Jan 21, 2018

@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 😄!

@Siilwyn
Copy link

Siilwyn commented Jan 21, 2018

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

@okwolf
Copy link
Contributor

okwolf commented Jan 21, 2018

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

@rhbecker
Copy link

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

@jorgebucaran
Copy link
Owner Author

jorgebucaran commented Jan 22, 2018

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 <insert framework here> wants me to use. The meat of it is I want to grow skills in JavaScript, not skills into frameworks.

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

@rhbecker
Copy link

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?

@jorgebucaran
Copy link
Owner Author

jorgebucaran commented Jan 22, 2018

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.

@jorgebucaran
Copy link
Owner Author

jorgebucaran commented Jan 22, 2018

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.

@SteveALee
Copy link

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

@daz4126
Copy link

daz4126 commented Jan 23, 2018

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

@zaceno
Copy link
Contributor

zaceno commented Jan 24, 2018

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.

@algebraic-brain
Copy link

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.

@SkaterDad
Copy link
Contributor

@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! 😄

@algebraic-brain
Copy link

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

@jorgebucaran
Copy link
Owner Author

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

@CanyonTurtle
Copy link
Contributor

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!

@jorgebucaran
Copy link
Owner Author

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

@jorgebucaran
Copy link
Owner Author

jorgebucaran commented Jan 25, 2018

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! 👍

@CanyonTurtle
Copy link
Contributor

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.

@jorgebucaran
Copy link
Owner Author

jorgebucaran commented Jan 25, 2018

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

@CanyonTurtle
Copy link
Contributor

CanyonTurtle commented Jan 25, 2018

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

@jorgebucaran
Copy link
Owner Author

@CanyonTurtle You are right. 👍

@CanyonTurtle
Copy link
Contributor

Thanks for listening! Hyperapp is awesome again.

@sergey-shpak
Copy link
Contributor

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

@brandonros
Copy link

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?

@jorgebucaran
Copy link
Owner Author

jorgebucaran commented Jan 31, 2018

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs You read it here first
Projects
None yet
Development

No branches or pull requests