Skip to content

Consumables

Manolo Edge edited this page Oct 28, 2023 · 5 revisions

Consumables are custom objects that hold a value, can be listened for changes, and can dispatch new values. They are used across Cardboard for different things. The State is just a Consumable, for example. They can also be used to reactively manipulate tags, ie. updating text, setting styles, hiding elements. They can be "intersected", which means creating a new Consumable that updates its value based on another Consumable.

Table Of Content

Creating and Using Consumables

A Consumable is a versatile object that holds a value and allows you to change the value and listen to value changes. This section will guide you on creating Consumables and using them effectively.

Creating a Consumable

To create a Consumable, you can use the createConsumable function:

import { createConsumable } from 'cardboard-js/dist/cardboard.js';

// Create a Consumable with an initial value (e.g., 42).
const count = createConsumable(42);

Or you can instantiate the class directly:

import { Consumable } from 'cardboard-js/dist/cardboard.js';

// Create a Consumable with an initial value (e.g., 42).
const count = new Consumable(42);

Or using the state function for a shorter alternative:

import { state } from 'cardboard-js/dist/cardboard.js';

// Create a Consumable with an initial value (e.g., 42).
const count = state(42);

Accessing the Value

You can access the current value of a Consumable using the value property. This will give you the real value for any cases where you might need it:

const currentValue = count.value; // Gets the current value (42 in this case).

Updating the Value

You can update the value of a Consumable using the value property. This will automatically trigger any registered listeners:

count.value = 55; // Sets the value to 55 and notifies listeners.

Or you can call the dispatch method:

count.dispatch(55); // Sets the value to 55 and notifies listeners.

Both approaches do the same thing.

Adding Listeners

You can add listeners to a Consumable to perform actions when its value changes. Use the changed(callback) method:

count.changed((newValue) => {
  console.log(`Value changed to: ${newValue}`);
});

Example: Reacting to Value Changes

// Creating a Consumable
const temperature = createConsumable(25);

// Adding a listener to react to changes
temperature.changed((newTemperature) => {
  if (newTemperature > 30) {
    console.log("It's getting hot!");
  } else {
    console.log("The temperature is comfortable.");
  }
});

// Changing the value
temperature.value = 35; // Triggers the listener, and "It's getting hot!" will be printed.

Intersecting Consumables

You can create a new Consumable that depends on the value of another Consumable using the intersect method. The new Consumable will automatically update when the original value changes:

import { intersect, createConsumable } from 'cardboard-js/dist/cardboard.js';

// Create a Consumable
const weight = createConsumable(70);

// Create an intersected Consumable based on 'weight'
const isOverweight = intersect(weight, (value) => value > 90);

// intersect the consumable directly
const isOverweight = weight.intersect((value) => value > 90);

// Adding a listener
isOverweight.changed((overweight) => {
  if (overweight) {
    console.log("You're overweight!");
  } else {
    console.log("You're in a healthy weight range.");
  }
});

// Changing the original value
weight.value = 95; // Triggers the listener, and "You're overweight!" will be printed.

Using With Tags

That's cool and all, you can create and use Consumables for whatever you want. But the main reason for having them is to be used for building apps with cardboard. For making apps reactive to be precise.

As you might or might not already now you can manipulate tags, manually and conditionally/reactively. It's in the conditionally and reactively part where Consumables come into play.

Let me explain with a simple example:

const isDisabled = createConsumable(false);

input()
  .disableIf(isDisabled)
  .classIf(isDisabled, ['box-disabled']);

// In another part of the code:
isDisabled.value = true;

Whenever the isDisabled consumable changes, the input element will react to that change, then disable or enable based on the new value of the consumable. The same goes for setting or removing classes.

Take a look at Conditionally Manipulating Tags for a more in-depth explanation.

Built-in Intersectors

Cardboard offers a set of functions provided to create new Consumables that represent common conditions or comparisons. These functions are readily available for common use cases. Here are the intersectors we have for now:

Most built-in interceptors allow you to pass in a value or a Consumable.

const temperature = createConsumable(32);
const isTooHot = greaterThan(temperature, 30);

// Or
const hotTemp = createConsumable(30);
const isTooHot = greaterThan(temperature, hotTemp);

Greater Than

The greaterThan function allows you to create a Consumable that checks if the value is greater than a specified threshold. This is useful for scenarios like tracking temperature exceeding a certain limit:

const temperature = createConsumable(32);
const isTooHot = greaterThan(temperature, 30);

isTooHot.changed((hot) => {
  if (hot) {
    console.log("It's too hot outside!");
  } else {
    console.log("The weather is pleasant.");
  }
});

Greater Than or Equal To

The greaterThanOr function creates a Consumable that checks if the value is greater than or equal to a specified threshold. It's helpful for scenarios like determining if a score meets or exceeds a passing grade:

const examScore = createConsumable(78);
const isPassing = greaterThanOr(examScore, 70);

isPassing.changed((pass) => {
  if (pass) {
    console.log("Congratulations! You passed the exam.");
  } else {
    console.log("You need a higher score to pass.");
  }
});

Less Than

The lessThan function creates a Consumable that checks if the value is less than a specified threshold. It's handy for scenarios like age verification for age-restricted content:

const userAge = createConsumable(16);
const isAdult = lessThan(userAge, 18);

isAdult.changed((adult) => {
  if (adult) {
    console.log("You are an adult and can access this content.");
  } else {
    console.log("Sorry, this content is for adults only.");
  }
});

Less Than or Equal To

The lessThanOr function creates a Consumable that checks if the value is less than or equal to a specified threshold. It's useful for scenarios like checking if a price falls within a budget:

const productPrice = createConsumable(25);
const isAffordable = lessThanOr(productPrice, 30);

isAffordable.changed((affordable) => {
  if (affordable) {
    console.log("This product fits within your budget.");
  } else {
    console.log("You might want to consider a more affordable option.");
  }
});

Equal To

The equalTo function creates a Consumable that checks if the value is equal to a specified value. It's beneficial for scenarios like verifying if an input matches a predefined value:

const userStatus = createConsumable("Active");
const isActive = equalTo(userStatus, "Active");

isActive.changed((active) => {
  if (active) {
    console.log("Your account is active.");
  } else {
    console.log("Your account is not active.");
  }
});

Not Equal To

The notEqualTo function creates a Consumable that checks if the value is not equal to a specified value. It's useful for scenarios like checking if a selected option is not the default one:

const selectedOption = createConsumable("Select an option");
const isCustomChoice = notEqualTo(selectedOption, "Select an option");

isCustomChoice.changed((custom) => {
  if (custom) {
    console.log("You've made a custom selection.");
  } else {
    console.log("You haven't selected a custom option yet.");
  }
});

By using these built-in intersectors, you can easily create Consumables that represent common conditions and comparisons in real-world scenarios, making it simple to react to changes in values and provide feedback or perform actions accordingly.