Skip to content

Instantly share code, notes, and snippets.

@joeldenning
Last active October 7, 2022 19:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joeldenning/a43bf0fb7f4de99769625bb950bbd04b to your computer and use it in GitHub Desktop.
Save joeldenning/a43bf0fb7f4de99769625bb950bbd04b to your computer and use it in GitHub Desktop.
Exploring some ideas
import React, { ChangeEvent, FormEvent } from "react"
import { Trigger, useActions } from './Actions'
function Form(props: FormProps) {
const [state, act, modify, respond] = useActions<FormState>(props)
respond(Trigger.Mount, fetchCountries)
respond(Trigger.Change, updateProvinceList, state.birthCountry?.countryCode)
return (
<form onSubmit={act(submitActor)}>
<label>
First Name:
<input maxLength={props.maxInputLength} value={state.firstName} onChange={modify(firstNameUpdater)} />
</label>
<label>
Birth Country:
<select value={state.birthCountry?.countryCode ?? ""}>
{state.countries.map(country => (
<option key={country.countryCode} value={country.countryCode}>
{country.name}
</option>
))}
</select>
</label>
<label>
Province:
<select value={state.birthCountry?.countryCode ?? ""}>
{state.provinces.map(province => (
<option key={province.abbreviation} value={province.abbreviation}>
{province.name}
</option>
))}
</select>
</label>
<button type="submit">
Submit
</button>
</form>
)
}
function firstNameUpdater(evt: ChangeEvent<HTMLInputElement>) {
// Simple modifiers return state updates
return {
firstName: evt.target.value
}
}
async function updateProvinceList(state: FormState, props: FormProps, abortController: AbortController, setState: (state: Partial<FormState>) => any) {
if (!state.birthCountry) {
return
}
setState({
provinces: []
})
const response = await fetch(`/api/provinces/${state.birthCountry}`, {
signal: abortController.signal
})
if (!response.ok) {
throw Error(`Failed to fetch provinces, server responded with status ${response.status} ${response.statusText}`)
}
return {
provinces: await response.json()
}
}
async function fetchCountries(state: FormState, props: FormProps, abortController: AbortController) {
const response = await fetch(`/api/countries`, {
signal: abortController.signal
})
if (!response.ok) {
throw Error(`Failed to fetch countries, server responded with status ${response.status} ${response.statusText}`)
}
return {
countries: await response.json()
}
}
function submitActor(evt: FormEvent<HTMLFormElement>) {
// Occurs synchronously during event firing
evt.preventDefault();
// Complex actors return "action" functions
return async (state: FormState, props: FormProps, abortController: AbortController) => {
// Occurs inside of an effect
const response = await fetch(`/api/contacts`, {
method: "POST",
// AbortController cancels if component is unmounted
signal: abortController.signal,
body: JSON.stringify({
firstName: state.firstName
}),
headers: {
'content-type': 'application/json'
}
})
if (!response.ok) {
throw Error(`Create contacts API responded with HTTP status ${response.status} ${response.statusText}`)
}
}
}
interface FormProps {
maxInputLength: number;
}
interface FormState {
firstName: string;
birthCountry?: Country;
countries: Country[];
provinces: Province[]
}
interface Country {
name: string;
countryCode: string;
}
interface Province {
name: string;
abbreviation: string;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment