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.
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><inputvalue={state.c}onChange={(e) => {dispatch({ type: "setCelcius", payload: e.target.value });}}/><span>Celcius = </span><inputvalue={state.f}onChange={(e) => {dispatch({ type: "setFahrenheit", payload: e.target.value });}}/><span>Fahrenheit</span></div>);};
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.
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-629601674const TemperatureConverterComponent = (props) => {const [{ c, f }, setTemp] = useRecoilState(outputSelector);console.log(c, f);return (<div><inputvalue={c}onChange={(e) => {setTemp({type: "celcius",payload: e.target.value,});}}/><span>Celcius = </span><inputvalue={f}onChange={(e) => {setTemp({type: "fahrenheit",payload: e.target.value,});}}/><span>Fahrenheit Recoil</span></div>);};