Simple State Management in JavaScript with Nanny State

Darren Jones
Share

Nanny State is a tiny library that I wrote to help make it easy to build state-based web apps using Vanilla JS. It’s similar to React, but with far less overhead and new syntax to learn. It also uses a single app-wide state object instead of each individual component having its own state. It was inspired by HyperApp and has many similarities to to Elm.

In this post, I’m going to explain how Nanny State works and then demonstrate what it can do with a couple of examples.

One-Way Data Flow

Nanny State uses a one-way data flow model, made up of 3 parts:

  • State – an object that stores all the app data
  • View – a function that returns a string of HTML based on the current state
  • Update – a function that is the only way to change the state and re-render the view
One way data flow

In Nanny State, the state is everything. The state object is the single source of truth for your app – every bit of app data is a property of this object. Even event handlers used in the View are methods of the state object.

The View is a representation of the state as HTML. It changes whenever the state changes and allows users to interact with the app.

The Update function is the only way the state can be changed. It is a single entry-point for updating the state and ensures that changes are deterministic, consistent and predictable.

These 3 things are all you need to build an app in Nanny State. In fact, it can be summarised by asking yourself the following 3 questions:

  1. What data do I need to store in my app? This will make up the properties of the State object
  2. How do I want the app data to be presented on the page? This will help you create the View function
  3. How will the app data change when the user interacts with it? The Update function will be needed for this

Hello Nanny State!

The easiest way to see how Nanny State works is to write some code! We’ll start with a basic example and then have a go at making something a bit more complex.

The easiest way to run the following example is to use an online code editor such as CodePen, or you can run it locally by installing the nanny-state package using NodeJS.

Copy the following code into the JS section of CodePen:

import { Nanny,html } from 'https://cdn.skypack.dev/nanny-state';

const View = state => html`<h1>Hello ${state.name}</h1>`

const State = {
  name: "Nanny State",
  View
}

const Update = Nanny(State)

This shows how the 3 parts of Nanny-State work together. Let’s take a close look at each part individually:

const View = state => html`<h1>Hello ${state.name}</h1>`

Nanny state uses µhtml to render HTML. The View function always accepts the state objects as its only parameter. It then uses the html function provided by µhtml to create HTML based on the template literal it is provided as an argument.

Using a template literal means that we can use the ${variable} notation to insert properties of the state into the view. In this example we are using it to insert the value of the name property inside the <h1> element.

const State = {
  name: "Nanny State",
  View
}

The State object is where all the app data is stored. It includes any properties and values that will be displayed in the View and might change over the life-cycle of the app, such as the name property in this example.

Notice that View is as also a property of the State using object shorthand notation. Remember the state is everything – every part of the app is a property of the state.

const Update = Nanny(State)

The final line defines the Update function as the return value of the Nanny function. This can now be used to update the value of any properties of the State. In fact it is the only way that any properties of the State can be updated. It also performs the initial render of the View based on the values provided in the State. This means that a heading will be displayed that says “Hello Nanny State” as can be seen in the CodePen below:

See the Pen Nanny State – Hello Nanny State (SitePoint) by DAZ (@daz4126) on CodePen.

This example is basically just a static page though. Let’s make it dynamic by adding an input box that allows the user to enter a name that they want to say hello to. Update the code so it looks like the following:

import { Nanny,html } from 'https://cdn.skypack.dev/nanny-state';

const View = state => html`<h1>Hello ${state.name}</h1><input oninput=${state.changeName}>`

const changeName = event => Update({name: event.target.value})

const State = {
  name: "Nanny State",
  changeName,
  View
}

const Update = Nanny(State)

In this example we’ve added an <input> element to the View. Event listeners are defined inline in the view, so in this example we have an oninput event listener attached to the <input> element. This will call the changeName event handler, which is a method of the state object, whenever any input is detected. This event listener needs defining, so let’s take a closer look at it:

const changeName = event => Update({name: event.target.value})

This is a standard event handler written in Vanilla JS. It accepts an event object as a parameter as usual and when it is called, we want to update the State object, so we use the Update function, as this is the only way we can update the State.

The argument we provide to the Update function is an object that contains any properties that we want to update in the state and the respective new values. In this case we want to update the name property to the value that was entered by the user into the input field, which is part of the event object and accessed using event.target.value. This will update the state with the new value from the input field and instantly re-render the page. Using µhtml for rendering means that only the parts of the View that have actually changed get updated. This means that re-rendering after aState update is both efficient and blazingly fast.

And that’s it – your first Nanny State app! Have a go at typing and you’ll see how fast it reacts to user input … and all with just a few lines of code. You can see the code in the CodePen below:

See the Pen Nanny State – Hello Nanny State with Input (SitePoint) by DAZ (@daz4126) on CodePen.

