Highly Declarable React #dont-make-me-think #hdr

Here's something I recently discovered and that I'm proud of; not because it's technically superior, but instead because it's much easier for the human to deal with. I present to you

 Highly Declarative React   


The idea is that your component declare its various states up front, making it easy to reason about

function GenerateAuthTokenForm({isOpen, setIsOpen}: GenerateAuthTokenFormParams) {
...

const [state, dispatch] = useReducer((state: { mode: string }, response: any) => { ... }
...
return <SomeContainerElement>
    <ShowProgressWhenUserClicksGenerate/>

    <ShowSuccessAfterTokenCreated/>

    <ShowInputUiByDefault/>

    <ShowErrorWhenTokenGenerationFails/>

    <NotifyWhenUserTriesToCreateNamelessToken/>

    <ButtonPanel onCancel={() => setIsOpen(false)} onConfirm={handleSubmit}/>
    </SomeContainerElement>);
}
Each element checks the local page state to see if it should render and returns an empty fragment if not:

function ShowInputUiByDefault() {

if (
state.mode !== 'SHOW_INPUT_UI' &&
state.mode !== 'USER_TRIED_TO_CONFIRM_WITHOUT_ENTERING_A_NAME' &&
state.mode !== 'AUTHTOKEN_GENERATE_FAILED'
) return <></>
return <> ... </>
}
In the above example, unless we are in one of three clearly defined states, we don't want to render the default input UI. However, if we are (e.g. if a user tried to confirm our dialog without entering a name for an authentication token), we render the UI after any progress and success messages but before any error messages.

At the bottom of the control, we want to render a button panel. Again, depending on the state, different controls will be generated, but they are conceptually a part of this button panel:

function ButtonPanel({onCancel, onConfirm}: ButtonPanelParams) {

if (state.mode === 'AUTHTOKEN_GENERATE_SUCCEEDED') {
return <NotificationBox
message='Please make sure you store your authentication token as its code will be inaccessible once you close this dialog'
notificationType={NotificationType.Warning}>
<Button text="I understand" icon={"warning"} primary={false} className="confirm-button" onClick={() => {
dispatch({type: 'USER_DISMISSED_DIALOG'});
setIsOpen(false);
}}/>
</NotificationBox>

}

return <>
<Button text="Generate token" className="confirm-button" onClick={onConfirm}/>
<Button text="Cancel" icon={"chevron-left"} primary={false} className="cancel"
onClick={() => {
dispatch({type: 'USER_DISMISSED_DIALOG'});
setIsOpen(false);
}}/>
</>
}
Any complexities about how to render the panel is hidden within the local function component, meaning that - most of the time - the human don't have to think about it!

Keep it simple. Don't make me think.


Comments

Popular posts from this blog

Auto Mapper and Record Types - will they blend?

Unit testing your Azure functions - part 2: Queues and Blobs

Testing WCF services with user credentials and binary endpoints