Skip to content

Instantly share code, notes, and snippets.

@cqfd
Created June 28, 2020 14:32
Show Gist options
  • Save cqfd/6b1a888db8130d36fa37a6e668c2958b to your computer and use it in GitHub Desktop.
Save cqfd/6b1a888db8130d36fa37a6e668c2958b to your computer and use it in GitHub Desktop.
React + generator workflows
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