How it works

Super simple state machine using just html, css, and js.

To ensure that the actions that the user can do have to be represented inside of the div of that type. That way it can only progress in the ways that you want and not have any side effects.

Structure

Under the section, each day would be hidden, would not used and show on when it is being used. So only one can be shown at a time and the one that is showing has the actions inside of it. So that only those actions can take place well that is visible.

<section data-state="initial">
    <div id="initial">
        <!-- do something here -->
        <button onClick="submit">
    </div>

    <div id="pending">
        <!-- show a spinner -->
        <button onClick="cancel">
    </div>

    <div id="success">
        <!-- show a success message -->
        <button onClick="reset">
    </div>

    <div id="error">
        <!-- show an error message -->
        <button onClick="reset">
    </div>
</section>

Styling

Here you just simply show the ID of the dive they wanna show and hide everything else.

[data-state="initial"] {
    #initial {
        display: block;
    }
    #pending, #success, #error {
        display: none;
    }
}
[data-state="pending"] {
    #pending {
        display: block;
    }
    #initial, #success, #error {
        display: none;
    }
}
[data-state="success"] {
    #success {
        display: block;
    }
    #initial, #pending, #error {
        display: none;
    }
}
[data-state="error"] {
    #error {
        display: block;
    }
    #initial, #pending, #success {
        display: none;
    }
}

Logic

let stateDiv = document.querySelector('[data-state]');

fn submit() {

    const pendingTimer = setTimeout(() => {
        stateDiv.dataset.state = 'pending';
    }, 300);

    // do something
    // after sucessful operation
    fetch('https://api.example.com', {
        method: 'POST',
        body: JSON.stringify({}),
        headers: {
            'Content-Type': 'application/json'
        }
    }).then(response => {
      clearTimeout(pendingTimer);
        if (response.ok) {
            stateDiv.dataset.state = 'success';
        } else {
            stateDiv.dataset.state = 'error';
        }
    }).catch(() => {
        stateDiv.dataset.state = 'error';
    });
}

fn reset() {
    stateDiv.dataset.state = 'initial';
}

fn cancel() {
    stateDiv.dataset.state = 'initial';
}

Considerations

This doesn’t really scale at all. Don’t use it for a large scale deployment for anything. This is great for a personal blog or a small little website. Analyze you have reactivity and have a very light weight and fast site when the state gets a lot bigger you’re gonna end up with spaghetti code.

Larger apps consider using a library like XState.