State

Components can be stateless or stateful. There are advantages to both. Chose the right one depending on use case. For example, if a component will only display the result of some other action, make it stateless and pass it the data it needs. If the data of a component can change at unpredictable times, make it stateful. That way, when its data changes, it will update automatically. If you have a fixed dataset and the component will never need to be rendered again during the session, make it stateless and pass it the data.

When you give a component state, Composi assigns the state getters and setters. Setting state causes the component to render a new virtual DOM, diff it with the old virtual DOM, and if there are differences, update the actual DOM. As such, the assignment of data to a component's state will trigger an update. Setting state directly is ideal for primitive data types, such as string, numbers:

See the Pen Composi state-1 by Robert Biggs (@rbiggs) on CodePen.

Or we can add state in the component's constructor when extending the Component class:

See the Pen Composi state-2 by Robert Biggs (@rbiggs) on CodePen.

Or you could add state when you initialize a new component:

See the Pen Composi state-3 by Robert Biggs (@rbiggs) on CodePen.

How State Gets Passed

When you give a component state, at render time the value of state gets passed to the render function as its data. No need to directly reference state inside the render function. So, in the above example, the value of the render parameter message will be the value of the component's state property.

In the case were no state is provided, the render function's parameter will reference any data pass to the component's update function.

Strings and Numbers as State

In our previous example we used as string as the component's state. You could also use a number. In that case you could perform Math operations with the value of the state and then reassign it back to the state to update the component.

Booleans

By default boolean values are not output. This is so they can be used for conditional checks. However, if you want to output a boolean value, just convert it to a string. There are two ways to do this. For true or false you can add toString() to convert them to strings. The values null and undefined do not have a toString() function, but you can use string concatenation to convert them, or use String(value). Below are examples of these approaches. Note, we are just showing the render function, not the complete component code:

// For true or false:
render(value) {
  return <p>The boolean value is: {value.toString()}</p>
}

// The above approach would throw an error if the boolean was undefined or null.
// For them, do the following:
render(value) {
  return <p>The boolean value is: {value + ''}</p>
}

// Make boolean uppercase:
render(value) {
  return <p>The boolean value is: {(String(value).toUpperCase()}</p>
}

setState

Most components will have state of complex data types: objects or arrays. To update the state of complex types you have two choices: use the setState method or get the state, perform your operations and set the state with the final result. We'll look at setState first.

Setting State for Primitive Types

When the type is primitive, a string, number or boolean, you can set the state directly, or use the setState method:

helloWorld.state = 'everybody'

// or:
helloWorld.setState('everybody')

Please note that although you can set state by assigning the data to the component's state property, it's always preferable and safer to use setState to do so.

Setting State for Complex Types - setState

The purpose of setState is to enable making a patch to state that is an object or array. Because state is controlled by its getters and setters, you cannot directly manipulate the data it contains. With component state you can either get it, or set it.

First lets look at a component that prints out a person object:

See the Pen Composi state-4 by Robert Biggs (@rbiggs) on CodePen.

Now imagine you want to update the the person's job. You might think you could access the job directingly on the state object, but you can't. That would be an assignment, and because state has a setter, you can only assign to the state itself:

// This assignment will not work:
personComponent.state.job = 'Web Developer'

Instead of trying to assign a property of the state object, we need to use the setState method. To update a state object property, we pass in an object with that property and the new value. Behind the scenes, setState will mixin the new object with the state object. Then it will create a new virtual DOM and patch the actual DOM to reflect those changes:

// Update the job property of the component's state:
personComponent.setState({job: 'Web Developer'})

Updating Array State

When a component has an array as state, you need to use a callback with setState. For instance, suppose we have a component fruitList that prints out a list of fruits. We notice that the third item in the list is mispelled and want to update it. We can do that as follows:

fruitList.state = ['Apples', 'Oranges', 'Pinpalpes', 'Bananas']

// Use second argument for index in the array you want to update:
fruitList.setState(prevState => {
  // Use array splice to replace mispelling:
  prevState.splice(2, 1, 'Pineapples')
  // Don't forget to return the new state:
  return prevState
}

Updating state for Array of Objects

Suppose a component's state is an array of objects:

const people = [
  {
    firstName: 'Joe',
    lastName: 'Bodoni',
    job: 'Mechanic'
  },
  {
    firstName: 'Ellen',
    lastName: 'Vanderbilt',
    job: 'Lab Technician'
  },
  {
    firstName: 'Sam',
    lastName: 'Anderson',
    job: 'Web Developer'
  }
]

This array is used as the state for a component of users. We want to update Joe's job to Rock Star. To update the job, use a callback, make the transform on the state and return it:

// Update the fist user's job to 'Rock Start':
userList.setState(prevState => {
  prevState[0].job = 'Rock Star'
  // Return prevState to udpate component:
  return prevState
})

Before version 2.4.0 you could update arrays by passing a second argument for the position in the array. That has since been removed in favor of setState with a callback to be inline with how React, Preact and Inferno work.

Complex State Operations

As we saw in our last example of arrays, sometimes you will need to get the state, operate on it separately and then set the component's state to that. For example, if you need to use map, filter, sort or reverse on an array, you'll want to get the complete state and perform these operations. Aftwards you can just set the state:

// Get the component's state:
const newState = fruitsList.state

// Reverse the array:
newState.reverse()

// Set the component's state with the new state:
fruitsList.setState(newState)

setState with a Callback

One option for handling the need for complex operations when setting state is to pass a callback to the setState method. When you do so, the first argument of the callback will be the component's state. In the example below, we get the state and manipulate it in the handleClick method. After doing what you need to with state, remember to return it. Otherwise the component's state will not get updated.

Notice how we use a callback in the setState method inside the handleClick method to test what the current value of state is:

Codepen Example:

See the Pen Composi state-5 by Robert Biggs (@rbiggs) on CodePen.

Be aware that whatever the callback returns will be set as the component's new state. Therefore you will need to make all changes to the component's whole state before returning it.

componentShouldUpdate

Sometimes you need to do some complex operations on data and you don't want the component to update constantly due to changes. Or you want to render a component with some external DOM plugin, possibly jQuery. For these situations you can use the comonentShouldUpdate property inside the class constructor. By default it is set to true. Setting it to false causes a component to render only once. Even though the update function is invoked on it, or its state changes, it will not update.

You can make the component react to updates again by setting this property back to true on the component instance. However, if you changed state before setting comonentShouldUpdate back to true and want the component to then update, you will need to do so by invoking the update() function on the component instance. This will use the current state.

class Hello extends Component {
  constructor(props) {
    super(props)
    this.container = 'header',
    this.state = 'World'
    this.componentShouldUpdate = false
  }
  render(data) {
    return (
      <h1>Hello, {data ? `: ${data}`: ''}!</h1>
    )
  }
}
// Create instance of Hello:
const hello = new Hello()

// Some time later update the component's state:
hello.setState('Joe')
// Because componentShouldUpdate is false, the component will not update.

// Some time later set componentShouldUpdate to true:
hello.componentShouldUpdate = true
hello.setState('Joe')
// Now the component updates.