The Exact Same App in Hyperstack

Catmando
Catmando
Mar 16, 2019 · 7 min read

Hyperstack is a full stack framework. And when we say full stack we mean it. With Hyperstack you build your entire app in Ruby, from HTML layout and event handlers all the way to the database models.

On the server side we currently leverage Rails goodness, and on the client side, we layer an easy to use Ruby DSL on top of React.

BTW if you like the following at all, be sure to read part II. I think you will be amazed!

The best way to appreciate Hyperstack is to look at some code. Earlier today I found this excellent blog post by Sunil Sandhu comparing the differences between Vue and React. While that post only examines the client side of things, I thought it would be a great place to start.

In the React version of Sunil’s Todo App, there are two components: The top level component — ToDo — and a subcomponent named ToDoItem. Our Hyperstack app will have the same structure.

Let's start by looking at the original React ToDoItem component:

In Hyperstack this translates to:

Basics

The actual structure is very similar. Like React and Vue, Hyperstack builds the UI out of components. In fact, Hyperstack components are React components. Instead of using JSX to create the underlying React API calls, Hyperstack uses Ruby methods.

Each of the HTML and SVG tags is represented as a Ruby method with the same name. For example, you can see there are DIV, P, and BUTTON tags in the above example.

  • Attributes (and component props) are passed as normal Ruby method parameters.
  • Each tag or component can also receive a block which will define the child components.
  • In Hyperstack, props (which we refer to as params) are represented as Ruby instance variables.

Hyperstack does require each component’s params be declared. So we see that param :item results in the @Item instance variable being mapped to the item parameter. We camel-case the param name so it stands out easily when reading the code.

Like JS each Ruby object has some instance data associated with it. In JS you would access this data by using this. this.stuff = 12 for example. In Ruby you would say @stuff = 12 .

Putting this all together we see that

<p className='ToDoItem-Text'>
{this.props.item}
</p>

is the same as

P(class: 'ToDoItem-Text') do 
@Item
end

In detail what the above code is saying is: call the method P and pass to it the parameter class with the string ‘ToDoItem-Text’, as well as block of code
{ @Item } The block acts like anonymous callback method. When its called, it will return the value of @Item .

Other Features

A couple of other features and we can fully understand the elements of the Hyperstack syntax used in this component.

The BUTTON tag takes an event handler. Event handlers (or any callback) can be attached to a component using the on method. The on method takes a list of one or more events, and when the event is fired the attached block will execute.

In our case when the button is clicked we want to send a delete_itemmessage out to the parent component.

To do this we use thefires :delete_item declaration. This is telling us that the component will fire a custom event (or callback) called delete_item. To send the event you use the event’s name followed by a ! .

Again like the rest of the syntax, the mapping is straight forward. In React callbacks and event handlers are passed as props. In Hyperstack you can do this as well, but the on method provides a cleaner syntax to accomplish the same thing. We will see in the next component how this makes attaching several event handlers very tidy.

A Bigger Component

Let's go on and compare the second component - ToDo- which will use TodoItem to display our list of todos. Here is the JSX code:

and the equivalent Ruby code:

Less Boiler Plate

Before we dive in, notice that in Hyperstack we don’t have a lot of unnecessary JS boilerplate declaring imports and exports. This is taken care of us by the Ruby language and the underlying framework. This concept of eliminating boilerplate is vigorously followed throughout Hyperstack.

Initializing Components

Moving on to the first block of JS code we see our JS component using the class constructor to initialize the component’s state.

In Hyperstack the component’s initializer is not used by the application, but instead, the before_mount macro defines any actions to be taken before the component is mounted.

In our case, we initialize two instance variables @list and @todo. The @list variable is an array of hashes, each hash representing a todo. The @todo variable contains the current todo being created by the user.

Note: Simply as a stylistic choice I moved the initial list of todos out into a constant.

Instance Variables Represent State

Like Vue, Hyperstack treats state simply as instance data. Unlike Vue, you will see that we will have to explicitly notify the framework with state data changes. We think this is a good balance between performance, programmer productivity, and readability.

Changing State

The create_new_todo_item is responsible for pushing the current new todo (@todo )onto the list of todos, and then clearing the value of @todo

