loading...

Functional Programming in JavaScript with Hyperapp

aspittel profile image Ali Spittel ・6 min read

I kinda feel like a traitor given my pretty long history with Vue and React, but I think I have a new go-to frontend framework. Hyperapp is everything that I wanted Elm to be -- it's easy to build code with, highly organized, and state is handled flawlessly. That being said, it's not as production ready as the aforementioned frameworks, but once it is I can see it being huge.

Let's start with zero on building a Hyperapp app -- with tools that are still emerging, I usually go more in-depth. I'll do the same here for Hyperapp.

Getting Started

A few weeks ago, I saw a few articles about Hyperapp when they released version 1.0 and surpassed 10,000 stars on GitHub. I looked briefly at the counter "hello world" in their documentation. I really liked how clean and simple it looked, and I wanted to try it out!

const { h, app } = hyperapp

const state = {
  count: 0
}

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

const view = (state, actions) =>
  h("div", {}, [
    h("h1", {}, state.count),
    h("button", { onclick: () => actions.down(1) }, ""),
    h("button", { onclick: () => actions.up(1) }, "+")
  ])

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

You can also use JSX instead of calling the h function to create elements. That's what I did since I am used to it from React! I looked through the examples on Hyperapp's Codepen. I ended up using a boilerplate so that I didn't have to setup webpack for transpiling JSX or have to deal with the setup. It was awesome, I didn't have any issues using it and it came with a file structure that I enjoyed using.

The File Structure I Used

Hyperapp uses an architecture inspired by Elm's -- it has views, models, and updates. It also follows the functional philosophy, similar to Elm. That means that state is immutable and the actions don't have side effects. The state management felt more like Redux than standard React, since state is centralized rather than component specific. Also, you do have to use thunks in order to build impure functions. The architecture and setup was smooth to work with and I didn't have many problems with it at all.

Since I've worked with Elm, React, Redux, Vue and Vuex in the past, I recognized the patterns and felt fine moving on to the final project after reading the documentation (which is minimal) and looking at the code examples.

The Final Project

I wanted to build something that would pull from an API -- which can be a relatively messy process in Redux. I didn't have one in mind, so I browsed this list to try and find one. I ended up using the FavQs API -- I had an idea to make a rotating list of quotes with a search available for the tags on different quotes. This would allow me to interact with the state quite a bit.

The first code that I wrote was the model for the state. I set initial properties for the attributes I needed in my project:

export default {
  quotes: [],
  term: '',
  index: 0
}

Here, something like TypeScript or Flow would have been nice to enforce typing. I'm sure they could be integrated easily enough into a hyperapp project.

The quotes were an array of the quotes coming back from the API, the term was the search term if the user specified that, and then the index was the current index of the quote the user was looking at.

I had a config file where I defined some constants to use throughout:

export const API_URL = 'https://favqs.com/api/quotes/'
export const COLORS = ['#DBEBFF', '#FFBBDD', '#e6f9ff', '#BBBBFF', '#F7FFFD', '#fff8e1']
export const FONT_COLORS = ['#62D0FF', '#FF62B0', '#33ccff', '#5757FF', '#03EBA6', '#ffb300']

I also made a services file that held Axios (a minimalist AJAX library) requests for my searches:

import axios from 'axios'
import { API_URL } from './constants'

const getRequest = url => {
  return axios.get(url, {
    headers: {'Authorization': `Token token="XXXXXXXX"`}
  }).catch(
    err => console.log(err)
  )
}

export default {
  getAll: _ => getRequest(API_URL),
  getQuery: query => getRequest(API_URL + `?filter=${query}&type=tag`)
}

The above files are framework agnostic, but I wanted to include them for context.

Potentially the most crucial file held the actions:

import request from '../config/request'

export default {
  getQuotes: quotes => (state, actions) => request.getAll().then(
    actions.setQuotes),
  submitSearch: quotes => (state, actions) => request.getQuery(
    state.term).then(actions.setQuotes),
  setQuotes: res => ({ quotes: res.data.quotes.filter(
    quote => quote.body && quote.body.length < 150) }),
  updateSearch: ({ term }) => ({ term }),
  next: e => ({ index, quotes }) => ({ index: index + 1 }),
  prev: e => ({ index, quotes }) => ({ index: index - 1 })
}

