With now have everything ready to put all together and implement the final UI.
At this point it's all about designing the UI and handling all possible states.
We have some solid building blocks to start with:
- Custom styled React Aria components
- Hooks to read data from the database with live queries
- Actors to handle requests to the database
At the top we import all the hooks to read data from the database:
function RouteComponent() {
const { date } = Route.useSearch();
const dailyLog = useDailyLog(date);
const dailyPlan = useDailyPlan(date);
const plans = usePlans();
// ...
}
Each hook returns data
/loading
/error
/empty
that we can use to match for missing or invalid data:
function RouteComponent() {
const { date } = Route.useSearch();
const dailyLog = useDailyLog(date);
const dailyPlan = useDailyPlan(date);
const plans = usePlans();
if (plans.loading) {
return (
<div className="flex items-center justify-center inset-0 bg-white">
<Spinner />
</div>
);
} else if (plans.error) {
return (
<div className="flex items-center justify-center inset-0 bg-white">
<p className="text-sm">
Invalid data: {JSON.stringify(plans.error, null, 2)}
</p>
</div>
);
} else if (plans.empty) {
return (
<div className="flex flex-col items-center justify-center gap-y-4">
<Text>No plan yet created</Text>
<Link to="/plan">Go to create plan</Link>
</div>
);
}
// 👍 Otherwise everything is fine, render the UI
}
We define separate component to handle use cases defined inside actors and state machines (useMachine
). The building blocks are our custom React Aria styled components:
Notice how we do not need to bother with state logic inside the component. All the logic is encapsulated inside the machine, so that at the end all we need is to compose the UI.
export default function CreateFood() {
const [snapshot, send] = useMachine(machine, { input: undefined });
return (
<DialogTrigger>
<Button className="w-full">Create food</Button>
<ModalOverlay isDismissable>
<Modal>
<Dialog>
{({ close }) => (
{/* 🕹️ Render the UI based on the current state */}
)}
</Dialog>
</Modal>
</ModalOverlay>
</DialogTrigger>
);
}
With this approach all changes to the data are applied from state machine events. Every time some data changes, pglite will automatically reload the data and the UI will be updated (Reactive updates).
This means that we don't need solutions like React Query or to trigger a manual refresh of the page. That's the magic of live queries ⚡️