Skip to content

Optimistic updates with rollback #825

@k2d222

Description

@k2d222

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:

  1. 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.
  1. 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>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions