Coder Social home page Coder Social logo

nanny-state's Introduction

NANNY STATE

simple & minimal state management

npm License Blazingly Fast

NANNY STATE makes it simple to build blazingly fast state-based web apps.

  • SIMPLE - Build interactive user-interfaces with just a few lines of vanilla JS.
  • MINIMAL - Only 3kb when minified and zipped with no transpiling.

NANNY STATE stores all the data in a single state and automatically renders the view when anything changes. This helps to make your code more organized and easier to maintain without the bloat of other front-end libraries.

Whether you're building a small interactive web page or large complex app, NANNY STATE is a simple and minimal alternative to React and Vue!

It's easy to get started - just follow the examples below and you'll see some impressive results in just a few lines of code.

What Is NANNY STATE?

NANNY STATE is comprised of 3 interdependent parts:

  • State - usually an object that stores all the app data
  • View - a function that returns a string of HTML based on the current state
  • Transformers - pure functions that update the state

The state is the single source of truth in the application and it can only be updated by NANNY STATE, ensuring that any changes are deterministic with predictable outcomes.

Data Flow

NANNY STATE uses a one-way data flow model:

Nanny State data flow diagram

When a user interacts with the page, it triggers an event that uses a transformer function to update the state. The page is then automatically re-rendered to reflect these changes.

Background

NANNY STATE was inspired by Hyperapp and Redux and uses the µhtml library for rendering. It is open source software; please feel free to help out or contribute.

The name is inspired by the British phrase for an overly protective, centralised government. In a similar way, NANNY STATE is overly protective of all the app data that it stores centrally. I'm also a big fan of the non-alcoholic beer with the same name.

Installation

Using NPM CLI

Install nanny-state from NPM.

npm install nanny-state

Then import like this:

import { Nanny, html } from "nanny-state";

ES Modules

If you use ES Modules, you don't need NPM. You can import from a CDN URL in your browser or on CodePen.

<script type="module">
  import { Nanny,html } from 'https://cdn.skypack.dev/nanny-state';
</script>

Quick Start Guide

Building a NANNY STATE app is simple and straightforward. It always follows these steps:

  1. Import the Nanny and html functions:
    import { Nanny, html } from 'nanny-state'
  2. Decide on the structure of your data and create a state object:
    const State = { 
       name: "World",
       view
      }
    ```
  3. Decide how you want the data to be displayed and create a view template:
    const view = state =>
       html`<h1>Hello ${name}
            <button onclick=${hello}>Click Me</button>`
  4. Create transformer functions to describe how the state changes when the user interacts with the page:
const hello = state => { name: "Nanny State" }
  1. Assign the Update function to the return value of Nanny(State):
const Update = Nanny(State)

The basic structure of any NANNY STATE app is:

import { Nanny, html } from 'nanny-state'

const view = state => html`some view code here`
const State = { 
  prop: value, 
  view
}
const transformer = state => ({ newState })
const handler = event => Update(transformer)

const Update = Nanny(State)

Usage examples

The easiest way to learn how NANNY STATE works is to try coding some examples. All the examples below can be coded on CodePen by simply entering the code in the 'JS' section.

Alternatively you could set up a basic HTML file and place all the code inside the <script> tags. You can see example files here.

And if you want it to look pretty, just copy the CSS code from the examples on CodePen!

Hello World Example

Hello World screenshot

This is a simple example to show how Nanny State renders the view based on the state.

You can see finished app and code on CodePen.

Start by importing the NANNY STATE functions:

import { Nanny, html } from 'nanny-state'

Next, create an object to represent the initial state (the state is usally an object, but can be any data-type). The state stores every bit of information about our app as data. In this simple example, we just want to store the value of a property called 'name':

const State = { name: "World" }

Our next job is to create the view - this is a function that accepts the state as an argument and returns a string of HTML that depends on the value of the state's properties. In NANNY STATE, everything is stored as a property of the state, even the view!

It is stored as a property called 'view', which we can create like so:

State.view = state => html`<h1>Hello ${state.name}</h1>`

Views in NANNY STATE use the html template function that is part of µhtml. This is a tag function that accepts a template literal as an argument. The template literal contains the HTML code for the view and uses ${expression} placeholders to insert values from the state.

These values are then bound to the view which ensures the view will automatically update to reflect any changes in the state. In this example we are inserting the value of the state object's 'name' property into the <h1> element.

Last of all, we need to call the Nanny function with State provided as an argument:

Nanny(State)

This passes the State object into the Nanny function, which renders the view based on the initial state.

Hello Batman Example

Hello Batman screenshot

This example shows how the state object can be updated using the Update function.

You can see the finished app and code on CodePen.

It starts in the same way as the last example, by importing the NANNY STATE functions:

import { Nanny, html } from 'nanny-state'

Next we'll create the view template and assign it to the variable view:

const view = state => 
  html`<h1>Hello ${state.name}</h1>
       <button onclick=${beBatman}>I'm Batman</button>`

