Building Hangman with Hyperapp - Part 3
Tags: hyperapp, javascript, functional, games
Published: 2020-01-21 23:30:18

---
title: Building Hangman with Hyperapp - Part 3
published: true
description: Learn how to add Actions in Hyperapp
tags: #hyperapp, #javascript, #functional, #games
---

# And... Action

In [Part 1](https://dev.to/adamdawkins/an-introduction-to-hyperapp-part-1-70p), we introduced the basics of Hyperapp, and in [Part 2](https://dev.to/adamdawkins/an-introduction-to-hyperapp-part-2-492h), we did a rough sketch of the game of _Hangman_ in code.  But we're not going to win any awards just yet - the user can't actually do anything.

To handle interactions with the user, or any other form of event, Hyperapp gives us Actions.

Let's quickly check our spec again:
* The Computer picks a random word for us to guess - hard-coded for now
* *The Player inputs letters to guess the word*
* Like the paper version, correct letters get inserted into the word, incorrect letters get listed elsewhere
* 8 incorrect guesses and the Player loses - done
* If the Player fills in the word correctly, they win. - done


Before anything else, we're going to want to use the [Object rest/spread](https://github.com/tc39/proposal-object-rest-spread) syntax in Javascript quite a bit with Hyperapp, and we need to add that to our build so parcel can use it. 

'Object rest/spread' syntax allows us to want all of the existing properties of an object, and then override the ones we want to change. It reads nicely, but it's also an important way of doing things - whenever we change the state in Hyperapp we actually need to create a _new_ state object, and object spread does just that.

Here's a quick example:

```js
const cat = {
name: 'Larry',
      legs: 4,
      sound: 'meow',
};

const dog = {
  ...cat, // <-- this is the spread we want to support
  sound: 'woof',
};

console.log(dog); // => { name: 'Larry', legs: 4, sounds: 'woof' }
```
Here our `dog` kept the `name` and `legs` properties of `cat`, but had it's own `sound`. We'll use this syntax when we want to return a new version of our `state`. Let's get it setup.

```bash
yarn add babel-plugin-transform-object-rest-spread -d
```

Put the following in a file called `.babelrc`:

```json
{
  "plugins": ["transform-object-rest-spread"]
}
```

Now that's out of the way, we'll start by building a form for our user to enter letters. I've included some basic styling on the input.

```js
import {
  div,
    h1,
    h2,
    ul,
    li,
    span,
    input,
    label,
    form,
    button,
} from '@hyperapp/html';

// ...

// VIEWS
// ...

const UserInput = () =>
form({}, [
    label({for: 'letter'}, 'Your guess:'),
    ,
    input({
type: 'text',
id: 'letter',
maxlength: 1,
style: {
border: '2px solid black',
fontSize: '36px',
width: '1.5em',
margin: '0 1em',
textAlign: 'center',
},
}),
    button({type: 'submit'}, 'Guess!'),
]);

// THE APP
app({
init: {
word: 'application'.split(''),
guesses: [],
},
view: state =>
div(
  {},
  isGameOver(state)
  ? h1({}, `Game Over! The word was "${state.word.join('')}"`)
  : isVictorious(state)
  ? [h1({}, 'You Won!'), Word(state)]
  : [UserInput(), Word(state), BadGuesses(state)],
  ),
node: document.getElementById('app'),
});
```

Nothing happens... Let's change that with an action.

Actions take the current state, an optional argument and return a new state.

For now, we just want to get our action working when we submit the form, so we'll hardcode the letter 'z' into the guess.


```js
// ACTIONS

const GuessLetter = state => ({
    ...state,
    guesses: state.guesses.concat(['z']),
    });
```

NB: We use `concat` here instead of `push` because Hyperapp always wants a _new_ state object, not a change to the existing one. To put it formally, state in Hyperapp is _immutable_.

When the `GuessLetter` action is called, we return the current state, with the letter 'z' added to the guesses.


We want to call this when the user submits the form, or on the `submit` event. 

```js
form({ onSubmit: GuessLetter } // ...
    ```

    This is the gist of it, but it won't actually work yet, because by default, submit events change the URL and refresh the page. We need to stop the default behaviour. We can do that manually, by calling `event.preventDefault()`.


    ```js
    form(
      {
onSubmit: (state, event) => {
event.preventDefault();
return GuessLetter;
},
},
```
This works, but it introduces a lot of extra boilerplate code all over our view. After all, Javascript UIs are _all about events_, or we'd just be building in plain HTML. Hyperapp has a `@hyperapp/events` package that has some useful helper functions for this sort of thing.

## Introducing Events

Let's install the  package:

```bash
yarn add @hyperapp/events
```

And we'll use the `preventDefault` helper function from there to stop our form refreshing the page.

```js
    import {preventDefault} from '@hyperapp/events';

    // ...

    // VIEWS

    const UserInput = letter =>
    form(
        {onSubmit: preventDefault(GuessLetter)},
        // ...
        );
    ```

    Now we can repeatedly guess the letter 'z' when we submit the form. Let's take it where we need to go, and capture the user input.


### Capturing User Input
    A key concept in Hyperapp is that there's only one state, and changing the state refreshes our 'loop' around the application. As such, we need to store the user's guessed letter before we submit the form so that we know which letter they've guessed within our `GuessLetter` action. 

    This is where we want our `GuessLetter` action to go:

    ```js
    const GuessLetter = state => ({
        ...state,
        guesses: state.guesses.concat([state.guessedLetter]),
        guessedLetter: '', // reset the letter after the user has guessed it
        });
```

So, let's add a `guessedLetter` to our state, set the input to be the same value as it, and change it whenever the value of the input changes.


1. Add the `guessedLetter` to our initial state.
```js
//  THE APP
app({
init: {
word: 'application'.split(''),
guesses: [],
guessedLetter: '',
},
// ...
});
```

2. Pass the letter to our `UserInput` view, and set it as the value of the `input` so that we can display it:

```js
// VIEWS

const UserInput = letter =>
form({onSubmit: preventDefault(GuessLetter)}, [
    label({for: 'letter'}, 'Your guess:'),
    ,
    input({
value: letter,
// ...
},
}),
    button({type: 'submit'}, 'Guess!'),
]);

// THE APP
app({
    // ...
view: // ...

[UserInput(state.guessedLetter), Word(state), BadGuesses(state)],
// ...
});

```

3. Change `state.guessedLetter` when the input changes.

The `onInput` event we have takes two arguments, the current state, passed in automatically from Hyperapp, and the event that was triggered, so we can use that to to do this action in line:

```js
input({
value: letter,
onInput: (state, event) => ({...state, guessedLetter: event.target.value}),
// ...
},
});
```

And, just like that, we can now make guesses with an input. We have Hangman.

### Getting Better

There's still more work to be done though, we need to make the word random, and we can tidy up some of the user experience. We'll look at those in the next part. 

Before you go, let's tidy this up a bit.

1. We'll take the styling out into a stylesheet: `style.css`

```css
.input {
border: 2px solid black;
        font-size: 36px;
width: 1.5em;
margin: 0 1em;
        text-align: center;
}
```

```html
<!-- ... -->
<head>
<link rel="stylesheet" href="./style.css">
</head>
<!-- ... -->
```

```js
// VIEWS

const UserInput = letter =>
form({/* ... */}, [
    // ...
    input({
      // ...
class: 'input', // add the class 'input'
// remove the style: block
// ...
}),
    // ...
]);
```

2. Get rid of the inline action.


```js
// ACTIONS

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

input({
    // ...
onInput: (_, event) => [SetGuessedLetter, event.target.value],
});
```

This is better, but another helper from `@hyperapp/events` allows us to abstract this pattern of using the event target value

```js
import {preventDefault, targetValue} from '@hyperapp/events';

// VIEWS
input({
    // ...
onInput: [SetGuessedLetter, targetValue],
});

```

So there we are, 101 lines of code, and we have a working Hangman. Let's *make it better* by introduction random words - in [Part 4](https://dev.to/adamdawkins/an-introduction-to-hyperapp-part-4-5faa).

* [Part 3 code](https://github.com/adamdawkins/hyperapp-hangman-part-3)

* [Part 1](https://dev.to/adamdawkins/an-introduction-to-hyperapp-part-1-70p)
* [Part 2](https://dev.to/adamdawkins/an-introduction-to-hyperapp-part-2-492h)
* [Part 4](https://dev.to/adamdawkins/an-introduction-to-hyperapp-part-4-5faa)

---
This tutorial was originally posted to [adamdawkins.uk](https://adamdawkins.uk) on [7th October 2019](https://adamdawkins.uk/2019/10/7/an-introduction-to-hyperapp-part-3)