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:
export default function DailyLogOverview({
date,
}: {
date: typeof DailyLogSelect.fields.date.Type;
}) {
const dailyLog = useDailyLog(date);
const dailyPlan = useDailyPlan(date);
const plans = usePlans();
// ...
}
Each hook returns a Either
that we can use to match for missing or invalid data, using Either.isLeft
and Match.valueTags
(exhaustive pattern matching):
export default function DailyLogOverview({
date,
}: {
date: typeof DailyLogSelect.fields.date.Type;
}) {
const dailyLog = useDailyLog(date);
const dailyPlan = useDailyPlan(date);
const plans = usePlans();
if (Either.isLeft(plans)) {
return pipe(
plans.left,
Match.valueTags({
MissingData: () => (
<div className="flex items-center justify-center inset-0 bg-white">
<Spinner />
</div>
),
InvalidData: ({ parseError }) => (
<div className="flex items-center justify-center inset-0 bg-white">
<p className="text-sm">
Invalid data: {JSON.stringify(parseError, null, 2)}
</p>
</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 âĄī¸