Last active
December 3, 2020 16:37
-
-
Save takkaria/207914615898406b9141045cbd50ccd7 to your computer and use it in GitHub Desktop.
macquette react
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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