This view is similar to the one we used in the Hello World example, but it also contains a button with an event listener. We'll get to this soon, but first we need to create the initial state:

const State = { 
  name: 'Bruce Wayne', 
  view 
}

Notice that as well as assigning the 'name' property the value of 'Bruce Wayne', we also add the view variable as a property of the State object using the shorthand object assignment.

Now let's take a look at the inline event listener attached to the button, using onclick. When the button is clicked the event handler 'beBatman' will be called. We want this function to update the state object so the 'name' property changes to 'Batman'.

The only way we can update the state is to use the Update function that is returned by the Nanny function.

Calling the Nanny function does 2 things:

  1. It renders the initial view based on the initial state provided as an argument (as we saw in the Hello World example).
  2. It also returns an Update function that is the only way to update the state.

To be able to use the Update function, we need to assign it to a variable when we call the Nanny function. We usually call it Update but it can be called anything you like:

const Update = Nanny(State)

The Update function can now be used to update the state. After any change to the state, NANNY STATE will automatically re-render the view using µhtml, which only updates the parts of the view that have actually changed. This means that re-rendering after a state update is efficient and therefore blazingly quick.

To see this in action, let's write the beBatman event handler function to update the state and change the state object's 'name' property to 'Batman' when the button is clicked (note that this function needs to go before the view function in your code):

const beBatman = event => Update(nameToBatman)