I used thunks for getQuotes and submitSearch -- meaning that I just actions a function from a function rather than a value. This allows for impure functions within the nested function, especially since the data from APIs is less predictable than functional programming requires. Since the Axios requests take a bit to execute, the state isn't actually updated until the setQuotes method is called after the data is fetched from the API. The other actions are relatively straight forward! The event handlers do take the event first and then the current state afterwards -- I did find this a bit "magic-y" but overall the experience with the actions was very smooth.

Finally, I created the views. The main view looked like this:

import { h, app } from 'hyperapp'
import Search from './Search'
import Quote from './Quote'
import { COLORS, FONT_COLORS } from '../config/constants'

const quote = (quotes, index) => quotes[index]
const color = index => COLORS[index % COLORS.length]
const fontColor = index => FONT_COLORS[index % FONT_COLORS.length]

export default ({ quotes, index }, { getQuotes, updateSearch, submitSearch, next, prev }) =>
  <div
    oncreate={getQuotes}
    className={ quotes ? 'body' : 'body hidden' }
    style={{ 'backgroundColor': color(index), 'color': fontColor(index) }}
  >
    <div className='centered-content'>
      <div className='container'>
        { index > 0 &&
        <div
          onclick={prev}
          className='direction left'
          style={{ 'color': fontColor(index) }}>
            &lt;
        </div> }
        { quotes.length > 0 && <Quote quote={quote(quotes, index)} /> }
        { index < quotes.length - 1 &&
        <div
          onclick={next}
          className='direction right'
          style={{ 'color': fontColor(index) }}>
            &gt;
        </div> }
        <Search
          updateSearch={updateSearch}
          submitSearch={submitSearch}
        />
      </div>
    </div>
  </div>

It looks essentially identical to a functional component in React! The event handlers are lower case, but otherwise the JSX is the same. Lifecycle methods are also a little bit different. I would normally use componentDidMount method in React to make an API request, but here I used the oncreate attribute instead. They do essentially the same thing, but the syntax is different. I also didn't see documentation for subscriptions, which are important in Elm. They allow you to use Websockets and add global event listeners. Some of the GitHub issues mentioned them, though, so I would assume they are implemented but not in the documentation yet.

I also had two "subcomponents", the quote one was very simple:

import { h, app } from 'hyperapp'

export default ({ quote }) =>
  <div className='quote'>
    <h1>{quote.body}</h1>
    <h4>{quote.author}</h4>
  </div>

The search one was as well:

import { h, app } from 'hyperapp'

export default ({ updateSearch, submitSearch }) =>
  <div className='search'>
    <input
      onkeyup={
        e => {
          e.keyCode === 13 ? submitSearch() : updateSearch({ term: e.target.value })
        }
      }
      placeholder='Search quote tags...'
    />
  </div>

Finally, the index.js combined the elements from the other files so that state could be used within the actions and the views.

import { app } from 'hyperapp'
import actions from './actions'
import state from './state'
import view from './components/View'

app(state, actions, view, document.querySelector('.hyperapp-root'))

This binding is essentially identical to how Elm combines the elements!

I liked splitting my code into multiple files, and I thought that it was really scalable. I could definitely see myself building something bigger with HyperApp in the future.

A Picture of the App

Next Steps

Again, Hyperapp is one of my favorite tools I've learned recently -- next to maybe Golang. I found it to be a nearly perfect marriage of tools that I've used in the past. It is also a tiny library and is super efficient, which is exciting especially in comparison to Angular, which I learned last week! Its API is so minimalistic and it enforces functional programming so well. I would definitely recommend learning it as an onramp to React with Redux. I would 100% use HyperApp again, I found it straightforward and I really liked the elegance of the code. I do hope that the community keeps expanding, the documentation improves, and a Redux/Elm like rewind feature is implemented. Otherwise, I had an excellent experience with Hyperapp and I am already planning on using it again!

App
Code

Part of my On Learning New Things Series

Other Similar Articles:

Posted on by:

aspittel profile

Ali Spittel

@aspittel

Passionate about education, Python, JavaScript, and code art.

Discussion

markdown guide
 

Thanks for the post!

