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:

  1. An update form that saves changes as you make them, removing the need for a save button.
  2. A create form that autosaves your changes into a draft, so you can come back to it later.

See the full example here.

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 
const BubbleTeaOrder: CoMapSchema<{
    name: z.z.ZodString;
}>
BubbleTeaOrder
= import coco.
map<{
    name: z.z.ZodString;
}>(shape: {
    name: z.z.ZodString;
}): CoMapSchema<{
    name: z.z.ZodString;
}>
export map
map
({
name: z.z.ZodStringname: import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString
export string
string
(),
}); export const
const DraftBubbleTeaOrder: CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
DraftBubbleTeaOrder
= import coco.
map<{
    name: z.ZodOptional<z.z.ZodString>;
}>(shape: {
    name: z.ZodOptional<z.z.ZodString>;
}): CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
export map
map
({
name: z.ZodOptional<z.z.ZodString>name: import zz.
optional<z.z.ZodString>(innerType: z.z.ZodString): z.ZodOptional<z.z.ZodString>
export optional
optional
(import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString
export string
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 
function OrderForm({ order, onSave, }: {
    order: Loaded<typeof BubbleTeaOrder> | Loaded<typeof DraftBubbleTeaOrder>;
    onSave?: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element
OrderForm
({
order: ({
    name: string;
} & CoMap) | ({
    name: string | undefined;
} & CoMap)
order
,
onSave: ((e: React.FormEvent<HTMLFormElement>) => void) | undefinedonSave, }: {
order: ({
    name: string;
} & CoMap) | ({
    name: string | undefined;
} & CoMap)
order
:
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<...
Loaded
<typeof
const BubbleTeaOrder: CoMapSchema<{
    name: z.z.ZodString;
}>
BubbleTeaOrder
> |
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<...
Loaded
<typeof
const DraftBubbleTeaOrder: CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
DraftBubbleTeaOrder
>;
onSave?: ((e: React.FormEvent<HTMLFormElement>) => void) | undefinedonSave?: (e: React.FormEvent<HTMLFormElement>e: React.interface React.FormEvent<T = Element>FormEvent<HTMLFormElement>) => void; }) { return ( <JSX.IntrinsicElements.form: React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>form React.DOMAttributes<HTMLFormElement>.onSubmit?: React.FormEventHandler<HTMLFormElement> | undefinedonSubmit={onSave: ((e: React.FormEvent<HTMLFormElement>) => void) | undefinedonSave}> <JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>label> Name <JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>input React.InputHTMLAttributes<HTMLInputElement>.type?: React.HTMLInputTypeAttribute | undefinedtype="text" React.InputHTMLAttributes<HTMLInputElement>.value?: string | number | readonly string[] | undefinedvalue={
order: ({
    name: string;
} & CoMap) | ({
    name: string | undefined;
} & CoMap)
order
.name: string | undefinedname}
React.InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefinedonChange={(e: React.ChangeEvent<HTMLInputElement>e) => (
order: ({
    name: string;
} & CoMap) | ({
    name: string | undefined;
} & CoMap)
order
.name: string | undefinedname = e: React.ChangeEvent<HTMLInputElement>e.React.ChangeEvent<HTMLInputElement>.target: EventTarget & HTMLInputElementtarget.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 | undefinedrequired /> </JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>label> {onSave: ((e: React.FormEvent<HTMLFormElement>) => void) | undefinedonSave && <JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.ButtonHTMLAttributes<HTMLButtonElement>.type?: "button" | "reset" | "submit" | undefinedtype="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 
function EditOrder(props: {
    id: string;
}): React.JSX.Element | undefined
EditOrder
(
props: {
    id: string;
}
props
: { id: stringid: string }) {
const
const order: ({
    name: string;
} & CoMap) | null | undefined
order
=
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
useCoState
(
const BubbleTeaOrder: CoMapSchema<{
    name: z.z.ZodString;
}>
BubbleTeaOrder
,
props: {
    id: string;
}
props
.id: stringid);
if (!
const order: ({
    name: string;
} & CoMap) | null | undefined
order
) return;
return <
function OrderForm({ order, onSave, }: {
    order: Loaded<typeof BubbleTeaOrder> | Loaded<typeof DraftBubbleTeaOrder>;
    onSave?: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element
OrderForm
order: ({
    name: string;
} & CoMap) | ({
    name: string | undefined;
} & CoMap)
order
={
const order: {
    name: string;
} & CoMap
order
} />;
}

Writing the create form

For the create form, we need to:

  1. Create a draft order.
  2. Edit the draft order.
  3. 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 | undefinedCreateOrder() {
  const { 
const me: NonNullable<Account | ({
    [x: string]: any;
} & Account) | null>
me
} =
useAccount<AccountClass<Account> | AnyAccountSchema>(AccountSchema?: AccountClass<Account> | AnyAccountSchema | undefined): {
    ...;
} (+1 overload)
useAccount
();
const [
const draft: ({
    name: string | undefined;
} & CoMap) | undefined
draft
,
const setDraft: React.Dispatch<React.SetStateAction<({
    name: string | undefined;
} & CoMap) | undefined>>
setDraft
] =
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.
@version16.8.0@see{@link https://react.dev/reference/react/useState}
useState
<
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<...
Loaded
<typeof
const DraftBubbleTeaOrder: CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
DraftBubbleTeaOrder
>>();
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
Accepts a function that contains imperative, possibly effectful code.
@parameffect Imperative function that can return a cleanup function@paramdeps If present, effect will only activate if the values in the list change.@version16.8.0@see{@link https://react.dev/reference/react/useEffect}
useEffect
(() => {
const setDraft: (value: React.SetStateAction<({
    name: string | undefined;
} & CoMap) | undefined>) => void
setDraft
(
const DraftBubbleTeaOrder: CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
DraftBubbleTeaOrder
.
create: (init: {
    name?: string | undefined;
}, options?: {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | Account | Group) => {
    ...;
} & CoMap
create
({}));
}, [
const me: NonNullable<Account | ({
    [x: string]: any;
} & Account) | null>
me
?.Account.id: string
@categoryContent
id
]);
const const onSave: (e: React.FormEvent<HTMLFormElement>) => voidonSave = (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(): voidpreventDefault(); if (!
const draft: ({
    name: string | undefined;
} & CoMap) | undefined
draft
|| !
const draft: {
    name: string | undefined;
} & CoMap
draft
.name: string | undefinedname) return;
const
const order: {
    name: string;
} & CoMap
order
=
const draft: {
    name: string | undefined;
} & CoMap
draft
as
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<...
Loaded
<typeof
const BubbleTeaOrder: CoMapSchema<{
    name: z.z.ZodString;
}>
BubbleTeaOrder
>; // TODO: this should narrow correctly
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 ```
@see[source](https://github.com/nodejs/node/blob/v20.11.1/lib/console.js)
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.
@sincev0.1.100
log
("Order created:",
const order: {
    name: string;
} & CoMap
order
);
}; if (!
const draft: ({
    name: string | undefined;
} & CoMap) | undefined
draft
) return;
return <
function OrderForm({ order, onSave, }: {
    order: Loaded<typeof BubbleTeaOrder> | Loaded<typeof DraftBubbleTeaOrder>;
    onSave?: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element
OrderForm
order: ({
    name: string | undefined;
} & CoMap) | ({
    name: string;
} & CoMap)
order
={
const draft: {
    name: string | undefined;
} & CoMap
draft
} onSave?: ((e: React.FormEvent<HTMLFormElement>) => void) | undefinedonSave={const onSave: (e: React.FormEvent<HTMLFormElement>) => voidonSave} />;
}

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 
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>, {
    validate(draft: Loaded<CoMapSchema<{
        name: z.ZodOptional<z.z.ZodString>;
    }>>): {
        ...;
    };
}>
DraftBubbleTeaOrder
= import coco.
map<{
    name: z.ZodOptional<z.z.ZodString>;
}>(shape: {
    name: z.ZodOptional<z.z.ZodString>;
}): CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
export map
map
({
name: z.ZodOptional<z.z.ZodString>name: import zz.
optional<z.z.ZodString>(innerType: z.z.ZodString): z.ZodOptional<z.z.ZodString>
export optional
optional
(import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString
export string
string
()),
}).
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<...>
withHelpers
((
type Self: CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
Self
) => ({
function validate(draft: Loaded<typeof Self>): {
    errors: string[];
}
validate
(
draft: {
    name: string | undefined;
} & CoMap
draft
:
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<...
Loaded
<typeof
type Self: CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
Self
>) {
const const errors: string[]errors: string[] = []; if (!
draft: {
    name: string | undefined;
} & CoMap
draft
.name: string | undefinedname) {
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.
@paramitems New elements to add to 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 | undefinedCreateOrder() {
  const { 
const me: NonNullable<Account | ({
    [x: string]: any;
} & Account) | null>
me
} =
useAccount<AccountClass<Account> | AnyAccountSchema>(AccountSchema?: AccountClass<Account> | AnyAccountSchema | undefined): {
    ...;
} (+1 overload)
useAccount
();
const [
const draft: ({
    name: string | undefined;
} & CoMap) | undefined
draft
,
const setDraft: React.Dispatch<React.SetStateAction<({
    name: string | undefined;
} & CoMap) | undefined>>
setDraft
] =
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.
@version16.8.0@see{@link https://react.dev/reference/react/useState}
useState
<
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<...
Loaded
<typeof
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>, {
    validate(draft: Loaded<CoMapSchema<{
        name: z.ZodOptional<z.z.ZodString>;
    }>>): {
        ...;
    };
}>
DraftBubbleTeaOrder
>>();
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
Accepts a function that contains imperative, possibly effectful code.
@parameffect Imperative function that can return a cleanup function@paramdeps If present, effect will only activate if the values in the list change.@version16.8.0@see{@link https://react.dev/reference/react/useEffect}
useEffect
(() => {
const setDraft: (value: React.SetStateAction<({
    name: string | undefined;
} & CoMap) | undefined>) => void
setDraft
(
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>, {
    validate(draft: Loaded<CoMapSchema<{
        name: z.ZodOptional<z.z.ZodString>;
    }>>): {
        ...;
    };
}>
DraftBubbleTeaOrder
.
create: (init: {
    name?: string | undefined;
}, options?: {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | Account | Group) => {
    ...;
} & CoMap
create
({}));
}, [
const me: NonNullable<Account | ({
    [x: string]: any;
} & Account) | null>
me
?.Account.id: string
@categoryContent
id
]);
const const onSave: (e: React.FormEvent<HTMLFormElement>) => voidonSave = (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(): voidpreventDefault(); if (!
const draft: ({
    name: string | undefined;
} & CoMap) | undefined
draft
) return;
const
const validation: {
    errors: string[];
}
validation
=
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>, {
    validate(draft: Loaded<CoMapSchema<{
        name: z.ZodOptional<z.z.ZodString>;
    }>>): {
        ...;
    };
}>
DraftBubbleTeaOrder
.
function validate(draft: Loaded<CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>>): {
    errors: string[];
}
validate
(
const draft: {
    name: string | undefined;
} & CoMap
draft
);
if (
const validation: {
    errors: string[];
}
validation
.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 ```
@see[source](https://github.com/nodejs/node/blob/v20.11.1/lib/console.js)
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.
@sincev0.1.100
log
(
const validation: {
    errors: string[];
}
validation
.errors: string[]errors);
return; } const
const order: {
    name: string;
} & CoMap
order
=
const draft: {
    name: string | undefined;
} & CoMap
draft
as
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<...
Loaded
<typeof
const BubbleTeaOrder: CoMapSchema<{
    name: z.z.ZodString;
}>
BubbleTeaOrder
>;
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 ```
@see[source](https://github.com/nodejs/node/blob/v20.11.1/lib/console.js)
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.
@sincev0.1.100
log
("Order created:",
const order: {
    name: string;
} & CoMap
order
);
}; if (!
const draft: ({
    name: string | undefined;
} & CoMap) | undefined
draft
) return;
return <
function OrderForm({ order, onSave, }: {
    order: Loaded<typeof BubbleTeaOrder> | Loaded<typeof DraftBubbleTeaOrder>;
    onSave?: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element
OrderForm
order: ({
    name: string | undefined;
} & CoMap) | ({
    name: string;
} & CoMap)
order
={
const draft: {
    name: string | undefined;
} & CoMap
draft
} onSave?: ((e: React.FormEvent<HTMLFormElement>) => void) | undefinedonSave={const onSave: (e: React.FormEvent<HTMLFormElement>) => voidonSave} />;
}

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 
const BubbleTeaOrder: CoMapSchema<{
    name: z.z.ZodString;
}>
BubbleTeaOrder
= import coco.
map<{
    name: z.z.ZodString;
}>(shape: {
    name: z.z.ZodString;
}): CoMapSchema<{
    name: z.z.ZodString;
}>
export map
map
({
name: z.z.ZodStringname: import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString
export string
string
(),
}); export const
const DraftBubbleTeaOrder: CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
DraftBubbleTeaOrder
= import coco.
map<{
    name: z.ZodOptional<z.z.ZodString>;
}>(shape: {
    name: z.ZodOptional<z.z.ZodString>;
}): CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
export map
map
({
name: z.ZodOptional<z.z.ZodString>name: import zz.
optional<z.z.ZodString>(innerType: z.z.ZodString): z.ZodOptional<z.z.ZodString>
export optional
optional
(import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString
export string
string
()),
}); export const
const AccountRoot: CoMapSchema<{
    draft: CoMapSchema<{
        name: z.ZodOptional<z.z.ZodString>;
    }>;
}>
AccountRoot
= import coco.
map<{
    draft: CoMapSchema<{
        name: z.ZodOptional<z.z.ZodString>;
    }>;
}>(shape: {
    draft: CoMapSchema<{
        name: z.ZodOptional<z.z.ZodString>;
    }>;
}): CoMapSchema<...>
export map
map
({
draft: CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
draft
:
const DraftBubbleTeaOrder: CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
DraftBubbleTeaOrder
,
}); export const
const JazzAccount: AccountSchema<{
    root: CoMapSchema<{
        draft: CoMapSchema<{
            name: z.ZodOptional<z.z.ZodString>;
        }>;
    }>;
    profile: CoMapSchema<{
        name: z.z.ZodString;
    }>;
}>
JazzAccount
= import coco.
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
account
({
root: CoMapSchema<{
    draft: CoMapSchema<{
        name: z.ZodOptional<z.z.ZodString>;
    }>;
}>
root
:
const AccountRoot: CoMapSchema<{
    draft: CoMapSchema<{
        name: z.ZodOptional<z.z.ZodString>;
    }>;
}>
AccountRoot
,
profile: CoMapSchema<{
    name: z.z.ZodString;
}>
profile
: import coco.
map<{
    name: z.z.ZodString;
}>(shape: {
    name: z.z.ZodString;
}): CoMapSchema<{
    name: z.z.ZodString;
}>
export map
map
({ name: z.z.ZodStringname: import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString
export string
string
() }),
}).
function withMigration(migration: (account: {
    root: {
        draft: {
            name: string | undefined;
        } & CoMap;
    } & CoMap;
    profile: {
        name: string;
    } & CoMap;
} & {
    profile: Profile;
} & Account, creationProps?: {
    name: string;
}) => void): AccountSchema<...>
withMigration
((
account: {
    root: {
        draft: {
            name: string | undefined;
        } & CoMap;
    } & CoMap;
    profile: {
        name: string;
    } & CoMap;
} & {
    profile: Profile;
} & Account
account
,
creationProps: {
    name: string;
} | undefined
creationProps
?: { name: stringname: string }) => {
if (
account: {
    root: {
        draft: {
            name: string | undefined;
        } & CoMap;
    } & CoMap;
    profile: {
        name: string;
    } & CoMap;
} & {
    profile: Profile;
} & Account
account
.
Account.root: {
    draft: {
        name: string | undefined;
    } & CoMap;
} & CoMap
root
=== var undefinedundefined) {
const
const draft: {
    name: string | undefined;
} & CoMap
draft
=
const DraftBubbleTeaOrder: CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
DraftBubbleTeaOrder
.
create: (init: {
    name?: string | undefined;
}, options?: {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | Account | Group) => {
    ...;
} & CoMap
create
({});
account: {
    root: {
        draft: {
            name: string | undefined;
        } & CoMap;
    } & CoMap;
    profile: {
        name: string;
    } & CoMap;
} & {
    profile: Profile;
} & Account
account
.
Account.root: {
    draft: {
        name: string | undefined;
    } & CoMap;
} & CoMap
root
=
const AccountRoot: CoMapSchema<{
    draft: CoMapSchema<{
        name: z.ZodOptional<z.z.ZodString>;
    }>;
}>
AccountRoot
.
create: (init: {
    draft: {
        name: string | undefined;
    } & CoMap;
}, options?: {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | Account | Group) => {
    ...;
} & CoMap
create
({
draft: {
    name: string | undefined;
} & CoMap
draft
});
} });

Let's not forget to update the AccountSchema.

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<...
Loaded
} from "jazz-tools"
import { function JazzProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | AnyAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, }: JazzProviderProps<S>): JSX.Element
@categoryContext & Hooks
JazzProvider
} from "jazz-react";
import {
const JazzAccount: AccountSchema<{
    root: CoMapSchema<{
        draft: CoMapSchema<{
            name: ZodOptional<ZodString>;
        }>;
    }>;
    profile: CoMapSchema<{
        name: ZodString;
    }>;
}>
JazzAccount
} from "./schema";
export function
function MyJazzProvider({ children }: {
    children: React.ReactNode;
}): React.JSX.Element
MyJazzProvider
({ children: React.ReactNodechildren }: { children: React.ReactNodechildren: 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.
@see{@link https://react-typescript-cheatsheet.netlify.app/docs/react-types/reactnode/ React TypeScript Cheatsheet}@example```tsx // Typing children type Props = { children: ReactNode } const Component = ({ children }: Props) => <div>{children}</div> <Component>hello</Component> ```@example```tsx // Typing a custom element type Props = { customElement: ReactNode } const Component = ({ customElement }: Props) => <div>{customElement}</div> <Component customElement={<div>hello</div>} /> ```
ReactNode
}) {
return ( <function JazzProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | AnyAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, }: JazzProviderProps<S>): JSX.Element
@categoryContext & Hooks
JazzProvider
sync: SyncConfigsync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com"peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
AccountSchema?: AccountSchema<{
    root: CoMapSchema<{
        draft: CoMapSchema<{
            name: ZodOptional<ZodString>;
        }>;
    }>;
    profile: CoMapSchema<{
        name: ZodString;
    }>;
}> | undefined
AccountSchema
={
const JazzAccount: AccountSchema<{
    root: CoMapSchema<{
        draft: CoMapSchema<{
            name: ZodOptional<ZodString>;
        }>;
    }>;
    profile: CoMapSchema<{
        name: ZodString;
    }>;
}>
JazzAccount
}
> {children: React.ReactNodechildren} </function JazzProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | AnyAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, }: JazzProviderProps<S>): JSX.Element
@categoryContext & Hooks
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 | undefinedCreateOrder() {
  const { 
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
me
} =
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)
useAccount
(
const JazzAccount: AccountSchema<{
    root: CoMapSchema<{
        draft: WithHelpers<CoMapSchema<{
            name: ZodOptional<ZodString>;
        }>, {
            validate(draft: Loaded<CoMapSchema<{
                name: ZodOptional<ZodString>;
            }>>): {
                ...;
            };
        }>;
    }>;
    profile: CoMapSchema<...>;
}>
JazzAccount
, {
resolve?: RefsToResolve<{
    root: ({
        draft: ({
            name: string | undefined;
        } & CoMap) | null;
    } & CoMap) | null;
    profile: ({
        ...;
    } & CoMap) | null;
} & Account, 10, []> | undefined
resolve
: {
root?: RefsToResolve<{
    draft: ({
        name: string | undefined;
    } & CoMap) | null;
} & CoMap, 10, [0]> | undefined
root
: {
draft?: RefsToResolve<{
    name: string | undefined;
} & CoMap, 10, [0, 0]> | undefined
draft
: true } },
}); if (!
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
me
?.
Account.root: ({
    draft: {
        name: string | undefined;
    } & CoMap;
} & {
    draft: ({
        name: string | undefined;
    } & CoMap) | null;
} & CoMap) | undefined
root
) return;
const const onSave: (e: React.FormEvent<HTMLFormElement>) => voidonSave = (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(): voidpreventDefault(); const
const draft: {
    name: string | undefined;
} & CoMap
draft
=
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
me
.
Account.root: {
    draft: {
        name: string | undefined;
    } & CoMap;
} & {
    draft: ({
        name: string | undefined;
    } & CoMap) | null;
} & CoMap
root
.
draft: {
    name: string | undefined;
} & CoMap
draft
;
if (!
const draft: {
    name: string | undefined;
} & CoMap
draft
) return;
const
const validation: {
    errors: string[];
}
validation
=
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{
    name: ZodOptional<ZodString>;
}>, {
    validate(draft: Loaded<CoMapSchema<{
        name: ZodOptional<ZodString>;
    }>>): {
        ...;
    };
}>
DraftBubbleTeaOrder
.
function validate(draft: Loaded<CoMapSchema<{
    name: ZodOptional<ZodString>;
}>>): {
    errors: string[];
}
validate
(
const draft: {
    name: string | undefined;
} & CoMap
draft
);
if (
const validation: {
    errors: string[];
}
validation
.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 ```
@see[source](https://github.com/nodejs/node/blob/v20.11.1/lib/console.js)
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.
@sincev0.1.100
log
(
const validation: {
    errors: string[];
}
validation
.errors: string[]errors);
return; } const
const order: {
    name: string;
} & CoMap
order
=
const draft: {
    name: string | undefined;
} & CoMap
draft
as
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<...
Loaded
<typeof
const BubbleTeaOrder: CoMapSchema<{
    name: ZodString;
}>
BubbleTeaOrder
>;
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 ```
@see[source](https://github.com/nodejs/node/blob/v20.11.1/lib/console.js)
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.
@sincev0.1.100
log
("Order created:",
const order: {
    name: string;
} & CoMap
order
);
// create a new empty draft
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
me
.
Account.root: {
    draft: {
        name: string | undefined;
    } & CoMap;
} & {
    draft: ({
        name: string | undefined;
    } & CoMap) | null;
} & CoMap
root
.
draft: {
    name: string | undefined;
} & CoMap
draft
=
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{
    name: ZodOptional<ZodString>;
}>, {
    validate(draft: Loaded<CoMapSchema<{
        name: ZodOptional<ZodString>;
    }>>): {
        ...;
    };
}>
DraftBubbleTeaOrder
.
create: (init: {
    name?: string | undefined;
}, options?: {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | Account | Group) => {
    ...;
} & CoMap
create
(
{}, ); }; return <
function CreateOrderForm({ id, onSave, }: {
    id: string;
    onSave: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element | undefined
CreateOrderForm
id: stringid={
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
me
.
Account.root: {
    draft: {
        name: string | undefined;
    } & CoMap;
} & {
    draft: ({
        name: string | undefined;
    } & CoMap) | null;
} & CoMap
root
.
draft: {
    name: string | undefined;
} & CoMap
draft
.CoMap.id: string
The ID of this `CoMap`
@categoryContent
id
} onSave: (e: React.FormEvent<HTMLFormElement>) => voidonSave={const onSave: (e: React.FormEvent<HTMLFormElement>) => voidonSave} />
} function
function CreateOrderForm({ id, onSave, }: {
    id: string;
    onSave: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element | undefined
CreateOrderForm
({
id: stringid, onSave: (e: React.FormEvent<HTMLFormElement>) => voidonSave, }: { id: stringid: string onSave: (e: React.FormEvent<HTMLFormElement>) => voidonSave: (e: React.FormEvent<HTMLFormElement>e: React.interface React.FormEvent<T = Element>FormEvent<HTMLFormElement>) => void; }) { const
const draft: ({
    name: string | undefined;
} & CoMap) | null | undefined
draft
=
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
useCoState
(
const DraftBubbleTeaOrder: WithHelpers<CoMapSchema<{
    name: ZodOptional<ZodString>;
}>, {
    validate(draft: Loaded<CoMapSchema<{
        name: ZodOptional<ZodString>;
    }>>): {
        ...;
    };
}>
DraftBubbleTeaOrder
, id: stringid);
if (!
const draft: ({
    name: string | undefined;
} & CoMap) | null | undefined
draft
) return;
return <
function OrderForm({ order, onSave, }: {
    order: Loaded<typeof BubbleTeaOrder> | Loaded<typeof DraftBubbleTeaOrder>;
    onSave?: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element
OrderForm
order: ({
    name: string | undefined;
} & CoMap) | ({
    name: string;
} & CoMap)
order
={
const draft: {
    name: string | undefined;
} & CoMap
draft
} onSave?: ((e: React.FormEvent<HTMLFormElement>) => void) | undefinedonSave={onSave: (e: React.FormEvent<HTMLFormElement>) => voidonSave} />;
}

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 
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;
}>
DraftBubbleTeaOrder
= import coco.
map<{
    name: z.ZodOptional<z.z.ZodString>;
}>(shape: {
    name: z.ZodOptional<z.z.ZodString>;
}): CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
export map
map
({
name: z.ZodOptional<z.z.ZodString>name: import zz.
optional<z.z.ZodString>(innerType: z.z.ZodString): z.ZodOptional<z.z.ZodString>
export optional
optional
(import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString
export string
string
()),
}).
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<...>
withHelpers
((
type Self: CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
Self
) => ({
function validate(draft: Loaded<typeof Self>): {
    errors: string[];
}
validate
(
draft: {
    name: string | undefined;
} & CoMap
draft
:
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<...
Loaded
<typeof
type Self: CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
Self
>) {
const const errors: string[]errors: string[] = []; if (!
draft: {
    name: string | undefined;
} & CoMap
draft
.name: string | undefinedname) {
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.
@paramitems New elements to add to the array.
push
("Plese enter a name.");
} return { errors: string[]errors }; }, function hasChanges(draft?: Loaded<typeof Self>): number | falsehasChanges(
draft: ({
    name: string | undefined;
} & CoMap) | undefined
draft
?:
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<...
Loaded
<typeof
type Self: CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>
Self
>) {
return
draft: ({
    name: string | undefined;
} & CoMap) | undefined
draft
? 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.
@paramo Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
keys
(
draft: {
    name: string | undefined;
} & CoMap
draft
.
CoMap._edits: {
    name?: LastAndAllCoMapEdits<string | undefined> | undefined;
}
@categoryCollaboration
_edits
).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 | undefinedDraftIndicator() {
  const { 
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
me
} =
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)
useAccount
(
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<...>;
}>
JazzAccount
, {
resolve?: RefsToResolve<{
    root: ({
        draft: ({
            name: string | undefined;
        } & CoMap) | null;
    } & CoMap) | null;
    profile: ({
        ...;
    } & CoMap) | null;
} & Account, 10, []> | undefined
resolve
: {
root?: RefsToResolve<{
    draft: ({
        name: string | undefined;
    } & CoMap) | null;
} & CoMap, 10, [0]> | undefined
root
: {
draft?: RefsToResolve<{
    name: string | undefined;
} & CoMap, 10, [0, 0]> | undefined
draft
: true } },
}); if (
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;
}>
DraftBubbleTeaOrder
.
function hasChanges(draft?: Loaded<CoMapSchema<{
    name: z.ZodOptional<z.z.ZodString>;
}>>): number | false
hasChanges
(
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
me
?.
Account.root: {
    draft: {
        name: string | undefined;
    } & CoMap;
} & {
    draft: ({
        name: string | undefined;
    } & CoMap) | null;
} & CoMap
root
.
draft: ({
    name: string | undefined;
} & CoMap) | undefined
draft
)) {
return ( <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.

See the full example here.

// schema.ts
export const 
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<...>;
}>
BubbleTeaOrder
= import coco.
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
map
({
baseTea: z.z.ZodLiteral<"Black" | "Oolong" | "Jasmine" | "Thai">baseTea: import zz.
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
literal
(["Black", "Oolong", "Jasmine", "Thai"]),
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.ZodDatedeliveryDate: import zz.
function date(params?: string | z.z.core.$ZodDateParams): z.z.ZodDate
export date
date
(),
withMilk: z.z.ZodBooleanwithMilk: import zz.
function boolean(params?: string | z.z.core.$ZodBooleanParams): z.z.ZodBoolean
export boolean
boolean
(),
instructions: z.ZodOptional<z.z.ZodString>instructions: import zz.
optional<z.z.ZodString>(innerType: z.z.ZodString): z.ZodOptional<z.z.ZodString>
export optional
optional
(import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString
export string
string
()),
});