DEV Community 👩‍💻👨‍💻

Ivan
Ivan

Posted on • Updated on

Easy apps with hyperHTML — 3, components and state

Moar about components and simple state management

Version en español

Part 3 written by

pinguxx image
paritho image
  1. Introduction, wire/bind
  2. Events and components
  3. Moar about components and simple state management
  4. Wire types and custom definitions (intents)
  5. Custom elements with hyper
  6. Customizing my custom elements
  7. Testing!
  8. Async loading, placeholder and a Typeahead with hyper
  9. Handling routes
  10. 3rd party libraries

In part 2, we used hyper Components to create a table that can be sorted. Before we add to our table, let’s review the code we previously wrote.

Table components

Did you notice there are parts of the code we can reuse? If we refactor our code, we can use parts for other tables and it will be easier to maintain those parts instead of updating the entire table for every change. These parts are great candidates for such a refactor:

  • The main table definition
  • The header (row and columns)
  • Body (rows and columns)
  • Footer… well we don’t have a footer yet but we will add one just for fun

Let’s look at how we can change these parts to be more maintainable and reusable.

Table

First, let’s create a Table.js file and move most of the code there. We won’t need to use bind() in this file, instead we’ll export our Table.

Next, let’s update index.js to import our table. This is where we’ll use the bind() function. Recall that bind() works on existing domNodes like document.body. Also note how we’re passing information to the Table: through an object in the constructor.


Header

Our Header class will extend Component as well.

Let’s first move the <thead> element of our table to render(), making it look like this:

In this template we have onclick listeners attached to our <th> tags. Since we don’t want to manage the state in this class, we are going to dispatch a custom event called sort. This custom event will have some details about the sorting like the column we are sorting on and if it is ascending or descending.

We are going to add an update function as well. By using this function, we can be sure we’re always rendering with the current data. If the data changes in the parent, our header will receive the new data. It will look like this:

Dispatch is a function provided by Component. It will create a custom event with a name based on the first parameter, and the details object based on the second parameter. This is a pretty useful function. Learn more about dispatch() in the documentation. Our onclick event handler now looks like this:

And here’s the complete Header class:

Now let’s update Table to load the header component. Import again is our friend. Then, in place of the <thead> mark-up in the render, let’s use the Header class ${Header.for(this).update(this.state)}.

Component.for its a utility that helps you create components in render(). Instead of creating the component somewhere else, you can create it right there in the function itself. Header.for(object) will then tie the header to the object passed, in this case our current table class, then call update to re-render the header with the state, this will passed on every render. We will use other ways to instantiate the modules later on. Read more in the documentation.

Lastly, we will add an event listener called onsort to the table: onsort="${this}". This function will listen to the sorting event we dispatched from the Header. We need to change the onclick function to onsort, and we’ll also simplify it a little. The information about the sort is coming in the event detail object. We can then sort the array like before and update the state of the table. Remember, we are passing this state down to the Header class.

Let’s take a look at the full code to this point:


Body

For our body component, let’s do the same thing — move the tbody from the render function of the Table to a new component called Body. Body will have its own render function; we’ll put the tbody code here:

Despite being the component that renders most of our table, this component is actually pretty compact. Let’s see the full file:

Notice the line:

this.props = props;

We’ll use this to pass data to the Body component. The information we pass will be the information shown in our Table.

Now, let’s update Table to load the Body component, using import just like before:

import { Body } from './Body';

In this case, let’s mix it up a little bit. Instead of Component.for, let’s create a new Body in the constructor. We don’t need to pass data on instantiation.

this.body = new Body();

With that, we have a table that will work correctly. It will sort, but our data is not really changing (adding or removing). But what if the data does change? We can add the update function inside Body to receive data, just like in Header.

This way we always receive the latest data from the Table. Now, we call the update() function on the Body component directly:

this.body.update(this.data)

Let’s see how it looks.


Footer

And as promised… we are going to add a little footer to our table. The footer will show the total number of records. Beside the html for the footer, there is nothing new in this class. Take a look:

We’ll update the Table component to load the Footer component. Let’s use Component.for and the update function since we always want to receive the latest data. Otherwise, our count of the items in the table will not be accurate.

Footer.for(this).update(this.data).

Aaannddd we are done! We have our simple table with simple state management: we pass data down and events up. We could have used a state management library, Redux or Flux. For our Table, we don’t really need to use anything more complex than what we currently have.


Before we move on to part 4, let’s explore a small library called ProppyJS. ProppyJS isn’t a state management library, but a library that allows us to compose properties. This is useful to compose props from various sources and use them in any component — in essence, we’re allowing ourselves a vast amount of freedom with our apps. With proppy, we could easily attach a redux store if we needed it later, when our component state gets more complex.

We created a prop factory and we use that p everywhere. It condensed our code a lot, and now the header is updating the sort directly. Let us know in the comments if you have questions about implementing ProppyJS.


We want these tutorials to be as awesome as they can be! If you have feedback, please be sure to leave it in the comments. Thanks for reading, and stay tuned for the next part where we’ll explore a powerful feature called “intents.” Intents will let us expand hyperHTML with custom definitions for our templates.

Top comments (0)

Update Your DEV Experience Level:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. 🛠