I'm not really convinced by this framework. Sure, the syntax and all the sugar coming from the API is cool, but Elm provides some interesting things and I can't live without now.

  • The compiler, the error log is awesome and there are "no runtime exceptions".

  • Type system, a type system that really works (I like TypeScript but type system could be better, try to write a high-order function and see what happens ;-) )

  • Maybe, it seems that you don't like it a lot, but the possibility to have a program without null, void, undefined or things like that is really cool.

 
 

Read bit.ly/typescriptTax

Types only contribute to a minority of potential bugs.

Types are not just for fixing potential bugs (well, not only for that). They provide a better developer experience by providing you with a way to structure your program with a "logical flow" based on the data/resource you use.

Having that let you write maintainable code with less headache and make refactoring easy (and let you be more confident about the code you write) which lead to fewer bugs non-related to types but more to the logic behind the program.

I think that JavaScript users (like Eric Elliot) don't like strict types system because of TypeScript. It provides a really poor integration with non-TS libraries and its error messages are sometimes really cryptic.

You should give a try to Elm, at least it can be fun. :)

 

Thanks for the great writeup! Definitely going to look into Hyperapp for basically the exact reasons you mentioned.

You mentioned Elm a lot in this post, and for obvious reasons. Right now, Elm occupies my "favorite frontend framework" spot (which may have a lot to do with my love of Elm's typing). What about Hyperapp do you find easier to work with than Elm? Just syntax familiarity, or is there some other clutch workflow feature missing from Elm?

Thanks again. Big fan of your work!

 

I think the syntax for sure -- Elm is a big jump because you need to learn the syntax, architecture, and functional programming. The barrier to entry is lower here. Also, since its just JavaScript, it's easier to work with libraries natively than in Elm. I also kind of disliked working with the data structures (i.e. linked lists are more fully featured than arrays) and Maybe's in Elm -- maybe that's just me though. I think if you added TypeScript or Flow into Hyperapp, you would get a pretty similar experience to Elm!

 

I started using HyperApp around a year ago, when its API was still heavily changing. I even created a small project with it and it was fun! There once were 'events' that you could use for a subscription mechanism. Now I think you can use the state-bound actions returned by the app function. See github.com/hyperapp/hyperapp/blob/...

 

Hi Ali, that's an excellent article, thanks.

I wonder what exactly you had in mind by "it's not as production ready as the aforementioned frameworks"? What did you feel is missing?

As far as I can tell it's excellent quality, has 100% code coverage and as it's small I could probably maintain it as if I had written it myself, though there is a community to help me. I saw the docs just got a major refresh which helps a lot.

Perhaps you meant the wider ecosystem of support and tooling is small? I discovered it is possible to use it with Create React App, a bastion of production readiness :). Also Visual studio and Browser F12 tools are excellent for debugging. Being small also I can pretty easily integrate almost any package I might want to without fighting with it.

 

As interesting as this is, for a small application like the one you describe in this post, I don't understand the seemingly obsessive necessity to use a framework like this.
I would like to understand why you prefer this over less than half the amount of vanilla code needed to produce identical functionality. Couldn't find a post by you yet talking about this.
Would you be so kind to provide insight?

edit: not necessarily obsessive by you, but by people in general.

 

for an application like this, probably not much of an advantage. But once you start adding complexity, muyltiple views, etc., the benefits of predictable state management, automatic rerendering on state change, and component-based architecture become extremely valuable. I don't think the point of this page is to be a tutorial on the best way to build a quote-fetching app, it is meant to be an introduction to hyperapp using a quote-fetching app as a simple, easy-to-understand entry point

 

That's really interesting Ali Spittel. You have put great efforts!! Great.

 

I have been following the series of learning new things, and I learned some things along the way, so thanks for sharing. Now that you are in the functional programming, do you have thought about try elixir lang? I would like to read your opinions about it. I am currently moving with javascript, but I espect to use elixir in the future. (I'm learning English too ¯_(ツ)_/¯ )

 

I haven't done a ton of experience with functional languages! I have more experience implementing functional paradigms in non-functional languages. I looked into Elixir a bit -- Phoenix looks a ton like Rails to me which is fascinating! Maybe I'll try!

 

I didn't know about Hyperapp. Thanks for sharing, I like its API.