Nanny State is a small library I wrote to make it easier to build state-based web applications using Vanilla JS. It’s similar to React, but with much less overhead and a new syntax to learn. It also uses a single application-wide state object instead of each individual component having its own state. It was inspired by HyperApp and has many similarities to Elm.
In this article, I’ll explain how Nanny State works, then demonstrate what it can do with a few examples.
Nanny State uses a one-way data flow model, consisting of 3 parts:
- State – an object that stores all the application data
- See – a function that returns an HTML string based on the current state
- Update – a function which is the only way to change the state and restore the view
In Nanny State, the state is everything. The state object is the only source of truth for your application – every bit of application data is a property of that object. Even the event handlers used in the view are methods of the state object.
The view is a representation of the report in HTML format. It changes whenever the state changes and allows users to interact with the application.
The update function is the only way to change the state. It is a single entry point for updating status and ensures changes are deterministic, consistent, and predictable.
These 3 things are all you need to create an app in Nanny State. In fact, it can be summed up by asking the following 3 questions:
- What data should I store in my app? This will constitute the properties of the
State
object - How do I want app data presented on the page? This will help you create the
View
function - How does 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 try to make something a little 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>`
The Nanny state uses µhtml to display HTML. The View
function still accepts state objects as its only setting. He then uses the html
function provided by µhtml to create HTML code based on the literal template provided as an argument.
Using a literal pattern means that we can use the ${variable}
notation to insert the state properties into the view. In this example, we use it to insert the value of name
property inside the element.
const State = {
name: "Nanny State",
View
}
The State
the object is where everything application data is stored. It includes all the properties and values that will be displayed in the View
and may change over the lifecycle of the application, such as the name
property in this example.
Notice that View
is also a property of State
using shorthand object notation. Remember the state is everything – each part of the application is the property of the State.
const Update = Nanny(State)
The last line defines the Update
function as the return value of the Nanny
function. This can now be used to update the value of all properties in the State
. In fact it is the only way that all the properties of the State
can be updated. It also performs the initial rendering of the View
based on the values provided in the State
. This means that a header will be displayed saying “Hello Nanny State” as seen in the CodePen below:
Hello Nanny State
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 to whom they want to say hello. Update the code to look like this:
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 added a element to the
View
. Event listeners are defined inline in the view, so in this example we have a oninput
event listener attached to element. This will call the
changeName
event handler, which is a method of the state object, whenever an input is detected. This event listener needs to be set, so let’s take a closer look:
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 called we want to update the State
object, so we use the Update
function, because it is the only way to update the State
.
The argument we provide to Update
function is an object that contains all the properties we want to update in 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 in 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 of the input field and instantly render the page. Using µhtml for rendering means that only parts of the View
that have actually changed are updated. This means that the new rendering after aState
Updating is both efficient and incredibly fast.
And that’s it – your first Nanny State app! Try typing and you’ll see how quickly it reacts to user input…and all with just a few lines of code. You can see the code in the CodePen below:
Dynamic input
Nanny State makes it easy to write responsive state-based applications. As you can see, there is not a lot of code required to create a dynamic state-based application that reacts to user interaction. That’s the beauty of Nanny State.
True or false quiz
Now that we’ve seen a basic example, let’s try to make something a little more complex. We will use Nanny State to create a True or False quiz. Open a new pen on CodePen and follow.
We will start in the same way, by importing the Nanny State library:
import { Nanny,html } from 'https://cdn.skypack.dev/nanny-state'
Next we will create the State
object and populate it with the initial property values 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 answered correctly and starts at 0index
– this keeps track of which question the player is at and matches the last property which is thequestions
deploy.questions
– This is an array of objects withquestion
andanswer
Properties. Thequestion
property is a string and theanswer
property is boolean
Now that we’ve created the data, let’s create the View
to view this 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.questionNumber].question}p>
<button onclick=${state.checkAnswer(true)}>TRUEbutton>
<button onclick=${state.checkAnswer(false)}>FALSEbutton>`
:
html`<h2>Game Over, you scored ${state.score}h2>`
}`
It’s a bit more complicated View
we saw in the previous example, but most should be pretty self-explanatory. After the title header, we display the score using the score
property of the State
object. Next, we use a ternary operator to fork the view. Since the view is written using template literals, you cannot use if-else
declarations to the fork code, so use ternary declarations.
This ternary statement checks whether questionNumber
is less than the length of the questions
array, which basically checks if there are still questions to be answered. If there are, the question is displayed with two buttons, one for TRUE and one for FALSE. If there are no more questions, we display a GAME OVER message with the player’s score.
One thing to note when deriving view code using ternary operators is that you must use the html
function for each new fork.
The buttons both have a line onclick
an event listener attached to them that calls the same event handler, checkAnswer
and accepts an argument either true
Where false
depending on which button you pressed. Now let’s write this event handler:
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 additional argument of answer
as good as event
object that all event handlers accept, so it must be curried using the double-arrow notation seen above. He calls the Update
function that uses a ternary operator to check if the answer provided as an argument matches the answer to the current question, if so, then the score
the property is increased by 1, otherwise the score remains the same. It also increases the value of the index
property, so the following question will appear.
This event handler should now be added to the State
with the View
. We can do this using shorthand object 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 it’s all part of the State
all that remains is to define Update
function by calling the Nanny
function and provide State
as an argument:
const Update = Nanny(State)
And that’s all! The quiz should start immediately and you should be able to answer each question and the score will change depending on whether the answer is correct or not. Another example of an interactive app that’s quick to build with a minimal amount of code. Try answering the questions and see how you do. You can see the finished code in the CodePen below:
True or false quiz
Once you’ve had fun playing and got used to how Nanny State works, here are some ideas for expanding the game:
- Add more questions
- Add “Start” and “Replay” buttons to allow players to replay
- Randomly select questions
- Keep track of the highest score
- Add questions with other answers, except true or false
- Create a multiple choice quiz
Nanny State includes many more other benefits such as built-in support for using local storage and routing. See the documentation for more information or contact us if you have any questions.
I would like to know what you think of Nanny State. Would you consider using it for a project? Is there anything else you would like to know about this? Leave a comment in our community!