# Task 2: Temperature

These XState v4 docs are no longer maintained

XState v5 is out now! Read more about XState v5 (opens new window) and check out the XState v5 docs (opens new window).

This is the second of The 7 Tasks from 7GUIs (opens new window):

Challenges: bidirectional data flow, user-provided text input.

The task is to build a frame containing two textfields TC and TF representing the temperature in Celsius and Fahrenheit, respectively. Initially, both TC and TF are empty. When the user enters a numerical value into TC the corresponding value in TF is automatically updated and vice versa. When the user enters a non-numerical string into TC the value in TF is not updated and vice versa. The formula for converting a temperature C in Celsius into a temperature F in Fahrenheit is C = (F - 32) * (5/9) and the dual direction is F = C * (9/5) + 32.

Temperature Converter increases the complexity of Counter by having bidirectional data flow between the Celsius and Fahrenheit inputs and the need to check the user input for validity. A good solution will make the bidirectional dependency very clear with minimal boilerplate code.

Temperature Converter is inspired by the Celsius/Fahrenheit converter from the book Programming in Scala. It is such a widespread example—sometimes also in the form of a currency converter—that one could give a thousand references. The same is true for the Counter task.

# Modeling

Instead of thinking about this as bidirectional data flow, it can be simpler to think of this as a UI rendered from two values: C and F, and these two values can be updated due to events, such as CELSIUS for changing the C˚ input value and FAHRENHEIT for changing the F˚ input value. It just so happens that the <input> element both displays and updates the values, but that's just an implementation detail.

Note that when one of these events is sent to the machine, two things happen simultaneously:

  • The desired temperature value is assigned to the event value
  • The other temperature value is calculated and assigned based on that same event value

States:

  • "active" - the state where converting the temperature is enabled

Context:

  • C - the temperature in degrees Celsius
  • F - the temperature in degrees Fahrenheit

Events:

  • "CELSIUS" - signals that the Celsius value should change
  • "FAHRENHEIT" - signals that the Fahrenheit value should change

# Coding

import { createMachine, assign } from 'xstate';

export const temperatureMachine = createMachine({
  initial: 'active',
  context: { C: undefined, F: undefined },
  states: {
    active: {
      on: {
        CELSIUS: {
          actions: assign({
            C: (_, event) => event.value,
            F: (_, event) =>
              event.value.length ? +event.value * (9 / 5) + 32 : ''
          })
        },
        FAHRENHEIT: {
          actions: assign({
            C: (_, event) =>
              event.value.length ? (+event.value - 32) * (5 / 9) : '',
            F: (_, event) => event.value
          })
        }
      }
    }
  }
});

# Result