Skip to content

0.14.0

Compare
Choose a tag to compare
@jorgebucaran jorgebucaran released this 06 Oct 05:43
bf61dc0

Simplified state management with state slices, replaced events with direct DOM event handling, introduced HOAs for extensibility, and added built-in hydration for interactivity. Streamlined element removal in the onremove event and removed mixins for code clarity.

State Slices

Hyperapp traditionally used a single state tree, which means that all your application-level state resides in a single object, serving as the single source of truth. This approach simplifies state management and debugging. However, updating deeply nested state immutably could be challenging without functional lenses or advanced techniques.

State slices address this challenge by providing a slice of the state tree via actions, corresponding to the namespace where both state and action are declared. Here's an example:

actions: {
  hello(state) {
    // The state is the global `state`.
  },
  foo: {
    bar: { 
      howdy(state) {
        // The state is: `state[foo][bar]`
      }
    }
  }
}

State slices make it easy to update deeply nested state immutably. For instance, updating a value like this:

state: {
  foo: {
    bar: {
      value: 0,
      anotherValue: 1
    }
  }
}

Previously required updating the entire record, including siblings. With state slices, you can update value more simply:

state: {
  foo: {
    bar: {
      value: 0,
      anotherValue: 1
    }
  }
}

And have a corresponding action inside a matching namespace:

actions: {
  foo: {
    bar: {
      updateValue(state) {
        // State is `state[foo][bar]`
        return { value: state.value + 1 }
      }
    }
  }
}

State slices also work with components. For example:

/* counter.js */
import { h } from "hyperapp"

export const counter = {
  state: {
    value: 0
  },
  actions: {
    up(state, actions) {
      return { value: state.value + 1 }
    }
  }
}

export function Counter(props) {
  return (
    <main>
      <h1>{props.value}</h1>
      <button onclick={props.up}>1UP</button>
    </main>
  )
}

/* index.js */
import { counter, Counter } from "./counter"

app({
  state: {
    counter: counter.state
  },
  actions: {
    counter: counter.actions
  },
  view: (state, actions) => (
    <Counter value={state.counter.value} up={actions.counter.up} />
  )
})

Events

This release bids farewell to events. Instead, app() now returns your actions wired to the state update mechanism, ready to go. You can register global DOM event listeners, fetch data, create socket connections, and perform tasks you would typically use events for.

For example:

const actions = app({ 
  // Your app here!
})

You can also handle events directly within your app using actions:

app({
  view(state, actions) { /* ... */ },
  state: {
    repos: [],
    isFetching: false,
    org: "hyperapp"
  },
  actions: {
    toggleFetching(state) { /* ... */ },
    populate(state, actions, repos) { /* ... */ },

    load(state, actions) {
      actions.toggleFetching() 
      fetch(`https://api.github.com/orgs/${state.org}/repos?per_page=100`)
        .then(repos => repos.json())
        .then(repos => actions.populate(repos) && actions.toggleFetching())
    }
  }
}).load({...})

Higher Order Apps

Higher Order Apps (HOAs) are a way to extend Hyperapp's functionality. A HOA is a function that receives the app function and returns a new app function. It allows tool authors to enable features that were previously possible using events.

Here's how it works:

function doNothing(app) {
  return props => app(props)
}

And it's used like this:

app(doNothing)({
  // Your app here!
})

In practice, HOAs can be used to enhance app functionality:

function doNothing(app) {
  return props => {
    return app(enhance(props))

    function enhance(props) {
      // Enhance your props here.
    }
  }
}

Hydration

Hydration is now built-in and free in Hyperapp. It allows you to turn statically rendered DOM nodes into an interactive application. Hydration works transparently with server-side rendering (SSR) and pre-rendered HTML, improving SEO optimization and time-to-interactive.

Lifecycle

The onremove lifecycle/VDOM event can now return a function that takes a remove function, simplifying element removal.

function AnimatedButton() {
  return (
    <div
      onremove={element => remove => fadeout(element).then(remove)}
    />
  )
}

Mixins

Mixins have been removed in this release. Instead, we recommend a more explicit approach to state and actions management to improve code clarity and avoid implicit dependencies.

For example:

// burger.js
export const burger = {
  state: {
    isVegan: 0
  },
  actions: {
    toggleVegan(state, actions) {
      return { isVegan: !state.isVegan }
    }
  }
}

// index.js
import { burger } from "./burger"

app({
  state: {
    burger: burger.state
  },
  actions: {
    burger: burger.actions
  }
})

This approach may be more verbose but is clear and transparent, preventing implicit dependencies and making your code easier to understand.

Acknowledgements

Thanks to @okwolf, @andyrj, @rajaraodv, @Mytrill, @Swizz, @lukejacksonn, @zaceno. 🎉