How to write autosaving forms to create and update CoValues
This guide shows you a simple and powerful way to implement forms for creating and updating CoValues.
We'll build:
- An update form that saves changes as you make them, removing the need for a save button.
- A create form that autosaves your changes into a draft, so you can come back to it later.
Note: If you do need a save button on your update form, this guide is not for you. Another option is to use react-hook-form, which you can see in this example.
Updating a CoValue
To update a CoValue, we simply assign the new value directly as changes happen. These changes are synced to the server.
<input type="text" value={order.name} onChange={(e) => order.name = e.target.value} />
It's that simple!
Creating a CoValue
However, when creating a CoValue, the CoValue does not exist yet, so we don't have the advantages previously mentioned.
There's a way around this, and it provides unexpected benefits too.
Using a Draft CoValue
Let's say we have a CoValue called BubbleTeaOrder
. We can create a "draft" CoValue,
which is an empty version of a BubbleTeaOrder
, that we can then modify when we are "creating"
a new CoValue.
A DraftBubbleTeaOrder
is essentially a copy of BubbleTeaOrder
, but with all the fields made optional.
// schema.ts export const
BubbleTeaOrder =
const BubbleTeaOrder: CoMapSchema<{ name: z.z.ZodString; }>
import co
co.map({
map<{ name: z.z.ZodString; }>(shape: { name: z.z.ZodString; }): CoMapSchema<{ name: z.z.ZodString; }> export map
name: z.z.ZodString
name:import z
z.string(), }); export const
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
DraftBubbleTeaOrder =
const DraftBubbleTeaOrder: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>
import co
co.map({
map<{ name: z.ZodOptional<z.z.ZodString>; }>(shape: { name: z.ZodOptional<z.z.ZodString>; }): CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }> export map
name: z.ZodOptional<z.z.ZodString>
name:import z
z.optional(
optional<z.z.ZodString>(innerType: z.z.ZodString): z.ZodOptional<z.z.ZodString> export optional
import z
z.string()), });
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
Writing the components in React
Let's write the form component that will be used for both create and update.
// OrderForm.tsx export function
OrderForm({
function OrderForm({ order, onSave, }: { order: Loaded<typeof BubbleTeaOrder> | Loaded<typeof DraftBubbleTeaOrder>; onSave?: (e: React.FormEvent<HTMLFormElement>) => void; }): React.JSX.Element
order,
order: ({ name: string; } & CoMap) | ({ name: string | undefined; } & CoMap)
onSave: ((e: React.FormEvent<HTMLFormElement>) => void) | undefined
onSave, }: {order:
order: ({ name: string; } & CoMap) | ({ name: string | undefined; } & CoMap)
Loaded<typeof
type Loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...
BubbleTeaOrder> |
const BubbleTeaOrder: CoMapSchema<{ name: z.z.ZodString; }>
Loaded<typeof
type Loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...
DraftBubbleTeaOrder>;
const DraftBubbleTeaOrder: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>
onSave?: ((e: React.FormEvent<HTMLFormElement>) => void) | undefined
onSave?: (e: React.FormEvent<HTMLFormElement>
e: React.interface React.FormEvent<T = Element>
FormEvent<HTMLFormElement>) => void; }) { return ( <JSX.IntrinsicElements.form: React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>
formReact.DOMAttributes<HTMLFormElement>.onSubmit?: React.FormEventHandler<HTMLFormElement> | undefined
onSubmit={onSave: ((e: React.FormEvent<HTMLFormElement>) => void) | undefined
onSave}> <JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>
label> Name <JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
inputReact.InputHTMLAttributes<HTMLInputElement>.type?: React.HTMLInputTypeAttribute | undefined
type="text"React.InputHTMLAttributes<HTMLInputElement>.value?: string | number | readonly string[] | undefined
value={order.
order: ({ name: string; } & CoMap) | ({ name: string | undefined; } & CoMap)
name: string | undefined
name}React.InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefined
onChange={(e: React.ChangeEvent<HTMLInputElement>
e) => (order.
order: ({ name: string; } & CoMap) | ({ name: string | undefined; } & CoMap)
name: string | undefined
name =e: React.ChangeEvent<HTMLInputElement>
e.React.ChangeEvent<HTMLInputElement>.target: EventTarget & HTMLInputElement
target.HTMLInputElement.value: string
Returns the value of the data at the cursor's current position. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)value)}React.InputHTMLAttributes<HTMLInputElement>.required?: boolean | undefined
required /> </JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>
label> {onSave: ((e: React.FormEvent<HTMLFormElement>) => void) | undefined
onSave && <JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
buttonReact.ButtonHTMLAttributes<HTMLButtonElement>.type?: "button" | "reset" | "submit" | undefined
type="submit">Submit</JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button>} </JSX.IntrinsicElements.form: React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>
form> ); }
Writing the edit form
To make the edit form, simply pass the BubbleTeaOrder
.
// EditOrder.tsx export function
EditOrder(
function EditOrder(props: { id: string; }): React.JSX.Element | undefined
props: {
props: { id: string; }
id: string
id: string }) { constorder =
const order: ({ name: string; } & CoMap) | null | undefined
useCoState(
useCoState<CoMapSchema<{ name: z.z.ZodString; }>, true>(Schema: CoMapSchema<{ name: z.z.ZodString; }>, id: string | undefined, options?: { resolve?: RefsToResolve<{ name: string; } & CoMap, 10, []> | undefined; } | undefined): ({ ...; } & CoMap) | ... 1 more ... | undefined
BubbleTeaOrder,
const BubbleTeaOrder: CoMapSchema<{ name: z.z.ZodString; }>
props.
props: { id: string; }
id: string
id); if (!order) return; return <
const order: ({ name: string; } & CoMap) | null | undefined
OrderForm
function OrderForm({ order, onSave, }: { order: Loaded<typeof BubbleTeaOrder> | Loaded<typeof DraftBubbleTeaOrder>; onSave?: (e: React.FormEvent<HTMLFormElement>) => void; }): React.JSX.Element
order={
order: ({ name: string; } & CoMap) | ({ name: string | undefined; } & CoMap)
order} />; }
const order: { name: string; } & CoMap
Writing the create form
For the create form, we need to:
- Create a draft order.
- Edit the draft order.
- Convert the draft order to a "real" order on submit.
Here's how that looks like:
// CreateOrder.tsx export function
function CreateOrder(): React.JSX.Element | undefined
CreateOrder() { const {me } =
const me: NonNullable<Account | ({ [x: string]: any; } & Account) | null>
useAccount(); const [
useAccount<AccountClass<Account> | AnyAccountSchema>(AccountSchema?: AccountClass<Account> | AnyAccountSchema | undefined): { ...; } (+1 overload)
draft,
const draft: ({ name: string | undefined; } & CoMap) | undefined
setDraft] =
const setDraft: React.Dispatch<React.SetStateAction<({ name: string | undefined; } & CoMap) | undefined>>
useState<{ name: string | undefined; } & CoMap>(): [({ name: string | undefined; } & CoMap) | undefined, React.Dispatch<React.SetStateAction<({ name: string | undefined; } & CoMap) | undefined>>] (+1 overload)
Returns a stateful value, and a function to update it.useState<Loaded<typeof
type Loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...
DraftBubbleTeaOrder>>();
const DraftBubbleTeaOrder: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
Accepts a function that contains imperative, possibly effectful code.useEffect(() => {setDraft(
const setDraft: (value: React.SetStateAction<({ name: string | undefined; } & CoMap) | undefined>) => void
DraftBubbleTeaOrder.
const DraftBubbleTeaOrder: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>
create({})); }, [
create: (init: { name?: string | undefined; }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group) => { ...; } & CoMap
me?.
const me: NonNullable<Account | ({ [x: string]: any; } & Account) | null>
Account.id: string
id]); constconst onSave: (e: React.FormEvent<HTMLFormElement>) => void
onSave = (e: React.FormEvent<HTMLFormElement>
e: React.interface React.FormEvent<T = Element>
FormEvent<HTMLFormElement>) => {e: React.FormEvent<HTMLFormElement>
e.React.BaseSyntheticEvent<Event, EventTarget & HTMLFormElement, EventTarget>.preventDefault(): void
preventDefault(); if (!draft || !
const draft: ({ name: string | undefined; } & CoMap) | undefined
draft.
const draft: { name: string | undefined; } & CoMap
name: string | undefined
name) return; constorder =
const order: { name: string; } & CoMap
draft as
const draft: { name: string | undefined; } & CoMap
Loaded<typeof
type Loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...
BubbleTeaOrder>; // TODO: this should narrow correctly
const BubbleTeaOrder: CoMapSchema<{ name: z.z.ZodString; }>
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.log("Order created:",order); }; if (!
const order: { name: string; } & CoMap
draft) return; return <
const draft: ({ name: string | undefined; } & CoMap) | undefined
OrderForm
function OrderForm({ order, onSave, }: { order: Loaded<typeof BubbleTeaOrder> | Loaded<typeof DraftBubbleTeaOrder>; onSave?: (e: React.FormEvent<HTMLFormElement>) => void; }): React.JSX.Element
order={
order: ({ name: string | undefined; } & CoMap) | ({ name: string; } & CoMap)
draft}
const draft: { name: string | undefined; } & CoMap
onSave?: ((e: React.FormEvent<HTMLFormElement>) => void) | undefined
onSave={const onSave: (e: React.FormEvent<HTMLFormElement>) => void
onSave} />; }
Validation
In a BubbleTeaOrder
, the name
field is required, so it would be a good idea to validate this before turning the draft into a real order.
Update the schema to include a validate
helper.
// schema.ts export const
DraftBubbleTeaOrder =
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): { ...; }; }>
import co
co.map({
map<{ name: z.ZodOptional<z.z.ZodString>; }>(shape: { name: z.ZodOptional<z.z.ZodString>; }): CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }> export map
name: z.ZodOptional<z.z.ZodString>
name:import z
z.optional(
optional<z.z.ZodString>(innerType: z.z.ZodString): z.ZodOptional<z.z.ZodString> export optional
import z
z.string()), }).
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
withHelpers((
withHelpers<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): { ...; }; }>(this: CoMapSchema<...>, helpers: (Self: CoMapSchema<...>) => { validate(draft: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): { ...; }; }): WithHelpers<...>
Self) => ({
type Self: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>
validate(
function validate(draft: Loaded<typeof Self>): { errors: string[]; }
draft:
draft: { name: string | undefined; } & CoMap
Loaded<typeof
type Loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...
Self>) { const
type Self: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>
const errors: string[]
errors: string[] = []; if (!draft.
draft: { name: string | undefined; } & CoMap
name: string | undefined
name) {const errors: string[]
errors.Array<string>.push(...items: string[]): number
Appends new elements to the end of an array, and returns the new length of the array.push("Please enter a name."); } return {errors: string[]
errors }; }, }));
Then perform the validation on submit.
// CreateOrder.tsx export function
function CreateOrder(): React.JSX.Element | undefined
CreateOrder() { const {me } =
const me: NonNullable<Account | ({ [x: string]: any; } & Account) | null>
useAccount(); const [
useAccount<AccountClass<Account> | AnyAccountSchema>(AccountSchema?: AccountClass<Account> | AnyAccountSchema | undefined): { ...; } (+1 overload)
draft,
const draft: ({ name: string | undefined; } & CoMap) | undefined
setDraft] =
const setDraft: React.Dispatch<React.SetStateAction<({ name: string | undefined; } & CoMap) | undefined>>
useState<{ name: string | undefined; } & CoMap>(): [({ name: string | undefined; } & CoMap) | undefined, React.Dispatch<React.SetStateAction<({ name: string | undefined; } & CoMap) | undefined>>] (+1 overload)
Returns a stateful value, and a function to update it.useState<Loaded<typeof
type Loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...
DraftBubbleTeaOrder>>();
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): { ...; }; }>
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
Accepts a function that contains imperative, possibly effectful code.useEffect(() => {setDraft(
const setDraft: (value: React.SetStateAction<({ name: string | undefined; } & CoMap) | undefined>) => void
DraftBubbleTeaOrder.
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): { ...; }; }>
create({})); }, [
create: (init: { name?: string | undefined; }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group) => { ...; } & CoMap
me?.
const me: NonNullable<Account | ({ [x: string]: any; } & Account) | null>
Account.id: string
id]); constconst onSave: (e: React.FormEvent<HTMLFormElement>) => void
onSave = (e: React.FormEvent<HTMLFormElement>
e: React.interface React.FormEvent<T = Element>
FormEvent<HTMLFormElement>) => {e: React.FormEvent<HTMLFormElement>
e.React.BaseSyntheticEvent<Event, EventTarget & HTMLFormElement, EventTarget>.preventDefault(): void
preventDefault(); if (!draft) return; const
const draft: ({ name: string | undefined; } & CoMap) | undefined
validation =
const validation: { errors: string[]; }
DraftBubbleTeaOrder.
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): { ...; }; }>
validate(
function validate(draft: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): { errors: string[]; }
draft); if (
const draft: { name: string | undefined; } & CoMap
validation.
const validation: { errors: string[]; }
errors: string[]
errors.Array<T>.length: number
Gets or sets the length of the array. This is a number one higher than the highest index in the array.length > 0) {var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.log(validation.
const validation: { errors: string[]; }
errors: string[]
errors); return; } constorder =
const order: { name: string; } & CoMap
draft as
const draft: { name: string | undefined; } & CoMap
Loaded<typeof
type Loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...
BubbleTeaOrder>;
const BubbleTeaOrder: CoMapSchema<{ name: z.z.ZodString; }>
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.log("Order created:",order); }; if (!
const order: { name: string; } & CoMap
draft) return; return <
const draft: ({ name: string | undefined; } & CoMap) | undefined
OrderForm
function OrderForm({ order, onSave, }: { order: Loaded<typeof BubbleTeaOrder> | Loaded<typeof DraftBubbleTeaOrder>; onSave?: (e: React.FormEvent<HTMLFormElement>) => void; }): React.JSX.Element
order={
order: ({ name: string | undefined; } & CoMap) | ({ name: string; } & CoMap)
draft}
const draft: { name: string | undefined; } & CoMap
onSave?: ((e: React.FormEvent<HTMLFormElement>) => void) | undefined
onSave={const onSave: (e: React.FormEvent<HTMLFormElement>) => void
onSave} />; }
Saving the user's work-in-progress
It turns out that using this pattern also provides a UX improvement.
By storing the draft in the user's account, they can come back to it anytime without losing their work. 🙌
// schema.ts export const
BubbleTeaOrder =
const BubbleTeaOrder: CoMapSchema<{ name: z.z.ZodString; }>
import co
co.map({
map<{ name: z.z.ZodString; }>(shape: { name: z.z.ZodString; }): CoMapSchema<{ name: z.z.ZodString; }> export map
name: z.z.ZodString
name:import z
z.string(), }); export const
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
DraftBubbleTeaOrder =
const DraftBubbleTeaOrder: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>
import co
co.map({
map<{ name: z.ZodOptional<z.z.ZodString>; }>(shape: { name: z.ZodOptional<z.z.ZodString>; }): CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }> export map
name: z.ZodOptional<z.z.ZodString>
name:import z
z.optional(
optional<z.z.ZodString>(innerType: z.z.ZodString): z.ZodOptional<z.z.ZodString> export optional
import z
z.string()), }); export const
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
AccountRoot =
const AccountRoot: CoMapSchema<{ draft: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>; }>
import co
co.map({
map<{ draft: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>; }>(shape: { draft: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>; }): CoMapSchema<...> export map
draft:
draft: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>
DraftBubbleTeaOrder, }); export const
const DraftBubbleTeaOrder: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>
JazzAccount =
const JazzAccount: AccountSchema<{ root: CoMapSchema<{ draft: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>; }>; profile: CoMapSchema<{ name: z.z.ZodString; }>; }>
import co
co.account({
account<{ root: CoMapSchema<{ draft: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>; }>; profile: CoMapSchema<{ name: z.z.ZodString; }>; }>(shape?: { root: CoMapSchema<{ draft: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>; }>; profile: CoMapSchema<{ name: z.z.ZodString; }>; } | undefined): AccountSchema<...> export account
root:
root: CoMapSchema<{ draft: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>; }>
AccountRoot,
const AccountRoot: CoMapSchema<{ draft: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>; }>
profile:
profile: CoMapSchema<{ name: z.z.ZodString; }>
import co
co.map({
map<{ name: z.z.ZodString; }>(shape: { name: z.z.ZodString; }): CoMapSchema<{ name: z.z.ZodString; }> export map
name: z.z.ZodString
name:import z
z.string() }), }).
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
withMigration((
function withMigration(migration: (account: { root: { draft: { name: string | undefined; } & CoMap; } & CoMap; profile: { name: string; } & CoMap; } & { profile: Profile; } & Account, creationProps?: { name: string; }) => void): AccountSchema<...>
account,
account: { root: { draft: { name: string | undefined; } & CoMap; } & CoMap; profile: { name: string; } & CoMap; } & { profile: Profile; } & Account
creationProps?: {
creationProps: { name: string; } | undefined
name: string
name: string }) => { if (account.
account: { root: { draft: { name: string | undefined; } & CoMap; } & CoMap; profile: { name: string; } & CoMap; } & { profile: Profile; } & Account
root ===
Account.root: { draft: { name: string | undefined; } & CoMap; } & CoMap
var undefined
undefined) { constdraft =
const draft: { name: string | undefined; } & CoMap
DraftBubbleTeaOrder.
const DraftBubbleTeaOrder: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>
create({});
create: (init: { name?: string | undefined; }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group) => { ...; } & CoMap
account.
account: { root: { draft: { name: string | undefined; } & CoMap; } & CoMap; profile: { name: string; } & CoMap; } & { profile: Profile; } & Account
root =
Account.root: { draft: { name: string | undefined; } & CoMap; } & CoMap
AccountRoot.
const AccountRoot: CoMapSchema<{ draft: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>; }>
create({
create: (init: { draft: { name: string | undefined; } & CoMap; }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group) => { ...; } & CoMap
draft }); } });
draft: { name: string | undefined; } & CoMap
Let's not forget to update the AccountSchema
.
import {
Loaded } from "jazz-tools" import {
type Loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...
function JazzProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | AnyAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, }: JazzProviderProps<S>): JSX.Element
JazzProvider } from "jazz-react"; import {JazzAccount } from "./schema"; export function
const JazzAccount: AccountSchema<{ root: CoMapSchema<{ draft: CoMapSchema<{ name: ZodOptional<ZodString>; }>; }>; profile: CoMapSchema<{ name: ZodString; }>; }>
MyJazzProvider({
function MyJazzProvider({ children }: { children: React.ReactNode; }): React.JSX.Element
children: React.ReactNode
children }: {children: React.ReactNode
children: React.type React.ReactNode = string | number | boolean | React.ReactElement<any, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.ReactPortal | null | undefined
Represents all of the things React can render. Where {@link ReactElement } only represents JSX, `ReactNode` represents everything that can be rendered.ReactNode }) { return ( <function JazzProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | AnyAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, }: JazzProviderProps<S>): JSX.Element
JazzProvidersync: SyncConfig
sync={{peer: "wss://cloud.jazz.tools/?key=you@example.com"
peer: "wss://cloud.jazz.tools/?key=you@example.com" }}AccountSchema={
AccountSchema?: AccountSchema<{ root: CoMapSchema<{ draft: CoMapSchema<{ name: ZodOptional<ZodString>; }>; }>; profile: CoMapSchema<{ name: ZodString; }>; }> | undefined
JazzAccount} > {
const JazzAccount: AccountSchema<{ root: CoMapSchema<{ draft: CoMapSchema<{ name: ZodOptional<ZodString>; }>; }>; profile: CoMapSchema<{ name: ZodString; }>; }>
children: React.ReactNode
children} </function JazzProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | AnyAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, }: JazzProviderProps<S>): JSX.Element
JazzProvider> ); }
Instead of creating a new draft every time we use the create form, let's use the draft from the account root.
// CreateOrder.tsx export function
function CreateOrder(): React.JSX.Element | undefined
CreateOrder() { const {me } =
const me: ({ root: { draft: { name: string | undefined; } & CoMap; } & { draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap; } & { root: ({ draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap) | null; profile: ({ ...; } & CoMap) | null; } & Account) | null | undefined
useAccount(
useAccount<AccountSchema<{ root: CoMapSchema<{ draft: WithHelpers<CoMapSchema<{ name: ZodOptional<ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: ZodOptional<ZodString>; }>>): { ...; }; }>; }>; profile: CoMapSchema<...>; }>, { ...; }>(AccountSchema: AccountSchema<...>, options?: { ...; } | undefined): { ...; } (+1 overload)
JazzAccount, {
const JazzAccount: AccountSchema<{ root: CoMapSchema<{ draft: WithHelpers<CoMapSchema<{ name: ZodOptional<ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: ZodOptional<ZodString>; }>>): { ...; }; }>; }>; profile: CoMapSchema<...>; }>
resolve: {
resolve?: RefsToResolve<{ root: ({ draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap) | null; profile: ({ ...; } & CoMap) | null; } & Account, 10, []> | undefined
root: {
root?: RefsToResolve<{ draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap, 10, [0]> | undefined
draft: true } }, }); if (!
draft?: RefsToResolve<{ name: string | undefined; } & CoMap, 10, [0, 0]> | undefined
me?.
const me: ({ root: { draft: { name: string | undefined; } & CoMap; } & { draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap; } & { root: ({ draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap) | null; profile: ({ ...; } & CoMap) | null; } & Account) | null | undefined
root) return; const
Account.root: ({ draft: { name: string | undefined; } & CoMap; } & { draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap) | undefined
const onSave: (e: React.FormEvent<HTMLFormElement>) => void
onSave = (e: React.FormEvent<HTMLFormElement>
e: React.interface React.FormEvent<T = Element>
FormEvent<HTMLFormElement>) => {e: React.FormEvent<HTMLFormElement>
e.React.BaseSyntheticEvent<Event, EventTarget & HTMLFormElement, EventTarget>.preventDefault(): void
preventDefault(); constdraft =
const draft: { name: string | undefined; } & CoMap
me.
const me: { root: { draft: { name: string | undefined; } & CoMap; } & { draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap; } & { root: ({ draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap) | null; profile: ({ ...; } & CoMap) | null; } & Account
root.
Account.root: { draft: { name: string | undefined; } & CoMap; } & { draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap
draft; if (!
draft: { name: string | undefined; } & CoMap
draft) return; const
const draft: { name: string | undefined; } & CoMap
validation =
const validation: { errors: string[]; }
DraftBubbleTeaOrder.
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{ name: ZodOptional<ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: ZodOptional<ZodString>; }>>): { ...; }; }>
validate(
function validate(draft: Loaded<CoMapSchema<{ name: ZodOptional<ZodString>; }>>): { errors: string[]; }
draft); if (
const draft: { name: string | undefined; } & CoMap
validation.
const validation: { errors: string[]; }
errors: string[]
errors.Array<T>.length: number
Gets or sets the length of the array. This is a number one higher than the highest index in the array.length > 0) {var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.log(validation.
const validation: { errors: string[]; }
errors: string[]
errors); return; } constorder =
const order: { name: string; } & CoMap
draft as
const draft: { name: string | undefined; } & CoMap
Loaded<typeof
type Loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...
BubbleTeaOrder>;
const BubbleTeaOrder: CoMapSchema<{ name: ZodString; }>
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.log("Order created:",order); // create a new empty draft
const order: { name: string; } & CoMap
me.
const me: { root: { draft: { name: string | undefined; } & CoMap; } & { draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap; } & { root: ({ draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap) | null; profile: ({ ...; } & CoMap) | null; } & Account
root.
Account.root: { draft: { name: string | undefined; } & CoMap; } & { draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap
draft =
draft: { name: string | undefined; } & CoMap
DraftBubbleTeaOrder.
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{ name: ZodOptional<ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: ZodOptional<ZodString>; }>>): { ...; }; }>
create( {}, ); }; return <
create: (init: { name?: string | undefined; }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group) => { ...; } & CoMap
CreateOrderForm
function CreateOrderForm({ id, onSave, }: { id: string; onSave: (e: React.FormEvent<HTMLFormElement>) => void; }): React.JSX.Element | undefined
id: string
id={me.
const me: { root: { draft: { name: string | undefined; } & CoMap; } & { draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap; } & { root: ({ draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap) | null; profile: ({ ...; } & CoMap) | null; } & Account
root.
Account.root: { draft: { name: string | undefined; } & CoMap; } & { draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap
draft.
draft: { name: string | undefined; } & CoMap
CoMap.id: string
The ID of this `CoMap`id}onSave: (e: React.FormEvent<HTMLFormElement>) => void
onSave={const onSave: (e: React.FormEvent<HTMLFormElement>) => void
onSave} /> } functionCreateOrderForm({
function CreateOrderForm({ id, onSave, }: { id: string; onSave: (e: React.FormEvent<HTMLFormElement>) => void; }): React.JSX.Element | undefined
id: string
id,onSave: (e: React.FormEvent<HTMLFormElement>) => void
onSave, }: {id: string
id: stringonSave: (e: React.FormEvent<HTMLFormElement>) => void
onSave: (e: React.FormEvent<HTMLFormElement>
e: React.interface React.FormEvent<T = Element>
FormEvent<HTMLFormElement>) => void; }) { constdraft =
const draft: ({ name: string | undefined; } & CoMap) | null | undefined
useCoState(
useCoState<WithHelpers<CoMapSchema<{ name: ZodOptional<ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: ZodOptional<ZodString>; }>>): { ...; }; }>, true>(Schema: WithHelpers<...>, id: string | undefined, options?: { ...; } | undefined): ({ ...; } & CoMap) | ... 1 more ... | undefined
DraftBubbleTeaOrder,
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{ name: ZodOptional<ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: ZodOptional<ZodString>; }>>): { ...; }; }>
id: string
id); if (!draft) return; return <
const draft: ({ name: string | undefined; } & CoMap) | null | undefined
OrderForm
function OrderForm({ order, onSave, }: { order: Loaded<typeof BubbleTeaOrder> | Loaded<typeof DraftBubbleTeaOrder>; onSave?: (e: React.FormEvent<HTMLFormElement>) => void; }): React.JSX.Element
order={
order: ({ name: string | undefined; } & CoMap) | ({ name: string; } & CoMap)
draft}
const draft: { name: string | undefined; } & CoMap
onSave?: ((e: React.FormEvent<HTMLFormElement>) => void) | undefined
onSave={onSave: (e: React.FormEvent<HTMLFormElement>) => void
onSave} />; }
When the new draft is created, we need to call useCoState
again, so that we are passing the new draft to <OrderForm/>
.
There you have it! Notice that when you refresh the page, you will see your unsaved changes.
Draft indicator
To improve the UX even further, in just a few more steps, we can tell the user that they currently have unsaved changes.
Simply add a hasChanges
helper to your schema.
// schema.ts export const
DraftBubbleTeaOrder =
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): { ...; }; hasChanges(draft?: Loaded<CoMapSchema<...>>): number | false; }>
import co
co.map({
map<{ name: z.ZodOptional<z.z.ZodString>; }>(shape: { name: z.ZodOptional<z.z.ZodString>; }): CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }> export map
name: z.ZodOptional<z.z.ZodString>
name:import z
z.optional(
optional<z.z.ZodString>(innerType: z.z.ZodString): z.ZodOptional<z.z.ZodString> export optional
import z
z.string()), }).
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string
withHelpers((
withHelpers<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): { ...; }; hasChanges(draft?: Loaded<CoMapSchema<...>>): number | false; }>(this: CoMapSchema<...>, helpers: (Self: CoMapSchema<...>) => { validate(draft: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): { ...; }; hasChanges(draft?: Loaded<CoMapSchema<...>>): number | false; }): WithHelpers<...>
Self) => ({
type Self: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>
validate(
function validate(draft: Loaded<typeof Self>): { errors: string[]; }
draft:
draft: { name: string | undefined; } & CoMap
Loaded<typeof
type Loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...
Self>) { const
type Self: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>
const errors: string[]
errors: string[] = []; if (!draft.
draft: { name: string | undefined; } & CoMap
name: string | undefined
name) {const errors: string[]
errors.Array<string>.push(...items: string[]): number
Appends new elements to the end of an array, and returns the new length of the array.push("Plese enter a name."); } return {errors: string[]
errors }; },function hasChanges(draft?: Loaded<typeof Self>): number | false
hasChanges(draft?:
draft: ({ name: string | undefined; } & CoMap) | undefined
Loaded<typeof
type Loaded<T extends CoValueClass | AnyCoSchema, R extends ResolveQuery<T> = true> = R extends boolean | undefined ? NonNullable<InstanceOfSchemaCoValuesNullable<T>> : [NonNullable<InstanceOfSchemaCoValuesNullable<T>>] extends [...] ? Exclude<...> extends CoValue ? R extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...> extends CoValue ? ItemDepth extends { ...; } ? ((CoValue & ... 1 more ... & (ItemDepth extends boolean | undefined ? CoValue & Exclude<...> : [...] extends [...] ? Exclude<...
Self>) { return
type Self: CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>
draft ?
draft: ({ name: string | undefined; } & CoMap) | undefined
var Object: ObjectConstructor
Provides functionality common to all JavaScript objects.Object.ObjectConstructor.keys(o: {}): string[] (+1 overload)
Returns the names of the enumerable string properties and methods of an object.keys(draft.
draft: { name: string | undefined; } & CoMap
_edits).
CoMap._edits: { name?: LastAndAllCoMapEdits<string | undefined> | undefined; }
Array<string>.length: number
Gets or sets the length of the array. This is a number one higher than the highest index in the array.length : false; }, }));
In the UI, you can choose how you want to show the draft indicator.
// DraftIndicator.tsx export function
function DraftIndicator(): React.JSX.Element | undefined
DraftIndicator() { const {me } =
const me: ({ root: { draft: { name: string | undefined; } & CoMap; } & { draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap; } & { root: ({ draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap) | null; profile: ({ ...; } & CoMap) | null; } & Account) | null | undefined
useAccount(
useAccount<AccountSchema<{ root: CoMapSchema<{ draft: WithHelpers<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): { ...; }; hasChanges(draft?: Loaded<CoMapSchema<...>>): number | false; }>; }>; profile: CoMapSchema<...>; }>, { ...; }>(AccountSchema: AccountSchema<...>, options?: { ...; } | undefined): { ...; } (+1 overload)
JazzAccount, {
const JazzAccount: AccountSchema<{ root: CoMapSchema<{ draft: WithHelpers<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): { ...; }; hasChanges(draft?: Loaded<CoMapSchema<...>>): number | false; }>; }>; profile: CoMapSchema<...>; }>
resolve: {
resolve?: RefsToResolve<{ root: ({ draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap) | null; profile: ({ ...; } & CoMap) | null; } & Account, 10, []> | undefined
root: {
root?: RefsToResolve<{ draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap, 10, [0]> | undefined
draft: true } }, }); if (
draft?: RefsToResolve<{ name: string | undefined; } & CoMap, 10, [0, 0]> | undefined
DraftBubbleTeaOrder.
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>, { validate(draft: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): { ...; }; hasChanges(draft?: Loaded<CoMapSchema<...>>): number | false; }>
hasChanges(
function hasChanges(draft?: Loaded<CoMapSchema<{ name: z.ZodOptional<z.z.ZodString>; }>>): number | false
me?.
const me: ({ root: { draft: { name: string | undefined; } & CoMap; } & { draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap; } & { root: ({ draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap) | null; profile: ({ ...; } & CoMap) | null; } & Account) | null | undefined
root.
Account.root: { draft: { name: string | undefined; } & CoMap; } & { draft: ({ name: string | undefined; } & CoMap) | null; } & CoMap
draft)) { return ( <
draft: ({ name: string | undefined; } & CoMap) | undefined
JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p>You have a draft</JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p> ); } }
A more subtle way is to show a small dot next to the Create button.
Handling different types of data
Forms can be more complex than just a single string field, so we've put together an example app that shows you how to handle single-select, multi-select, date, and boolean inputs.
// schema.ts export const
BubbleTeaOrder =
const BubbleTeaOrder: CoMapSchema<{ baseTea: z.z.ZodLiteral<"Black" | "Oolong" | "Jasmine" | "Thai">; addOns: CoListSchema<z.z.ZodLiteral<"Pearl" | "Lychee jelly" | "Red bean" | "Brown sugar" | "Taro">>; deliveryDate: z.z.ZodDate; withMilk: z.z.ZodBoolean; instructions: z.ZodOptional<...>; }>
import co
co.map({
map<{ baseTea: z.z.ZodLiteral<"Black" | "Oolong" | "Jasmine" | "Thai">; addOns: CoListSchema<z.z.ZodLiteral<"Pearl" | "Lychee jelly" | "Red bean" | "Brown sugar" | "Taro">>; deliveryDate: z.z.ZodDate; withMilk: z.z.ZodBoolean; instructions: z.ZodOptional<...>; }>(shape: { baseTea: z.z.ZodLiteral<"Black" | "Oolong" | "Jasmine" | "Thai">; addOns: CoListSchema<z.z.ZodLiteral<"Pearl" | "Lychee jelly" | "Red bean" | "Brown sugar" | "Taro">>; deliveryDate: z.z.ZodDate; withMilk: z.z.ZodBoolean; instructions: z.ZodOptional<...>; }): CoMapSchema<...> export map
baseTea: z.z.ZodLiteral<"Black" | "Oolong" | "Jasmine" | "Thai">
baseTea:import z
z.literal(["Black", "Oolong", "Jasmine", "Thai"]),
literal<["Black", "Oolong", "Jasmine", "Thai"]>(value: ["Black", "Oolong", "Jasmine", "Thai"], params?: string | z.z.core.$ZodLiteralParams): z.z.ZodLiteral<"Black" | "Oolong" | "Jasmine" | "Thai"> (+1 overload) export literal
addOns: CoListSchema<z.z.ZodLiteral<"Pearl" | "Lychee jelly" | "Red bean" | "Brown sugar" | "Taro">>
addOns:const ListOfBubbleTeaAddOns: CoListSchema<z.z.ZodLiteral<"Pearl" | "Lychee jelly" | "Red bean" | "Brown sugar" | "Taro">>
ListOfBubbleTeaAddOns,deliveryDate: z.z.ZodDate
deliveryDate:import z
z.date(),
function date(params?: string | z.z.core.$ZodDateParams): z.z.ZodDate export date
withMilk: z.z.ZodBoolean
withMilk:import z
z.boolean(),
function boolean(params?: string | z.z.core.$ZodBooleanParams): z.z.ZodBoolean export boolean
instructions: z.ZodOptional<z.z.ZodString>
instructions:import z
z.optional(
optional<z.z.ZodString>(innerType: z.z.ZodString): z.ZodOptional<z.z.ZodString> export optional
import z
z.string()), });
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString export string