Skip to content

States

What is a State?

In Paintor, State is an object, whose values are bound to chosen parts of the web page. This means that when you update the values of the State, the corresponding parts of the page are being automatically updated.

To create a State, use the function state(). It takes an Array or an Object and returns a Proxy of it.

Object <--> State

This makes both, the original Object and the State bound together. When the values of one change, the corresponding values of the other also change.

WARNING

state() is useless when generating HTML code, because the code is a string and is not reactive.

html
<script type="module">
  import { state } from 'paintor'
  
  /* Create a State from an Object */
  const myObject = { count: 0 }
  const myState = state(myObject)
   
  /* Increment myState.count on every second */
  setInterval(() => {
    myState.count += 1
    console.log(`myObject.count: ${myObject.count} | myState.count: ${myState.count}`)
  }, 1000)
</script>
html
<script type="module">
  import { state } from 'paintor'
  
  /* Create a State from an Object */
  const myObject = { count: 0 }
  const myState = state(myObject)

  /* Increment myObject.count on every second */
  setInterval(() => {
    myObject.count += 1
    console.log(`myObject.count: ${myObject.count} | myState.count: ${myState.count}`)
  }, 1000)
</script>

If we run either of these for 3 seconds, the output will be:

bash
myObject.count: 1 | myState.count: 1
myObject.count: 2 | myState.count: 2
myObject.count: 3 | myState.count: 3

State <--> DOM

Now that we have a State, we can use it in a Template to bind the State's values with the DOM.

But first, in order to achieve reactivity, here are few IMPORTANT things to remember:

  • Although the State and the original Object are bound together, in the Template you should work with the State. For example, myState.value will be reactive, but myObject.value will not.
  • Where you want to reactively get a value from the State, wrap it in a callback function. For example, use () => myState.value instead of myState.value.

Change the DOM on State changes

Let's have two buttons, - and +, and a <span> element between them. Clicking on either button going to change myState.value, which is bound with the text content of the <span> element.

js
import { component, state } from 'paintor'

component((x) => {
  const myState = state({ value: 0 })

  const increment = () => myState.value += 1
  const decrement = () => myState.value -= 1

  x.div(
    x.button({ onClick: decrement }, '-'),
    x.span(() => myState.value), // The callback here is needed for reactivity
    x.button({ onClick: increment }, '+')
  )
}).paint('#states-1')
css
#states-1 {
  & span {
    padding: 0 0.5rem;
  }
}
html
<div id="states-1"></div>

Click on the - and + buttons below to try it out:

example

Change the State on DOM changes

Let's have myState.text and an input field from which myState.text can be changed.

js
import { component, state } from 'paintor'

component((x) => {
  const myState = state({ text: '' })

  const setText = (event) => {
    myState.text = event.target.value
  }

  x.div(
    x.input({
      type: 'text',
      placeholder: 'Type something here',
      onKeyUp: setText
    }),
    x.span(() => myState.text), // The callback here is needed for reactivity
  )
}).paint('#states-2')
html
<div id="states-2"></div>

Type something in the input field below. You should see the same text that you are typing on the right side of the input field, where the <span> is.

example

Scope

Local State

Each template can have its own local (internal) state:

js
import { component, state, template } from 'paintor'

const plusMinus = template((x) => {
  const localState = state({ value: 0 })

  x.div(
    x.button({ onClick: () => localState.value -= 1 }, '-'),
    x.span(() => localState.value),
    x.button({ onClick: () => localState.value += 1 }, '+')
  )
})

component((x) => {
  x.div('Template 1', plusMinus)
  x.div('Template 2', plusMinus)
}).paint('#states-scope-local')
css
#states-scope-local {
  & span {
    padding: 0 0.5rem;
  }
}
html
<div id="states-scope-local"></div>
example

Global State

Or, the template can use states from upper scope. In this case, multiple templates can use the same state.

js
import { component, state, template } from 'paintor'

const globalState = state({ value: 0 })

const plusMinus = template((x) => {
  x.div(
    x.button({ onClick: () => globalState.value -= 1 }, '-'),
    x.span(() => globalState.value),
    x.button({ onClick: () => globalState.value += 1 }, '+')
  )
})

component((x) => {
  x.div('Template 1', plusMinus)
  x.div('Template 2', plusMinus)
}).paint('#states-scope-global')
css
#states-scope-global {
  & span {
    padding: 0 0.5rem;
  }
}
html
<div id="states-scope-global"></div>
example

One State in Many Templates

One State can serve multiple Templates at the same time.

js
import { component, state, template } from 'paintor'

const globalState = state({ tick: 0 })

setInterval(() => {
  globalState.tick += 1
}, 1000)

const tick = () => globalState.tick

const button = template((x) => {
  x.button({ textContent: tick })
})

const paragraph = template((x) => {
  x.p(tick)
})

const textarea = template((x) => {
  x.textarea({ value: tick })
})

component(
  button,
  paragraph,
  textarea
).paint('#states-one-in-many')
html
<div id="states-one-in-many"></div>
example

Many States in One Template

js
import { component, state, template } from 'paintor'

const stateOne = state({ number: 0 })
const stateTwo = state({ number: 0 })

setInterval(() => {
  stateOne.number += 1
  stateTwo.number -= 1
}, 1000)

const myTemplate = template((x) => {
  x.p(() => stateOne.number )
  x.p(() => stateTwo.number )
})

component(myTemplate).paint('#states-many-in-one')
html
<div id="states-many-in-one"></div>
example

State from Array

js
import { component, state } from 'paintor'

const globalState = state([ 0, 0 ])

setInterval(() => {
  globalState[0] += 1
  globalState[1] -= 1
}, 2000)

component((x) => {
  x.p(() => globalState[0] )
  x.p(() => globalState[1] )
}).paint('#states-array')
html
<div id="states-array"></div>
example