Skip to content

Using forEach()

INFO

forEach() is a method, used in the Template Tree.

forEach() accepts a State as an input, and it calls the provided callback function once for each element in the state. You can render the same pattern of DOM elements for each element in the state. The callback function provides two arguments - value and key.

js
x.forEach(myState, (value, key) => {
  // ...
})

You can also use one extra callback function to handle the case when the state is empty.

The reactivity happens when you add, update or remove elements from the state, which in turn will add or remove elements from the DOM.

Add Elements

In the example below there are 2 initial elements in the array state. Click on the Add button to add more elements and see what happens.

js
import { component, state } from 'paintor'

component((x) => {
  const localState = state([1, 2])

  x.button({
      onClick: () => localState.push(localState.length + 1)
    },
    'Add'
  )

  x.ul(
    x.forEach(localState, (value, key) => {
      x.li(
        x.label('key ' + key + ' | value '),
        x.input({ type: 'number', value: value })
      )
    })
  )

}).paint('#using-foreach-add')
css
#using-foreach-add {
  input[type=number] {
    width: 2.5rem;
  }
}
html
<div id="using-foreach-add"></div>
example

Note that the existing DOM elements are not re-rendered. You can test this if you change the values in the inputs and then add new elements - the inputs will stay the same.

The behavior is the same when the state is an Object.

Update Elements

Now let's try to update a value in the array state and see how that reflects on the DOM. In the example below we have an array state with 3 elements. Click on the Update Middle button to increment the middle value.

js
import { component, state } from 'paintor'

component((x) => {
  const localState = state([1, 2, 3])

  x.button({
      onClick: () => localState[1] += 1
    },
    'Update Middle'
  )

  x.ul(
    x.forEach(localState, (value, key) => {
      x.li(
        x.label('key ' + key + ' | value '),
        x.input({ type: 'number', value: value })
      )
    })
  )

}).paint('#using-foreach-update')
css
#using-foreach-update {
  input[type=number] {
    width: 2.5rem;
  }
}
html
<div id="using-foreach-update"></div>
example

Only the middle <li> is being re-rendered.

The behavior is the same when the state is an Object.

Remove Elements

js
import { component, state } from 'paintor'

component((x) => {
  const localState = state([1, 2, 3])

  x.button({
      onClick: () => localState.push(localState.length + 1)
    },
    'Add'
  )

  x.button({
      onClick: () => localState.shift()
    },
    'Remove (shift))'
  )

  x.button({
      onClick: () => localState.pop()
    },
    'Remove (pop)'
  )

  x.ul(
    x.forEach(localState, (value, key) => {
      x.li(
        x.label('key ' + key + ' | value '),
        x.input({ type: 'number', value: value })
      )
    })
  )

}).paint('#using-foreach-remove')
css
#using-foreach-remove {
  input[type=number] {
    width: 2.5rem;
  }
}
html
<div id="using-foreach-remove"></div>
example

The behavior is the same when the state is an Object.

Automatic Order of the Elements

Object with numeric keys

In JavaScript, if you dynamically add numeric keys to an Object, they are automatically sorted. The Object behaves like an Array in this case:

js
const myObject = { 1: 'One', 3: 'Three'}
myObject[2] = 'Two'

// myObject is now sorted by its keys: { 1: 'One', 2: 'Two', 3: 'Three' }

This automatic sorting behavior is reflected in Paintor. Below we have an Object state with numeric keys and 5 values, which are initially rendered into 5 different buttons. Click on any button, and this will cause its origin value in the state to be deleted, which in turn will delete the <button> itself. But after 1 second, in the setTimeout() callback the deleted value will be re-created, which in turn will create a new <button>. This new <button> will take the same place where the previous <button> was.

js
import { component, state } from 'paintor'

component((x) => {
  const localState = state({
    1: '1',
    2: '2',
    3: '3',
    4: '4',
    5: '5'
  })

  x.forEach(localState, (value, key) => {
    x.button({
      textContent: value,
      onClick: () => {
        delete localState[key]

        setTimeout(() => {
          localState[key] = key.toString()
        }, 1000)
      }
    })
  })
}).paint('#using-foreach-automatic-order-object')
html
<div id="using-foreach-automatic-order-object"></div>
example

Array

In JavaScript, this is what happens when we use the delete keyword to delete element from an Array:

js
const myArray = [ '0', '1', '2' ]
delete myArray[1]

// myArray is now: [ '0', empty, '2' ]

The behavior in Paintor is similar. Just like the example with the Object, when you click on any button, it will be re-created at the same place after 1 second:

js
import { component, state } from 'paintor'

component((x) => {
  const localState = state([ '0', '1', '2', '3', '4' ])

  x.forEach(localState, (value, key) => {
    x.button({
      textContent: value,
      onClick: () => {
        delete localState[key]

        setTimeout(() => {
          localState[key] = key.toString()
        }, 1000)
      }
    })
  })
}).paint('#using-foreach-automatic-order-array')
html
<div id="using-foreach-automatic-order-array"></div>
example

Applying Array Mutating Methods Over Array State

In JavaScript there are multiple methods to manipulate an Array. Some of them are mutating the input Array. These methods also work on an array state:

  • fill()
  • copyWithin()
  • push()
  • reverse()
  • shift()
  • pop()
  • sort()
  • splice()
  • unshift()

INFO

When using sort(), all DOM elements will be repainted.

Render Elements on Empty State

js
import { component, state } from 'paintor'

component((x) => {
  const localState = state([])

  function addItem() {
    localState.push('item ' + (localState.length + 1))
  }

  function deleteItem() {
    localState.pop()
  }

  x.button({ onClick: addItem }, 'Add item')
  x.button({ onClick: deleteItem }, 'Remove item')

  x.ul(
    x.forEach(
      localState,
      (value) => {
        x.li(value)
      },
      () => {
        x.div('No items')
      }
    )
  )
}).paint('#using-foreach-on-empty-state')
html
<div id="using-foreach-on-empty-state"></div>
example