DEV Community

Jacob Evans
Jacob Evans

Posted on • Updated on

XState Series: Temperature Converter

TL;DR
If you just want the code/project to run locally -- https://github.com/JacobMGEvans/finite-state-machine/tree/master/fsm-projects/temperature

Since I actually ran into my first couple of roadblocks see any GIF of someone hitting their computer for reference Now I put myself in the situation because I wanted to hurt a little after the quick win with the XState Counter project. I was thinking to myself, "Hell yeah, I got this it's all gonna click just read the docs" Well I also knew to humble myself and not to copy-paste tutorial code, thinking I am learning a whole lot(learned that the hard way at my Coding Bootcamp.

Ok, so I still copy-pasted the Machine from the Temperature Converter 7GUI list. However, I specifically avoided other code implementation... What did I come up with it looked a little something like

import React from "react";
import { useMachine } from "@xstate/react";
import { temperatureMachine } from "../../machines/temperature-machine";

const sStyle = { fontSize: 20 };
export default function Temperature() {
  const [current, send] = useMachine(temperatureMachine);

  return (
    <>
      <input
        type="number"
        style={sStyle}
        onChange={e => send(`FAHRENHEIT`, { value: e.target.value })}
      />
      <span>{current.context.F}˚F</span>
      <br />
      <input
        type="number"
        style={sStyle}
        onChange={e => send(`CELSIUS`, { value: e.target.value })}
      />
      <span>{current.context.C}˚C</span>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now some of you might be noticing something missing right away and mind you that is very astute. It took me hours to figure out what I was doing wrong. Not sure why but my brain had completely disassociated React's local state implementation from XState implementation in the component.
Once I remedied that I ended up with this instead:
return (<br>
    <><br>
      <input<br>
        type="number"<br>
        value={state.context.F}<br>
        style={sStyle}<br>
        onChange={e => send( raw `FAHRENHEIT` endraw , { value: e.target.value })}<br>
      /><br>
      <span>˚F</span><br>
      <br /><br>
      <input<br>
        type="number"<br>
        value={state.context.C}<br>
        style={sStyle}<br>
        onChange={e => send( raw `CELSIUS` endraw , { value: e.target.value })}<br>
      /><br>
      <span>˚C</span><br>
    </><br>
  );

Keep in mind I changed the "current" to "state" that was for my own mental context not because it somehow affected the implementation, but helped me to better understand the implementation. It could be "UnicornRainbow" for all the component cares.

Alright, so we are done with this Temp Project right!? No.
🚧🚦Stop if purely interested in XState implementation🚧🚦
While I was testing with browser console open I noticed something, React was complaining, "What's wrong React, did I do something" React laughs in error message "Uncontrolled input is switching to controlled" I know again some of you are like "I KNOW!"...
I wish you were sitting next to me when it happened lol 😅😆

I investigate... I think it potentially XState w/ React related and reach out to David, who confirms its a React thing and dig deeper. So I dig real deep and after some time in existential crisis I figured it out.
It actually has to do with that original copy-paste of the Machine 😅

import { Machine, assign } from "xstate";

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

So some of you might see it now, which took me quite some time to notice. Well, "value" in React expects a type and apparently uses that type has been passed for the input to be controlled.
so this:

context: { C: undefined, F: undefined },
Enter fullscreen mode Exit fullscreen mode

was making that input think it was uncontrolled, so maybe expecting a ref to change it from undefined or something, instead, I was updating the "value" attribute with state.context switching it to controlled.
The fix was just a quick change to the initial values.

context: { C: ``, F: `` },
Enter fullscreen mode Exit fullscreen mode

I think this was also a more difficult challenge because I was still disassociating XState implementation from the React behavior and component for some reason. I think this exercise helped to concrete XState is a means of implementation, not a replacement.

Top comments (1)

Collapse
 
tweettamimi profile image
Tamimi

That's a very neat example! I have an important question tho, what theme are you using for your VsCode 😅