|
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) |
|
} |