Because this is an event handler, the only parameter is the event object (although it isn't actually needed in this example). The purpose of this event handler is to call the Update function. The argument to this function, nameToBatman, is a reference to a transformer function that tells NANNY STATE how to update the value of the state.

We still need to write this function, but transformer functions are a really important part of NANNY STATE, so let's take a look at how they work before we write it.

Transformer Functions

A transformer function accepts the current state as an argument and returns a new representation of the state. It basically maps the current state to a new state:

Transformer function diagram

ES6 arrow functions are perfect for transformer functions as they visually show the mapping of the current state to a new state.

Transformer functions must be pure functions. They should always return the same state given the same arguments and should not cause any side-effects. They take the following structure:

state => params => newState

If the transformer doesn't have any other parameters, apart from the current state, then you can omit them and just write the transformer in the form:

state => newState

In our Batman example we want the transformer function to update the 'name' property to 'Batman'. This means that it needs to return a new object with a 'name' property of 'Batman'. Add the following code underneath the event handler:

const nameToBatman = state => ({ name: 'Batman'})

Note that when arrow functions return an object literal, it needs wrapping in parentheses

Transformer functions are passed by reference to the Update function, which will then implicityly pass the current state as an argument.

We now have everything wired up correctly. When the user clicks the button, the beBatman event handler is called. This passes the nameToBatman transformer function to the Update function which updates the state by changing the 'name' property to 'Batman'. It then re-renders the page using the new state.

Try clicking the button to see the view change based on user input!

Counter Example

The next example will be a simple counter app that lets the user increase or decrease the count by pressing buttons. The state will change with every click of a button, so this example will show how easy NANNY STATE makes dynamic updates.

Counter Example screenshot

You can see the finished app and code on CodePen

The value of the count will be stored in the state as a number (the state is usually an object, but it doesn't have to be). Let's initialize it with a value of 10:

import { Nanny, html } from 'nanny-state';

const State = 10;

Now let's create the view that will return the HTML we want to display:

const view = number => html`<h1>Nanny State</h1>
                            <h2>Counter Example</h2>
                            ${Counter(number)}`

This view contains a component called Counter. A component is a function that returns some view code that can be reused throughout the app. They are easy to insert into the main view using the ${ComponentName} placeholder notation inside the template literal.

Now we need to write the code for the Counter component (note it is convention to use PascalCase when naming components):

const Counter = number => html`<div id='counter'>${number}</div>
                               <button onclick=${down}>-</button>
                               <button onclick=${up}>+</button>`

The two buttons call the event handlers, down and up, which deal with changing the value of the counter when they are pressed. We're going to need to transformer function to deal with incrementing this value, so let's write it:

const increment = number => (i=1) => number + i

This function accepts the current state (the number to be incremented) as well as an extra parameter that is the amount it is to be incremented by, which has a default value of 1. We could make this value negative to make the count go down. Now we can write the event handlers that use this transformer function to update the state:

const up = event => Update(increment)
const down = event => Update(increment, -1)

Both these event handlers pass the increment transformer function to the Update function. The first argument of the Update function is always a reference to the transformer function that will be used to update the state. The current state is always passes to this as an argument automatically. If a transformer function requires any more arguments as well as current state, they need to be provided as extra arguments to the update function, as can be seen above with the extra argument of -1.

The up handler uses the increment transformer function with the default parameter of 1, so no extra arguments need providing. The down handler provides an extra argument of -1 that is passed to the increment transformer function so that the value of the state will decrease by 1.

Note: The first parameter of every transformer functions is always the state. This will be implicitly provided as an argument by the Update function, so does not need to be included when calling Update. Any additional arguments are added after the name of the function.

Last of all, we just need to call the Nanny function and assign its return value to the variable Update. In this example, the state is a number, so we cannot assign any properties to it. This means we can't make the view a property of State. Fortunately, the Nanny function accepts a second options parameter. This is an object that has a property called 'view' that can be assigned to the variable view using the object property shorthand notation:

const Update = Nanny(State,{ view })

This will render the initial view with the count set to 10 and allow you to increase or decrease the count by clicking on the buttons.

More Examples

You can see a full set of examples of how Nanny State can be used, with source code, on CodePen. This includes:

Extra Info

Now that you've learnt the basics of NANNY STATE, here's some extra info that helps give you some extra control over the settings.

Before and After Functions

before and after are properties of the state object and are functions that are called before or after a state update respectively. They can also be passed to the Nanny function as part of the options object.

For example, try updating the last line of the 'Hello Batman' example to the following code instead:

State.before = state => console.log('Before:', state)
State.after = state => console.log('After:', state)

const Update = Nanny(State)

Now, when you press the I'm Batman button, the following is logged to the console, showing how the state has changed:

"Before:"
{
  "name": "Bruce Wayne"
}
"After:"
{
  "name": "Batman"
}

The after function is useful if you want to use the localStorage API to save the state between sessions. The following after function will do this:

State.after = state => localStorage.setItem('NannyState',JSON.stringify(state))

You will also have to set state to be the value stored in localStorage or the initial state if there is not value in local storage:

const initialState = { name: 'World' }
const State = localStorage.getItem('NannyState') ? JSON.parse(localStorage.getItem('NannyState')) : initialState

Default Element

By Default the view will be rendered inside the body element of the page. This can be changed using the element property of the state object or by providing it as part of the options object of the Nanny function. For example, if you wanted the view to be rendered inside an element with the id of 'app', you just need to specify this as an option when you call the Nanny function:

State.element = document.getElementById('app')

Debug Mode & Log State

debug is a property of the state that is false by default, but if you set it to true, then the value of the state will be logged to the console after the initial render and after any state update"

State.debug = true

logState is a property of the state that does exactly the same thing as the debug property when set to true:

State.logState = true

Transformer Function and Fragments of State

Transformer functions don't need to return an object that represents the full state. You only need to return an object that contains the properties that have changed. For example, if the initial state is represented by the following object:

const State = {
  name: "World",
  count: 10
}

If we write a transformer function that doubles the count, then we only need to return an object that shows the new value of the 'count' property and don't need to worry about the 'name' property:

const double = state => ({ count: state.count * 2})

We can also destructure the state object in the parameter so that it only references properties required by the transformer function:

const double = { count } => ({ count: count * 2})

Docs

You can see more in-depth docs here

License

Released under Unlicense by @daz4126.

nanny-state's People

Contributors

daz4126 avatar michaelcurrin avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.