Adam Dawkins

📔 📄

An Introduction to Hyperapp - Part 4

Special Effects

Our Hangman works now, but it's got a bit of a way to go before we'd really want to show it to anyone. Anyway, before we think of ways to jazz it up, let's look back at our original MVP list:

  • The Computer picks a random word for us to guess
  • The Player inputs letters to guess the word - done
  • Like the paper version, correct letters get inserted into the word, incorrect letters get listed elsewhere -done
  • 8 incorrect guesses and the Player loses - done
  • If the Player fills in the word correctly, they win. - done

We need to do one last thing to complete our initial spec - get the computer to pick a random word.

Talking to the Internet

To get our random word, we're going to make an HTTP call to a server that is going to return as a random word. We could obviously pre-load our app with a list of words, but HTTP calls are so common and useful to make our applications work that we'll do this instead.

I've created an endpoint at https://adamdawkins.uk/randomword.json that will return a random word in the format:

{ "word": "the_random_word" }

To use something from outside our app like this, we need to use something Hyperapp calls 'effects'.

What are effects?

We run programs for their side effects. Likewise, we want our programs to be predictable, easy to compose, test, and parallelize. What if I tell you we can have our cake and eat it too?

Rather than creating a timeout or using fetch to create an HTTP request, actions are allowed to return a description of work that needs done, and Hyperapp will do it behind the scenes. Kind of like the view function returns a specification of the DOM, but doesn't touch the real DOM; an effect describes a side effect: creating an HTTP request, giving focus to an element, saving data to local storage, sending data over a WebSocket, generating a random number, etc., without executing any code!

Now, effects are not built-into Hyperapp, instead we usually import a module that has the effects we need. These modules encapsulate the implementation — the part that tells Hyperapp exactly how to do certain task.

Getting our word from the internet

Let's take a second to remind ourselves what we're trying to do here. The initial state of our application currently looks like this:

app({
init: {
word: "application".split(""),
guesses: [],
guessedLetter: ""
}
//...
//
//
});

We want to replace that hard-coded word "application" with the result of a request to our server: adamdawkins.uk/randomword.json. We can break this down into a series of small tasks:

  1. Work out how to make our server call when our application loads
  2. Make the server call and send the result to an action
  3. Write an action that sets state.word to an array of letters

Let's start with what we know. We know how to write an action that updates the state, we've done this with SetGuessedLetter:

const SetGuessedLetter = (state, letter) => ({
    ...state,
    guessedLetter: letter
    });

Writing an action that updates state.word is almost exactly the same:

const SetWord = (state, word) => ({
    ...state,
    word: word.split("")
    });

Ok, that's number 3 done. All of the 'changes' or actions we've performed so far have been in response to user input. When it comes to Effects, hyperapp has a way of letting us trigger them as the application loads.

Instead of init being just an object representing the initial state of our application, it can be an array containing that object and other effects we want to perform when the application starts. Let's assume we have an action called GetWord and see what that would look like:

app({
init: [
{
word: [],
guesses: [],
guessedLetter: ""
},
getWord()
]
//...
});

So we know how to trigger our effect, and we know how to handle the response to update the state of our application, the last thing we need is the effect itself. How are we going to make this HTTP request and hook it into these actions?

Enter @hyperapp/http.

@hyperapp/http

The @hyperapp/http package gives us a get() function that we can use to perform get requests. It's tailored to wire into hyperapp, it takes a url, an expect format and an action to pass the result to.

For this application, I've put a random word server at https://adamdawkins.uk/randomword.json, and our getWord() function looks like this:

const getWord = () =>
get({
url: `https://adamdawkins.uk/randomword.json`,
expect: "json",
action: SetWord
});

We're done!

And we're done, we have a working version of Hangman in Hyperapp, with an HTTP call, in 116 lines of code.

We'll do one more pass on this is Part 5 to add some styling and think about some different enhancements we could make.