Nanny State makes it super easy to write reactive state-based apps. As you can see, there’s not much code required to build a dynamic state-based application that reacts to user interaction. This is the beauty of Nanny State.

True or False Quiz

Now we’ve seen a basic example, let’s try making something a bit more complex. We’ll use Nanny State to build a True or False quiz game. Open up a new pen on CodePen and follow along.

We’ll start in the same way, by importing the Nanny State library:

import { Nanny,html } from 'https://cdn.skypack.dev/nanny-state'

Next, we’ll create the State object and fill it with the initial property values that the game will use:

const State = {
  score: 0,
  index: 0,
  questions: [
    {question: "A Nanny State is a country where nannies are employed by the state", answer: false},
    {question: "Nanny State is also the name of a beer", answer: true},
    {question: "Mila Kunis and Ashton Kutcher employ 16 nannies to help raise their children", answer: false},
    {question: "The Nanny McPhee films are based on the Nurse Matilda books", answer: true},
    {question: "Nanny State uses the µhtml library for rendering", answer: true},
]
}

This object contains 3 properties:

  • score – this keeps track of how many questions the player has answered correctly and starts at 0
  • index – this keeps track of which question the player is up to and corresponds to the last property which is the questions array.
  • questions – This is an array of objects with question and answer properties. The question property is a string and the answer property is a Boolean

Now we’ve created the data, let’s create the View to visualise that data:

const View = state => html`
<h1>True or False?</h1>
<h2>Score: ${state.score}</h2>
${state.index < state.questions.length ?
html`<p>${index + 1}) ${state.questions[state.index].question}</p>
     <button onclick=${state.checkAnswer(true)}>TRUE</button>
     <button onclick=${state.checkAnswer(false)}>FALSE</button>`
:
html`<h2>Game Over, you scored ${state.score}</h2>`
}`

This is a slightly more complicated View than we saw in the earlier example, but most of it should be fairly self-explanatory. After the title heading, we display the score using the score property of the State object. Then we use a ternary operator to fork the view. Because the view is written using template literals, you can’t use if-else statements to fork code, so need to use ternary statements.

This ternary statement checks if questionNumber is less than the length of the questions array, which is basically checking to see if there are still any questions left to answer. If there are then the question is displayed with two buttons, on for TRUE and one for FALSE. If there are no questions left then we show a GAME OVER message along with the player’s score.

One thing to note when forking the view code using ternary operators is that you need to use the html function for each new fork.

The buttons both have an inline onclick event listener attached to them that calls the same event handler, checkAnswer and accepts an argument of either true or false depending on which button was pressed. Let’s write this event handler now:

const checkAnswer = answer => event => Update(state => ({
    score: answer === state.questions[state.index].answer ? state.score + 1 : state.score,
    index: state.index + 1
}))

This event handler accepts an extra argument of answer as well as the event object that all event handlers accept, so it needs to be curried using the double arrow notation seen above. It calls the Update function which uses a ternary operator to check if the answer provided as an argument matches the answer to the current question, if it does then the score property is increased by 1, if not then the score stays the same. It also increases the value of the index property, so the next question will be displayed.

This event handler now needs adding to the State, along with the View. We can do this using the object shorthand notation, as long as checkAnswer and View are defined before State:

const State = {
  score: 0,
  index: 0,
  questions: [
    {question: "A Nanny State is a country where nannies are employed by the state", answer: false},
    {question: "Nanny State is also the name of a beer", answer: true},
    {question: "Mila Kunis and Ashton Kutcher employ 16 nannies to help raise their children", answer: false},
    {question: "The Nanny McPhee films are based on the Nurse Matilda books", answer: true},
    {question: "Nanny State uses the µhtml library for rendering", answer: true},
],
  checkAnswer,
  View
}

Now that everything is part of the State, all that is left to do is define the Update function by calling the Nanny function and providing State as an argument:

const Update = Nanny(State)

And that’s it! The quiz should start running straight away and you should be able to answer each question and the score will change based on if the answer is right or not. Another example of an interactive app that is quick to build with minimal amount of code. Have a go at answering the questions and see how you get on. You can see the finished code in the CodePen below:

See the Pen True or False Quiz – SitePoint by DAZ (@daz4126) on CodePen.

Once you’ve had some fun playing it and got used to how Nanny State works, here are a few ideas for extending the game:

  • Add more questions
  • Add a ‘Start’ and ‘Play Again’ buttons to allow players to play again
  • Select questions at random
  • Keep track of the highest score
  • Add questions with other answers, except true or false
  • Create a multiple-choice quiz

Nanny State includes a lot more other goodies such as built-in support for using local storage and routing. See the docs for more info or get in touch if you have any questions.

I’d love to hear what you think of Nanny State. Would you consider using it for a project? Is there anything more you’d like to know about it? Leave a comment in our community!