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.

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: co.Map<{
    name: z.z.ZodString;
}, unknown, Account | Group>
BubbleTeaOrder
= import coco.
map<{
    name: z.z.ZodString;
}>(shape: {
    name: z.z.ZodString;
}): co.Map<{
    name: z.z.ZodString;
}, unknown, Account | Group>
export map
map
({
name: z.z.ZodStringname: import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString (+1 overload)
export string
string
(),
}); export const
const DraftBubbleTeaOrder: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
= import coco.
map<{
    name: z.ZodOptional<z.z.ZodString>;
}>(shape: {
    name: z.ZodOptional<z.z.ZodString>;
}): co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
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 (+1 overload)
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: co.loaded<typeof BubbleTeaOrder> | co.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
: import coco.
type loaded<T extends CoValueClassOrSchema, 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<...
export loaded
loaded
<typeof
const BubbleTeaOrder: co.Map<{
    name: z.z.ZodString;
}, unknown, Account | Group>
BubbleTeaOrder
> | import coco.
type loaded<T extends CoValueClassOrSchema, 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<...
export loaded
loaded
<typeof
const DraftBubbleTeaOrder: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
>;
onSave?: ((e: React.FormEvent<HTMLFormElement>) => void) | undefinedonSave?: (e: React.FormEvent<HTMLFormElement>e: React.interface React.FormEvent<T = Element>FormEvent<HTMLFormElement>) => void; }) { return ( <React.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}> <React.JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>label> Name <React.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 /> </React.JSX.IntrinsicElements.label: React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>label> {onSave: ((e: React.FormEvent<HTMLFormElement>) => void) | undefinedonSave && <React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.ButtonHTMLAttributes<HTMLButtonElement>.type?: "button" | "reset" | "submit" | undefinedtype="submit">Submit</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>} </React.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<co.Map<{
    name: z.z.ZodString;
}, unknown, Account | Group>, true>(Schema: co.Map<{
    name: z.z.ZodString;
}, unknown, Account | Group>, id: string | undefined, options?: {
    ...;
} | undefined): ({
    ...;
} & CoMap) | ... 1 more ... | undefined
React hook for subscribing to CoValues and handling loading states. This hook provides a convenient way to subscribe to CoValues and automatically handles the subscription lifecycle (subscribe on mount, unsubscribe on unmount). It also supports deep loading of nested CoValues through resolve queries.
@returnsThe loaded CoValue, or `undefined` if loading, or `null` if not found/not accessible@example```tsx // Deep loading with resolve queries const Project = co.map({ name: z.string(), tasks: co.list(Task), owner: TeamMember, }); function ProjectView({ projectId }: { projectId: string }) { const project = useCoState(Project, projectId, { resolve: { tasks: { $each: true }, owner: true, }, }); if (!project) { return project === null ? "Project not found or not accessible" : "Loading project..."; } return ( <div> <h1>{project.name}</h1> <p>Owner: {project.owner.name}</p> <ul> {project.tasks.map((task) => ( <li key={task.id}>{task.title}</li> ))} </ul> </div> ); } ```@example```tsx // Using with optional references and error handling const Task = co.map({ title: z.string(), assignee: co.optional(TeamMember), subtasks: co.list(Task), }); function TaskDetail({ taskId }: { taskId: string }) { const task = useCoState(Task, taskId, { resolve: { assignee: true, subtasks: { $each: { $onError: null } }, }, }); if (!task) { return task === null ? "Task not found or not accessible" : "Loading task..."; } return ( <div> <h2>{task.title}</h2> {task.assignee && <p>Assigned to: {task.assignee.name}</p>} <ul> {task.subtasks.map((subtask, index) => ( subtask ? <li key={subtask.id}>{subtask.title}</li> : <li key={index}>Inaccessible subtask</li> ))} </ul> </div> ); } ``` For more examples, see the [subscription and deep loading](https://jazz.tools/docs/react/using-covalues/subscription-and-loading) documentation.
useCoState
(
const BubbleTeaOrder: co.Map<{
    name: z.z.ZodString;
}, unknown, Account | Group>
BubbleTeaOrder
,
props: {
    id: string;
}
props
.id: stringid);
if (!
const order: ({
    name: string;
} & CoMap) | null | undefined
order
) return;
return <
function OrderForm({ order, onSave, }: {
    order: co.loaded<typeof BubbleTeaOrder> | co.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> | null | undefined
me
} =
useAccount<CoreAccountSchema<z.z.core.$ZodLooseShape> | AccountClass<Account>, true>(AccountSchema?: CoreAccountSchema<z.z.core.$ZodLooseShape> | AccountClass<...> | undefined, options?: {
    ...;
} | undefined): {
    ...;
}
React hook for accessing the current user's account and authentication state. This hook provides access to the current user's account profile and root data, along with authentication utilities. It automatically handles subscription to the user's account data and provides a logout function.
@returnsAn object containing: - `me`: The loaded account data, or `undefined` if loading, or `null` if not authenticated - `agent`: The current agent (anonymous or authenticated user). Can be used as `loadAs` parameter for load and subscribe methods. - `logOut`: Function to log out the current user@example```tsx // Deep loading with resolve queries function ProjectListWithDetails() { const { me } = useAccount(MyAppAccount, { resolve: { profile: true, root: { myProjects: { $each: { tasks: true, }, }, }, }, }); if (!me) { return me === null ? <div>Failed to load your projects</div> : <div>Loading...</div>; } return ( <div> <h1>{me.profile.name}'s projects</h1> <ul> {me.root.myProjects.map((project) => ( <li key={project.id}> {project.name} ({project.tasks.length} tasks) </li> ))} </ul> </div> ); } ```
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
<import coco.
type loaded<T extends CoValueClassOrSchema, 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<...
export loaded
loaded
<typeof
const DraftBubbleTeaOrder: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
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: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
.
CoMapSchema<{ name: ZodOptional<ZodString>; }, unknown, Account | Group>.create: (init: {
    name?: string | undefined;
}, options?: Account | Group | {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | undefined) => {
    ...;
} & CoMap
create
({}));
}, [
const me: NonNullable<Account | ({
    [x: string]: any;
} & Account) | null> | null | undefined
me
?.Account.id: string | undefined
@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 import coco.
type loaded<T extends CoValueClassOrSchema, 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<...
export loaded
loaded
<typeof
const BubbleTeaOrder: co.Map<{
    name: z.z.ZodString;
}, unknown, Account | Group>
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 (+2 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
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: co.loaded<typeof BubbleTeaOrder> | co.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 validateDraftOrder helper.

// schema.ts
export const 
const DraftBubbleTeaOrder: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
= import coco.
map<{
    name: z.ZodOptional<z.z.ZodString>;
}>(shape: {
    name: z.ZodOptional<z.z.ZodString>;
}): co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
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 (+1 overload)
export string
string
()),
}); export function
function validateDraftOrder(draft: co.loaded<typeof DraftBubbleTeaOrder>): {
    errors: string[];
}
validateDraftOrder
(
draft: {
    name: string | undefined;
} & CoMap
draft
: import coco.
type loaded<T extends CoValueClassOrSchema, 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<...
export loaded
loaded
<typeof
const DraftBubbleTeaOrder: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
>) {
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> | null | undefined
me
} =
useAccount<CoreAccountSchema<z.z.core.$ZodLooseShape> | AccountClass<Account>, true>(AccountSchema?: CoreAccountSchema<z.z.core.$ZodLooseShape> | AccountClass<...> | undefined, options?: {
    ...;
} | undefined): {
    ...;
}
React hook for accessing the current user's account and authentication state. This hook provides access to the current user's account profile and root data, along with authentication utilities. It automatically handles subscription to the user's account data and provides a logout function.
@returnsAn object containing: - `me`: The loaded account data, or `undefined` if loading, or `null` if not authenticated - `agent`: The current agent (anonymous or authenticated user). Can be used as `loadAs` parameter for load and subscribe methods. - `logOut`: Function to log out the current user@example```tsx // Deep loading with resolve queries function ProjectListWithDetails() { const { me } = useAccount(MyAppAccount, { resolve: { profile: true, root: { myProjects: { $each: { tasks: true, }, }, }, }, }); if (!me) { return me === null ? <div>Failed to load your projects</div> : <div>Loading...</div>; } return ( <div> <h1>{me.profile.name}'s projects</h1> <ul> {me.root.myProjects.map((project) => ( <li key={project.id}> {project.name} ({project.tasks.length} tasks) </li> ))} </ul> </div> ); } ```
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
<import coco.
type loaded<T extends CoValueClassOrSchema, 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<...
export loaded
loaded
<typeof
const DraftBubbleTeaOrder: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
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: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
.
CoMapSchema<{ name: ZodOptional<ZodString>; }, unknown, Account | Group>.create: (init: {
    name?: string | undefined;
}, options?: Account | Group | {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | undefined) => {
    ...;
} & CoMap
create
({}));
}, [
const me: NonNullable<Account | ({
    [x: string]: any;
} & Account) | null> | null | undefined
me
?.Account.id: string | undefined
@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
=
function validateDraftOrder(draft: co.loaded<typeof DraftBubbleTeaOrder>): {
    errors: string[];
}
validateDraftOrder
(
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 (+2 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
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 import coco.
type loaded<T extends CoValueClassOrSchema, 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<...
export loaded
loaded
<typeof
const BubbleTeaOrder: co.Map<{
    name: z.z.ZodString;
}, unknown, Account | Group>
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 (+2 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
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: co.loaded<typeof BubbleTeaOrder> | co.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: co.Map<{
    name: z.z.ZodString;
}, unknown, Account | Group>
BubbleTeaOrder
= import coco.
map<{
    name: z.z.ZodString;
}>(shape: {
    name: z.z.ZodString;
}): co.Map<{
    name: z.z.ZodString;
}, unknown, Account | Group>
export map
map
({
name: z.z.ZodStringname: import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString (+1 overload)
export string
string
(),
}); export const
const DraftBubbleTeaOrder: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
= import coco.
map<{
    name: z.ZodOptional<z.z.ZodString>;
}>(shape: {
    name: z.ZodOptional<z.z.ZodString>;
}): co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
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 (+1 overload)
export string
string
()),
}); export const
const AccountRoot: co.Map<{
    draft: co.Map<{
        name: z.ZodOptional<z.z.ZodString>;
    }, unknown, Account | Group>;
}, unknown, Account | Group>
AccountRoot
= import coco.
map<{
    draft: co.Map<{
        name: z.ZodOptional<z.z.ZodString>;
    }, unknown, Account | Group>;
}>(shape: {
    draft: co.Map<{
        name: z.ZodOptional<z.z.ZodString>;
    }, unknown, Account | Group>;
}): co.Map<...>
export map
map
({
draft: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
draft
:
const DraftBubbleTeaOrder: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
,
}); export const
const JazzAccount: co.Account<{
    root: co.Map<{
        draft: co.Map<{
            name: z.ZodOptional<z.z.ZodString>;
        }, unknown, Account | Group>;
    }, unknown, Account | Group>;
    profile: co.Map<{
        ...;
    }, unknown, Account | Group>;
}>
JazzAccount
= import coco.
account<{
    root: co.Map<{
        draft: co.Map<{
            name: z.ZodOptional<z.z.ZodString>;
        }, unknown, Account | Group>;
    }, unknown, Account | Group>;
    profile: co.Map<...>;
}>(shape?: {
    root: co.Map<{
        draft: co.Map<{
            name: z.ZodOptional<z.z.ZodString>;
        }, unknown, Account | Group>;
    }, unknown, Account | Group>;
    profile: co.Map<...>;
} | undefined): co.Account<...>
export account
Defines a collaborative account schema for Jazz applications. Creates an account schema that represents a user account with profile and root data. Accounts are the primary way to identify and manage users in Jazz applications.
@templateShape - The shape of the account schema extending BaseAccountShape@paramshape - The account schema shape. Defaults to a basic profile with name, inbox, and inboxInvite fields, plus an empty root object.@example```typescript // Basic account with default profile const BasicAccount = co.account(); // Custom account with specific profile and root structure const JazzAccount = co.account({ profile: co.profile({ name: z.string(), avatar: z.optional(z.string()), }), root: co.map({ organizations: co.list(Organization), draftOrganization: DraftOrganization, }), }).withMigration(async (account) => { // Migration logic for existing accounts if (account.profile === undefined) { const group = Group.create(); account.profile = co.profile().create( { name: getRandomUsername() }, group ); group.addMember("everyone", "reader"); } }); ```
account
({
root: co.Map<{
    draft: co.Map<{
        name: z.ZodOptional<z.z.ZodString>;
    }, unknown, Account | Group>;
}, unknown, Account | Group>
root
:
const AccountRoot: co.Map<{
    draft: co.Map<{
        name: z.ZodOptional<z.z.ZodString>;
    }, unknown, Account | Group>;
}, unknown, Account | Group>
AccountRoot
,
profile: co.Map<{
    name: z.z.ZodString;
}, unknown, Account | Group>
profile
: import coco.
map<{
    name: z.z.ZodString;
}>(shape: {
    name: z.z.ZodString;
}): co.Map<{
    name: z.z.ZodString;
}, unknown, Account | Group>
export map
map
({ name: z.z.ZodStringname: import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString (+1 overload)
export string
string
() }),
}).
AccountSchema<{ root: CoMapSchema<{ draft: CoMapSchema<{ name: ZodOptional<ZodString>; }, unknown, Account | Group>; }, unknown, Account | Group>; profile: CoMapSchema<...>; }>.withMigration(migration: (account: {
    root: ({
        draft: ({
            name: string | undefined;
        } & CoMap) | null;
    } & CoMap) | null;
    profile: ({
        name: string;
    } & CoMap) | null;
} & Account, creationProps?: {
    name: string;
}) => void): co.Account<...>
withMigration
((
account: {
    root: ({
        draft: ({
            name: string | undefined;
        } & CoMap) | null;
    } & CoMap) | null;
    profile: ({
        name: string;
    } & CoMap) | null;
} & Account
account
,
creationProps: {
    name: string;
} | undefined
creationProps
?: { name: stringname: string }) => {
if (
account: {
    root: ({
        draft: ({
            name: string | undefined;
        } & CoMap) | null;
    } & CoMap) | null;
    profile: ({
        name: string;
    } & CoMap) | null;
} & Account
account
.
Account.root: ({
    draft: ({
        name: string | undefined;
    } & CoMap) | null;
} & CoMap) | null
root
=== var undefinedundefined) {
const
const draft: {
    name: string | undefined;
} & CoMap
draft
=
const DraftBubbleTeaOrder: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
.
CoMapSchema<{ name: ZodOptional<ZodString>; }, unknown, Account | Group>.create: (init: {
    name?: string | undefined;
}, options?: Account | Group | {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | undefined) => {
    ...;
} & CoMap
create
({});
account: {
    root: ({
        draft: ({
            name: string | undefined;
        } & CoMap) | null;
    } & CoMap) | null;
    profile: ({
        name: string;
    } & CoMap) | null;
} & Account
account
.
Account.root: ({
    draft: ({
        name: string | undefined;
    } & CoMap) | null;
} & CoMap) | null
root
=
const AccountRoot: co.Map<{
    draft: co.Map<{
        name: z.ZodOptional<z.z.ZodString>;
    }, unknown, Account | Group>;
}, unknown, Account | Group>
AccountRoot
.
CoMapSchema<{ draft: CoMapSchema<{ name: ZodOptional<ZodString>; }, unknown, Account | Group>; }, unknown, Account | Group>.create: (init: {
    draft: ({
        name: string | undefined;
    } & CoMap) | {
        name?: string | undefined;
    };
}, options?: Account | Group | {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | undefined) => {
    ...;
} & CoMap
create
({
draft: ({
    name: string | undefined;
} & CoMap) | {
    name?: string | undefined;
}
draft
});
} });

Let's not forget to update the AccountSchema.

import { function JazzReactProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | CoreAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, enableSSR, }: JazzProviderProps<S>): JSX.Element
@categoryContext & Hooks
JazzReactProvider
} from "jazz-tools/react";
import {
const JazzAccount: AccountSchema<{
    root: CoMapSchema<{
        draft: CoMapSchema<{
            name: ZodOptional<ZodString>;
        }, unknown, Account | Group>;
    }, unknown, Account | Group>;
    profile: CoMapSchema<...>;
}>
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 | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.ReactPortal | Promise<...> | 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 JazzReactProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | CoreAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, enableSSR, }: JazzProviderProps<S>): JSX.Element
@categoryContext & Hooks
JazzReactProvider
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>;
        }, unknown, Account | Group>;
    }, unknown, Account | Group>;
    profile: CoMapSchema<...>;
}> | undefined
AccountSchema
={
const JazzAccount: AccountSchema<{
    root: CoMapSchema<{
        draft: CoMapSchema<{
            name: ZodOptional<ZodString>;
        }, unknown, Account | Group>;
    }, unknown, Account | Group>;
    profile: CoMapSchema<...>;
}>
JazzAccount
}
> {children: React.ReactNodechildren} </function JazzReactProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | CoreAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, enableSSR, }: JazzProviderProps<S>): JSX.Element
@categoryContext & Hooks
JazzReactProvider
>
); }

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<co.Account<{
    root: co.Map<{
        draft: co.Map<{
            name: ZodOptional<ZodString>;
        }, unknown, Account | Group>;
    }, unknown, Account | Group>;
    profile: co.Map<...>;
}>, {
    ...;
}>(AccountSchema?: co.Account<...> | undefined, options?: {
    ...;
} | undefined): {
    ...;
}
React hook for accessing the current user's account and authentication state. This hook provides access to the current user's account profile and root data, along with authentication utilities. It automatically handles subscription to the user's account data and provides a logout function.
@returnsAn object containing: - `me`: The loaded account data, or `undefined` if loading, or `null` if not authenticated - `agent`: The current agent (anonymous or authenticated user). Can be used as `loadAs` parameter for load and subscribe methods. - `logOut`: Function to log out the current user@example```tsx // Deep loading with resolve queries function ProjectListWithDetails() { const { me } = useAccount(MyAppAccount, { resolve: { profile: true, root: { myProjects: { $each: { tasks: true, }, }, }, }, }); if (!me) { return me === null ? <div>Failed to load your projects</div> : <div>Loading...</div>; } return ( <div> <h1>{me.profile.name}'s projects</h1> <ul> {me.root.myProjects.map((project) => ( <li key={project.id}> {project.name} ({project.tasks.length} tasks) </li> ))} </ul> </div> ); } ```
useAccount
(
const JazzAccount: co.Account<{
    root: co.Map<{
        draft: co.Map<{
            name: ZodOptional<ZodString>;
        }, unknown, Account | Group>;
    }, unknown, Account | Group>;
    profile: co.Map<...>;
}>
JazzAccount
, {
resolve?: RefsToResolve<{
    root: ({
        draft: ({
            name: string | undefined;
        } & CoMap) | null;
    } & CoMap) | null;
    profile: ({
        ...;
    } & CoMap) | null;
} & Account, 10, []> | undefined
Resolve query to specify which nested CoValues to load from the account
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
=
function validateDraftOrder(draft: co.loaded<typeof DraftBubbleTeaOrder>): {
    errors: string[];
}
validateDraftOrder
(
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 (+2 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
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 import coco.
type loaded<T extends CoValueClassOrSchema, 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<...
export loaded
loaded
<typeof
const BubbleTeaOrder: co.Map<{
    name: ZodString;
}, unknown, Account | Group>
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 (+2 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
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: co.Map<{
    name: ZodOptional<ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
.
CoMapSchema<{ name: ZodOptional<ZodString>; }, unknown, Account | Group>.create: (init: {
    name?: string | undefined;
}, options?: Account | Group | {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | undefined) => {
    ...;
} & 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<co.Map<{
    name: ZodOptional<ZodString>;
}, unknown, Account | Group>, true>(Schema: co.Map<{
    name: ZodOptional<ZodString>;
}, unknown, Account | Group>, id: string | undefined, options?: {
    ...;
} | undefined): ({
    ...;
} & CoMap) | ... 1 more ... | undefined
React hook for subscribing to CoValues and handling loading states. This hook provides a convenient way to subscribe to CoValues and automatically handles the subscription lifecycle (subscribe on mount, unsubscribe on unmount). It also supports deep loading of nested CoValues through resolve queries.
@returnsThe loaded CoValue, or `undefined` if loading, or `null` if not found/not accessible@example```tsx // Deep loading with resolve queries const Project = co.map({ name: z.string(), tasks: co.list(Task), owner: TeamMember, }); function ProjectView({ projectId }: { projectId: string }) { const project = useCoState(Project, projectId, { resolve: { tasks: { $each: true }, owner: true, }, }); if (!project) { return project === null ? "Project not found or not accessible" : "Loading project..."; } return ( <div> <h1>{project.name}</h1> <p>Owner: {project.owner.name}</p> <ul> {project.tasks.map((task) => ( <li key={task.id}>{task.title}</li> ))} </ul> </div> ); } ```@example```tsx // Using with optional references and error handling const Task = co.map({ title: z.string(), assignee: co.optional(TeamMember), subtasks: co.list(Task), }); function TaskDetail({ taskId }: { taskId: string }) { const task = useCoState(Task, taskId, { resolve: { assignee: true, subtasks: { $each: { $onError: null } }, }, }); if (!task) { return task === null ? "Task not found or not accessible" : "Loading task..."; } return ( <div> <h2>{task.title}</h2> {task.assignee && <p>Assigned to: {task.assignee.name}</p>} <ul> {task.subtasks.map((subtask, index) => ( subtask ? <li key={subtask.id}>{subtask.title}</li> : <li key={index}>Inaccessible subtask</li> ))} </ul> </div> ); } ``` For more examples, see the [subscription and deep loading](https://jazz.tools/docs/react/using-covalues/subscription-and-loading) documentation.
useCoState
(
const DraftBubbleTeaOrder: co.Map<{
    name: ZodOptional<ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
, id: stringid);
if (!
const draft: ({
    name: string | undefined;
} & CoMap) | null | undefined
draft
) return;
return <
function OrderForm({ order, onSave, }: {
    order: co.loaded<typeof BubbleTeaOrder> | co.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: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
= import coco.
map<{
    name: z.ZodOptional<z.z.ZodString>;
}>(shape: {
    name: z.ZodOptional<z.z.ZodString>;
}): co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
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 (+1 overload)
export string
string
()),
}); export function
function validateDraftOrder(draft: co.loaded<typeof DraftBubbleTeaOrder>): {
    errors: string[];
}
validateDraftOrder
(
draft: {
    name: string | undefined;
} & CoMap
draft
: import coco.
type loaded<T extends CoValueClassOrSchema, 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<...
export loaded
loaded
<typeof
const DraftBubbleTeaOrder: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
>) {
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 }; }; export function function hasChanges(draft?: co.loaded<typeof DraftBubbleTeaOrder>): number | falsehasChanges(
draft: ({
    name: string | undefined;
} & CoMap) | undefined
draft
?: import coco.
type loaded<T extends CoValueClassOrSchema, 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<...
export loaded
loaded
<typeof
const DraftBubbleTeaOrder: co.Map<{
    name: z.ZodOptional<z.z.ZodString>;
}, unknown, Account | Group>
DraftBubbleTeaOrder
>) {
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<co.Account<{
    root: co.Map<{
        draft: co.Map<{
            name: z.ZodOptional<z.z.ZodString>;
        }, unknown, Account | Group>;
    }, unknown, Account | Group>;
    profile: co.Map<...>;
}>, {
    ...;
}>(AccountSchema?: co.Account<...> | undefined, options?: {
    ...;
} | undefined): {
    ...;
}
React hook for accessing the current user's account and authentication state. This hook provides access to the current user's account profile and root data, along with authentication utilities. It automatically handles subscription to the user's account data and provides a logout function.
@returnsAn object containing: - `me`: The loaded account data, or `undefined` if loading, or `null` if not authenticated - `agent`: The current agent (anonymous or authenticated user). Can be used as `loadAs` parameter for load and subscribe methods. - `logOut`: Function to log out the current user@example```tsx // Deep loading with resolve queries function ProjectListWithDetails() { const { me } = useAccount(MyAppAccount, { resolve: { profile: true, root: { myProjects: { $each: { tasks: true, }, }, }, }, }); if (!me) { return me === null ? <div>Failed to load your projects</div> : <div>Loading...</div>; } return ( <div> <h1>{me.profile.name}'s projects</h1> <ul> {me.root.myProjects.map((project) => ( <li key={project.id}> {project.name} ({project.tasks.length} tasks) </li> ))} </ul> </div> ); } ```
useAccount
(
const JazzAccount: co.Account<{
    root: co.Map<{
        draft: co.Map<{
            name: z.ZodOptional<z.z.ZodString>;
        }, unknown, Account | Group>;
    }, unknown, Account | Group>;
    profile: co.Map<{
        ...;
    }, unknown, Account | Group>;
}>
JazzAccount
, {
resolve?: RefsToResolve<{
    root: ({
        draft: ({
            name: string | undefined;
        } & CoMap) | null;
    } & CoMap) | null;
    profile: ({
        ...;
    } & CoMap) | null;
} & Account, 10, []> | undefined
Resolve query to specify which nested CoValues to load from the account
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 (function hasChanges(draft?: co.loaded<typeof DraftBubbleTeaOrder>): number | falsehasChanges(
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 ( <React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>You have a draft</React.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: co.Map<{
    baseTea: z.z.ZodLiteral<"Black" | "Oolong" | "Jasmine" | "Thai">;
    addOns: co.List<z.z.ZodLiteral<"Pearl" | "Lychee jelly" | "Red bean" | "Brown sugar" | "Taro">>;
    deliveryDate: z.z.ZodDate;
    withMilk: z.z.ZodBoolean;
    instructions: z.ZodOptional<...>;
}, unknown, Account | Group>
BubbleTeaOrder
= import coco.
map<{
    baseTea: z.z.ZodLiteral<"Black" | "Oolong" | "Jasmine" | "Thai">;
    addOns: co.List<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: co.List<z.z.ZodLiteral<"Pearl" | "Lychee jelly" | "Red bean" | "Brown sugar" | "Taro">>;
    deliveryDate: z.z.ZodDate;
    withMilk: z.z.ZodBoolean;
    instructions: z.ZodOptional<...>;
}): co.Map<...>
export map
map
({
baseTea: z.z.ZodLiteral<"Black" | "Oolong" | "Jasmine" | "Thai">baseTea: import zz.
literal<readonly ["Black", "Oolong", "Jasmine", "Thai"]>(value: readonly ["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: co.List<z.z.ZodLiteral<"Pearl" | "Lychee jelly" | "Red bean" | "Brown sugar" | "Taro">>addOns: const ListOfBubbleTeaAddOns: co.List<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 (+1 overload)
export string
string
()),
});