-
Notifications
You must be signed in to change notification settings - Fork 145
Description
Describe The Problem To Be Solved
I'm looking for ways to perform optimistic updates in Solid, and I think it could be a useful primitive.
When a user interacts with a UI element that triggers a server API call (e.g., clicking a like button), there are two ways to handle asynchronicity:
- The "conservative" way:
- send the request and wait/listen to the response,
- if server success: mutate the frontend state.
- if server failure: display the error message.
- The "opportunistic" way:
- preemptively mutate the frontend state,
- send the request and wait/listen to the response,
- if server failure: rollback the state and display the error message.
Opportunistic updates make frontends snappier by erasing the client-server communication latency.
Suggest A Solution
I've recently been using a small utility to provide signals with async updates and rollback functionality.
The short version is below:
Details
function createOptimisticSignal<T>(commitFn: (val: T) => PromiseLike<void>, initialVal: T) {
const [committedVal, setCommittedVal] = createSignal<T>(initialVal)
const [tempVal, setTempVal] = createSignal<T>(initialVal)
const commit = async (v: T) => {
setTempVal(v)
try {
await commitFn(v)
setCommittedVal(v)
} catch (e) {
// rollback if update failed
setTempVal(committedVal())
throw e
}
}
createEffect(() => {
// reset the temporary value on commit
setTempVal(committedVal())
})
return {
val: tempVal,
commit,
committedVal,
setCommittedVal,
}
}in a nutshell, it returns a signal getter and a setter (val and commit). The commit function must throw or reject if the transaction failed. It also provides means to get and set the committed value manually. A more polished version of this utility would contain error handling and state management, like createResource().
Example usage below:
Details
interface Server {
setLiked(b: boolean) => Promise<void> // may throw, if the server fails for some reason
getLiked() => Promise<boolean>
on(event: string, callback: Function) // has events that may affect the 'liked' state
}
const likeButton = createOptimisticSignal<boolean>(server.setLiked, false)
server.on('liked', likeButton.setCommittedVal) // may be triggered if my friend clicks the button…
<button on:click={() => likeButton.commit(!likeButton.val())}>{likeButton.val() ? 'Unlike me!' : 'Like me!'}</button>