Skip to content

Instantly share code, notes, and snippets.

@takkaria
Last active December 3, 2020 16:37
Show Gist options
  • Save takkaria/207914615898406b9141045cbd50ccd7 to your computer and use it in GitHub Desktop.
Save takkaria/207914615898406b9141045cbd50ccd7 to your computer and use it in GitHub Desktop.
macquette react
import React, { useState } from "react";
import { render } from "react-dom";
// This is obviously not a 'real' assessment class.
// I often try to practice API-first design and in this case this is the interface
// that fell out from trying to write the React views how I'd like to.
// We'll need something different for the actual assessment class.
class Assessment {
constructor() {
this.scenarios = [];
}
findScenario(name) {
return this.scenarios.filter((s) => s.name === name);
}
getScenario(name) {
let found = this.findScenario(name);
if (found.length === 0) {
throw new Error("Scenario doesn't exist");
}
return found[0];
}
newScenario(name) {
let found = this.findScenario(name);
if (found.length > 0) {
throw new Error(
"Can't create a new scenario with the same name as existing"
);
}
let scenario = {
name: name,
solarHotWater: new SolarHotWater(),
};
this.scenarios.push(scenario);
return scenario;
}
}
class SolarHotWater {
constructor() {
this.data = {
A: 1,
n0: 100,
};
}
get A() {
return this.data.A;
}
get n0() {
return this.data.n0;
}
set A(v) {
this.data.A = v;
update();
}
set n0(v) {
this.data.n0 = v;
update();
}
}
function Result({ val, dp = 2 }) {
if (isNaN(val)) {
return <span>-</span>;
} else {
return <span>{val.toFixed(dp)}</span>;
}
}
function useExternalState(propVal) {
// External state might not be the best phrase to use for this concept.
//
// The idea is that we need a form of state-holding that can react to changes in our
// data outside the React view. If you just use useState() then when a new prop
// comes into your function from the outside, it gets ignored. And sometimes this
// is right. But in our case we have data that gets recalculated outside of the
// Reacty portion of the code and we need the right updates to percolate into our
// code.
//
// So we have three values: the prop value, the current value and the monitor value:
//
// * The prop value is what is passed to us. This is only changed when we are
// passed a new value from whatever is telling React to render us.
// * The current value is the updatable value. If you have a text field, this is
// what we modify in onChange.
// * The monitor value is the third value we use to see what kind of change is
// happening. If it's the not same as the current value, we know that the user is
// editing our state and we should ignore any changes to the prop value. But if
// it's the same as our current value and the prop value changes, we treat this
// as an update from on high.
//
// Happily this is all abstractable within this function and it doesn't have to leak
// outside of it.
const [currentVal, setCurrentVal] = useState(propVal);
const [monitorVal, setMonitorVal] = useState(propVal);
if (propVal === currentVal && propVal !== monitorVal) {
setMonitorVal(propVal);
} else if (propVal !== currentVal && currentVal === monitorVal) {
setCurrentVal(propVal);
setMonitorVal(propVal);
}
return [currentVal, monitorVal, setCurrentVal];
}
function NumberField({ id, children, units, value, setValue }) {
const [current, monitor, setCurrent] = useExternalState(value);
// parseFloat is the wrong thing to use; we need a nice input component that allows
// entering numbers and the decimal place marker but also doesn't write 'NaN' when
// you empty out the field. XXX
return (
<div className="row">
<label htmlFor={`field_${id}`}>{children}</label>
<input
id={`field_${id}`}
onChange={(evt) => setCurrent(parseFloat(evt.target.value))}
onBlur={() => {
if (current !== monitor) {
setValue(current);
}
}}
value={current}
/>
{units}
</div>
);
}
function SolarHotWaterView({ scenario }) {
return (
<div>
<ul>
<li>
A = <Result val={scenario.solarHotWater.A} />
</li>
<li>
n = <Result val={scenario.solarHotWater.n0} />
</li>
</ul>
<NumberField
id="aperture"
units="m²"
value={scenario.solarHotWater.A}
setValue={(val) => (scenario.solarHotWater.A = val)}
>
Aperture area of solar collector, <var>A</var>
</NumberField>
<NumberField
id="n0"
value={scenario.solarHotWater.n0}
setValue={(val) => (scenario.solarHotWater.n0 = val)}
>
Zero-loss collector efficiency, <var>η0</var>, from test certificate or
Table H1
</NumberField>
<NumberField
id="AFP"
value={scenario.solarHotWater.AFP}
setValue={(val) => (scenario.solarHotWater.AFP = val)}
>
Ddfdfd
</NumberField>
</div>
);
}
const a = new Assessment();
let scenario = a.newScenario("Baseline");
function update() {
render(
React.createElement(SolarHotWaterView, {
assessment: a,
scenario: scenario,
}),
document.getElementById("root")
);
}
update();
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
let x = new Proxy(function (key) {
if (key == "cat") {
return "meow";
} else {
return 8;
}
});
x.test // == 8
x.cat // == "meow"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment