70 %
Chris Biscardi

7guis RecoilJS: Temperature Converter

7guis, task1. Counter in regular React with useState vs a RecoilJS implementation.

It seems possible to actually combine the useReducer and atoms to build up a larger, easier to use set of functionality. Haven't fully explored building higher-level APIs on top of atoms yet though.

React useReducer

temperature-converter-react.js
JS
import React, { useReducer } from "react";
const cToF = (c) => c * (9 / 5) + 32;
const fToC = (f) => (f - 32) * (5 / 9);
const reducer = (state, action) => {
const parsedPayload = parseInt(action.payload);
const isNaN = Number.isNaN(parsedPayload);
switch (action.type) {
case "setCelcius":
if (isNaN) {
return {
...state,
c: action.payload,
};
}
return {
c: parsedPayload,
f: cToF(parsedPayload),
};
break;
case "setFahrenheit":
if (isNaN) {
return {
...state,
f: action.payload,
};
}
return {
c: fToC(parsedPayload),
f: parsedPayload,
};
break;
default:
throw new Error("invalid action");
}
};
export const TemperatureConverter = (props) => {
const [state, dispatch] = useReducer(reducer, { c: "", f: "" });
return (
<div>
<input
value={state.c}
onChange={(e) => {
dispatch({ type: "setCelcius", payload: e.target.value });
}}
/>
<span>Celcius = </span>
<input
value={state.f}
onChange={(e) => {
dispatch({ type: "setFahrenheit", payload: e.target.value });
}}
/>
<span>Fahrenheit</span>
</div>
);
};

Recoil Example

Note the usage of a selector to control how the atoms get set in tandem.

Another approach I thought about taking was to insert a lastUpdated value in each atom, then doing the cToF and fToC calculations in render based on which input was last updated.

Overall I'm not sure Recoil is a good fit for forms. It's possible that because this example requires both inputs to be "user input" and "user output" that we basically don't get a good result anyway.

temperature-converter-recoil.js
JS
import React from "react";
import {
RecoilRoot,
useRecoilValue,
useRecoilState,
atom,
selector,
} from "recoil";
const cToF = (c) => c * (9 / 5) + 32;
const fToC = (f) => (f - 32) * (5 / 9);
const celciusInput = atom({
key: "celcius",
default: "",
});
const fahrenheitInput = atom({
key: "fahrenheit",
default: "",
});
const outputSelector = selector({
key: "temp",
get: ({ get }) => {
return {
c: get(celciusInput),
f: get(fahrenheitInput),
};
},
set({ get, set }, newValue) {
const parsedPayload = parseInt(newValue.payload);
const isNaN = Number.isNaN(parsedPayload);
switch (newValue.type) {
case "celcius":
if (isNaN) {
console.log('nanset')
set(celciusInput, newValue.payload);
} else {
set(celciusInput, parsedPayload);
set(fahrenheitInput, cToF(parsedPayload));
}
break;
case "fahrenheit":
if (isNaN) {
set(fahrenheitInput, newValue.payload);
} else {
set(fahrenheitInput, parsedPayload);
set(celciusInput, fToC(parsedPayload));
}
break;
}
},
});
export const TemperatureConverter = (props) => (
<RecoilRoot>
<TemperatureConverterComponent />
</RecoilRoot>
);
const sel = selector({
key: "one",
get() {
return 2;
},
});
// https://github.com/facebookexperimental/Recoil/issues/41#issuecomment-629601674
const TemperatureConverterComponent = (props) => {
const [{ c, f }, setTemp] = useRecoilState(outputSelector);
console.log(c, f);
return (
<div>
<input
value={c}
onChange={(e) => {
setTemp({
type: "celcius",
payload: e.target.value,
});
}}
/>
<span>Celcius = </span>
<input
value={f}
onChange={(e) => {
setTemp({
type: "fahrenheit",
payload: e.target.value,
});
}}
/>
<span>Fahrenheit Recoil</span>
</div>
);
};