The code looks like this:

def create_new_todo_item
mutate @list << {todo: @todo},
@todo = nil
end

Ruby’s push operator is used to shove a new hash (roughly the same as a JS object) containing the current value of @todo onto the list. Then we set the value of @todo back to nil. The mutate method will then be called to notify the system that this object’s state has changed.

If we forgot to call mutate then the system would not know that the data in @list and the value of @todo had changed.

The rule is simple: You can read state anytime by accessing any instance variable, but when you change the value of the variable or its contents you must call mutate.

Mounting Children

The rest of our component uses the same constructs as the previous TodoItem component. The one new thing here is the interaction between the parent component and the TodoItems.

DIV(class: 'ToDo-Content') do
@list.each do |item|
TodoItem(key: item, item: item[:todo])
.on(:delete_item) { mutate @list.delete(item) }
end
end

If you are unfamiliar with Ruby you might be puzzled by do…end vs { … } . Both of these constructs do the same thing: They declare a block of code to be passed to a method. They can be used interchangeably but accepted Ruby style uses { … } when the block fits on one line, and do…end otherwise. Like JS the curly brackets are also used to define hash constants, but you can easily tell which is which from context.

Inside the DIV we iterate over our list of todos, and for each item, we will add a TodoItem child component. Each child is passed the item to display. Then we attach an event handler to capture any delete_item events that the child may fire.

If you are new to React as well as Hyperstack this a great little piece of code to understand the underlying power of React’s declarative programming:

Imagine the list has 3 items in it. So we will loop over the list using the each method, and for each item, we will display the item using the TodoItem component. If the user clicks the delete button on one of those components it will cause the on(:delete_item) block to execute which will delete that item, and then call mutate. Seeing our instance data changing we re-evaluate our list and redraw it now with one less component. Clever code inside react makes sure that this is all done very effeciently. Sheer Magic!

Chaining Event Handlers

A nice side effect of Ruby syntax is that event handlers can be chained together. Thus we see

INPUT(type: :text, value: @todo)
.on(:change) { |e| mutate @todo = e.target.value }
.on(:enter) { create_new_todo_item }

which is attaching both a change and an enter event handler to the INPUT .

This syntax is just shorthand for saying:

INPUT(
type: :text, value: @todo,
on_change: ->(e) { mutate @todo = e.target.value },
on_enter: -> { create_new_todo_item }
)

Other Niceties

  • Like Vue, Hyperstack defines a helpful enter event which is fired whenever the enter key is pressed.
  • Any object will generate an appropriate unique key to that object so we can simply say key: item which keeps us from knowing details of how items are stored.
  • All instance variables are initialized to nil, and where appropriate nil will be treated as an empty string.

BTW: If you want to look at the entire App, build it and play around on your box its all here: https://github.com/hyperstack-org/todo-compare.

Hyperstack vs. React…

In truth, you don’t have to think of it that way. As you can see the Hyperstack UI is really just React with what we think of as a much improved and integrated JSX. Indeed you can directly interoperate between Hyperstack and React components.

A lot of the things that I think people like about Vue are already incorporated into Hyperstack and a lot of the shortfalls of React are overcome.

The proof, however, is in the pudding. In our little experiment, we wrote just half the code to accomplish the same task. There is less boilerplate, state is easier to understand and manage, and because Ruby has more syntactic hints (i.e. not everything is a curly brace and a function call) the code is easier to read.

The Bigger Picture

But this is just a very small slice of what Hyperstack offers. Here we have looked at a simple component that has no data persistence and does not interact with the larger world around it.

In part II we will add in just a few lines of code the ability for our Todo App to store its data on the server, and to synchronize the data between multiple clients.

To give you a little hint, all we will have to do is change our Array of Todos to be a Rails Todo Model declared in just a few lines of code. Because everything is written in Ruby we will not create any additional code beyond the minimum to describe our model's behavior.

Stay tuned…

P.S. If you want to know more right now visit the Hyperstack site.

Catmando

Written by

Catmando

AKA Mitch VanDuyn, is the founder and chief mad scientist at CatPrint.com, an internet printing company.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade