Created
June 28, 2020 14:32
-
-
Save cqfd/6b1a888db8130d36fa37a6e668c2958b to your computer and use it in GitHub Desktop.
React + generator workflows
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
function NumberPicker({ onResult }: { onResult: (x: number) => void }) { | |
const inputEl: any = useRef(null) | |
return ( | |
<React.Fragment> | |
<label> | |
Enter a number: <input type="number" ref={inputEl} /> | |
</label> | |
<button onClick={() => onResult(Number(inputEl.current.value))}> | |
Ok | |
</button> | |
</React.Fragment> | |
) | |
} | |
function NamePicker({ onResult }: { onResult: (x: string) => void }) { | |
const inputEl: any = useRef(null) | |
return ( | |
<div> | |
<label> | |
Enter your name: <input type="text" ref={inputEl} /> | |
</label> | |
<button onClick={() => onResult(inputEl.current.value)}>Ok</button> | |
</div> | |
) | |
} | |
function StringPicker({ | |
name, | |
onResult, | |
}: { | |
name: string | |
onResult: (x: string) => void | |
}) { | |
const inputEl: any = useRef(null) | |
return ( | |
<div> | |
<label> | |
Enter your {name}: <input type="text" ref={inputEl} /> | |
</label> | |
<button onClick={() => onResult(inputEl.current.value)}>Ok</button> | |
</div> | |
) | |
} | |
function* someBigSuspendedSum(threshold: number) { | |
let x = 0 | |
while (x < threshold) { | |
const num = yield suspend((resume: any) => ( | |
<div> | |
<p>So far: {x}</p> | |
<NumberPicker onResult={resume} /> | |
</div> | |
)) | |
x += num | |
} | |
return x | |
} | |
class Suspend { | |
suspendedJsx: Function | |
constructor(suspendedJsx: Function) { | |
this.suspendedJsx = suspendedJsx | |
} | |
} | |
function suspend(suspendedJsx: Function) { | |
return new Suspend(suspendedJsx) | |
} | |
const sleep = (ms: number) => new Promise((wakeUp) => setTimeout(wakeUp, ms)) | |
function* login() { | |
const mobile = yield suspend((resume: any) => ( | |
<StringPicker name="mobile" onResult={resume} /> | |
)) | |
const password = yield suspend((resume: any) => ( | |
<StringPicker name="password" onResult={resume} /> | |
)) | |
return { mobile, password } | |
} | |
function* signUp() { | |
const mobile = yield suspend((resume: any) => ( | |
<StringPicker name="mobile" onResult={resume} /> | |
)) | |
const password = yield* getConfirmedPassword() | |
return { mobile, password } | |
} | |
function* getConfirmedPassword(): any { | |
const password = yield suspend((resume: any) => ( | |
<StringPicker name="password" onResult={resume} /> | |
)) | |
const confirmedPassword = yield suspend((resume: any) => ( | |
<div> | |
<p>Ok, now please confirm your password:</p> | |
<StringPicker name="password" onResult={resume} /> | |
</div> | |
)) | |
if (password != confirmedPassword) { | |
yield <p>Oh no! Those passwords didn't match :( Please try again.</p> | |
yield sleep(1000) | |
return yield* getConfirmedPassword() | |
} | |
return password | |
} | |
function LoadingSpinner() { | |
return ( | |
<Flow | |
flow={function* () { | |
let numDots = 1 | |
while (true) { | |
yield <p>Loading{".".repeat(numDots)}</p> | |
numDots = 1 + (numDots % 3) | |
yield sleep(1000) | |
} | |
}} | |
/> | |
) | |
} | |
// | |
function* example() { | |
let x = 6 | |
while (x > 0) { | |
yield <p>Counting down... {x}</p> | |
yield sleep(1000) | |
x -= 1 | |
} | |
const name = yield suspend((resume: any) => <NamePicker onResult={resume} />) | |
yield <p>Hi {name}!</p> | |
yield sleep(1000) | |
const numExclamationMarks = yield* someBigSuspendedSum(3) | |
const signupDetails = yield* signUp() | |
yield <LoadingSpinner /> | |
yield sleep(5000) | |
yield ( | |
<p> | |
Welcome to Wave {name} | |
{"!".repeat(numExclamationMarks)} | |
</p> | |
) | |
} | |
function oneshot(f: Function) { | |
let alreadyCalled = false | |
return function (...args: any) { | |
if (alreadyCalled) { | |
throw new Error("oneshot violation!") | |
} | |
alreadyCalled = true | |
return f(...args) | |
} | |
} | |
function Flow({ flow, onResult }: any) { | |
const [currentJsx, setCurrentJsx] = useState(null) | |
useEffect(() => { | |
let isMounted = true | |
function safeSetCurrentJsx(jsx: any) { | |
if (isMounted) { | |
setCurrentJsx(jsx) | |
} | |
} | |
let generator = flow() | |
function handleRequest(request: any) { | |
if (request instanceof Promise) { | |
request.then(respond, toss) | |
} else if (request instanceof Suspend) { | |
const resume = oneshot(respond) | |
const jsx = request.suspendedJsx(resume) | |
safeSetCurrentJsx(jsx) | |
} else { | |
safeSetCurrentJsx(request) | |
respond() | |
} | |
} | |
function respond(response?: any) { | |
handleNext(generator.next(response)) | |
} | |
function toss(exc: Error) { | |
handleNext(generator.throw(exc)) | |
} | |
function handleNext(next: any) { | |
if (!next.done) { | |
handleRequest(next.value) | |
} else if (onResult) { | |
setImmediate(() => onResult(next.value)) | |
} | |
} | |
respond() | |
return () => { | |
isMounted = false | |
} | |
}, []) | |
return currentJsx | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment