Skip to content

Instantly share code, notes, and snippets.

@staydecent
Last active February 26, 2020 16:34
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 staydecent/f03afc5614b7b4b3fb574755178a0edc to your computer and use it in GitHub Desktop.
Save staydecent/f03afc5614b7b4b3fb574755178a0edc to your computer and use it in GitHub Desktop.
const variantType = require("variant-type")
const hasProp = Object.prototype.hasOwnProperty
const LikedRes = (res) => hasProp.call(res, 'liked')
// We will use a "variant type" aka "tagged union" aka "sum type".
// This is sort of like a finite-state-machine, in that the "Request"
// value can only ever represent one of the following "states".
const Request = variantType({
Loading: [],
Loaded: [LikedRes],
Optimistic: [LikedRes],
Failed: [Error]
})
export function LikeButton () {
// Default state is loading, we can't let the user click the like button,
// because we don't know what value to send on a click, until we get the
// current value from the server.
const [state, setState] = useState(Request.Loading)
// Load value from server, only if we are currently 'Loading'
Request.case({
Loading: () => fetchValue()
.then(res => setState(Request.Loaded(res)))
.catch(err => setState(Request.Failed(err))),
_: () => null // Must always include a default, if you don't have a "handler" for every possible type
})(state)
// click handler
const handleClick = (ev) => {
ev.preventDefault()
// here is how we operate on the current value of the Request type.
Request.case({
// Only if our request is in Loaded state, can we properly handle a click
Loaded: ({ liked }) => {
setState(Request.Optimistic({ liked: !liked }))
postValueToServer(!liked)
.then(res => setState(Request.Loaded(res)))
.catch(err => setState(Request.Failed(err))
},
// for any case other than 'Loaded'
_: () => console.warn('like button is disabled')
})(state)
}
// We will also render based on the current Request type
return Request.case({
Loading: () => <div>Loading...</div>,
Loaded: ({ liked }) => <button onClick={handleClick}>{liked ? 'unlike' : 'like'}</button>,
// We removed the click handler when the update is still only Optimistic, but it's not necessary.
// If you refer the the click handler, it won't do anything unless the current state is Loaded.
// You could re-use the same JSX for both Loaded and Optimistic.
Optimistic: ({ liked }) => <button>{liked ? 'unlike' : 'like'}</button>,
Failed: () => <div>BROKEN</div>
})(state)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment