React-sync-ui promisify your React Components and make your UI awaitable
react-sync-ui
react-sync-ui
promisify your React Components and make your UI awaitable.
usage example
<button
onClick={async () => {
// call synchronous UI workflow with promisified React components
const likeSyncUI = await syncConfirm("Do you like this library?");
if (likeSyncUI) {
await syncAlert("Thanks, we like you too");
} else {
const isUserSure = await syncConfirm("Are you sure?");
if (isUserSure) {
await syncAlert("Try to give it a second try");
} else {
await syncAlert("Thanks, we like you too");
}
}
}}
>
Run
</button>
<button
onClick={async () => {
const name = await syncPrompt("Fill your name");
while ((await syncPrompt(`Fill your password!`)) !== "1234") {
await syncAlert("Invalid password, keep trying");
}
await syncAlert(`Congratulation ${name}, you are logged in`);
}}
>
Login
</button>
react-sync-ui
provides function makeSyncUI<InputData, ResolveValue>
which transform your declarative React Components
into the promisified awaitable functions.
// defining of your custom react-sync-ui component
export const syncAlert = makeSyncUI<string, void>((props) => (
<Modal isOpen toggle={() => props.resolve()}>
<ModalHeader>{props.data}</ModalHeader>
<ModalFooter>
<Button onClick={() => props.resolve()}>OK</Button>
</ModalFooter>
</Modal>
);
what is react-sync-ui
solving?
For a long time, I did not like that React’s functional way of declarative UI forced you to write nice UI code, but with ugly distributed business logic. When you have a complex business use case which is a composition of asynchronous actions like HTTP requests together with user interactions, your business logic code is distributed over many async React handlers and it’s really hard to understand the sequence of business logic.
People very often solve this problem by dependencies in the useEffect(..., [dependency])
which transfer React logic to the event-driven architecture.
When you change the dependency variable value, the different components will register the dependency change, and useEffect
will be re-called.
With this event-driven programming style, your code complexity is 1000 times more complex, and your newcomer programmes who see the code have the business workflow is.
A nice solution for this problem is to wrap your declarative React components into Promise wrappers which split your UIs into many smaller functions that can be simply composed together inside of your Javascript function and you’ll get very complex business logic in just a few lines of code.
Thanks to react-sync-ui
you can simply promisify your React Components UI and make your UI awaitable.
And that’s why react-sync-ui
was created. ❤
PS: i took inspiration from awesome functions: window.alert
, window.confirm
and window.prompt
.
Installation
npm i react-sync-ui
Usage
Setup <SyncUI />
Component into the root of yout project
import { SyncUI } from 'react-sync-ui'
const App = () => (
<>
<SyncUI />
<YourAppStuffs />
<>
)
const root = createRoot(document.getElementById('root')!);
root.render(<App />);
create sync UI
Now, you just have to define your custom React component which will be promisifed by makeSyncUI
function.
makeSyncUI
returns Promise which render your custom React Component.
Promise will be resolve when you call props.resolve(any)
inside of your custom UI.
Alert example
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from "reactstrap";
import { makeSyncUI } from "react-sync-ui";
export const syncAlert = makeSyncUI<string, void>((props) => (
<Modal isOpen={true} toggle={() => props.resolve()}>
<ModalHeader>{props.data}</ModalHeader>
<ModalFooter>
<Button onClick={() => props.resolve()}>OK</Button>
</ModalFooter>
</Modal>
);
// usage:
<button
onClick={async () => {
await syncAlert("Wait for it...");
await syncAlert("Wait for it...");
await syncAlert("You're hacked");
}}
>
click to me
</button>;
Prompt example
import { Button, Modal, ModalBody, ModalFooter } from "reactstrap";
import { makeSyncUI } from "react-sync-ui";
export const syncPrompt = makeSyncUI<string, string>((props) => {
const [input, setInput] = React.useState("");
return (
<Modal
toggle={() => props.reject(new Error("User forced close prompt modal"))}
isOpen={true}
>
<form
onSubmit={(e) => {
e.preventDefault();
setInput("");
props.resolve(input);
}}
>
<ModalBody>
<label>
{props.data}
<input
value={input}
onChange={(e) => setInput(e.target.value)}
type="text"
/>
</label>
</ModalBody>
<ModalFooter>
<Button type="submit">Accept</Button>
</ModalFooter>
</form>
</Modal>
);
});
// usage:
<button
onClick={async () => {
const usersFeelings = await syncPrompt("how are you");
}}
>
click to me
</button>;
Confirm example
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from "reactstrap";
import { makeSyncUI } from "react-sync-ui";
export const syncConfirm = makeSyncUI<
{
title: string;
description?: string;
okBtn?: string;
notOkBtn?: string;
},
boolean
>((props) => (
<Modal isOpen={true} toggle={() => props.resolve(false)}>
<ModalHeader>{props.data.title}</ModalHeader>
<ModalBody>{props.data.description}</ModalBody>
<ModalFooter>
<Button autoFocus onClick={() => props.resolve(true)}>
{props.data.okBtn ?? "Yes"}
</Button>
<Button onClick={() => props.resolve(false)}>
{props.data.notOkBtn ?? "No"}
</Button>
</ModalFooter>
</Modal>
));
// usage:
<button
onClick={async () => {
const isOk = await syncConfirm({
title: "How are you",
okBtn: "Good",
notOkBtn: "Not good",
});
}}
>
click to me
</button>;
Advance use-case, Multiple async queues
You’re able to initialized multiple independent instances of react-sync-ui
queue.
Multiple instances are made by xxx
import { syncUIFactory } from "react-sync-ui";
export const syncUI1 = syncUIFactory();
export const syncUI2 = syncUIFactory();
You may check the full multiple queue example here
react-sync-ui/example/MultiQueuesApp.tsx