Creating and updating CoValues in a form

Normally, we implement forms using the onSubmit handler, or by making a controlled form with useState, or by using special libraries like react-hook-form.

In Jazz, we can do something simpler and more powerful, because CoValues give us reactive, persisted state which we can use to directly edit live objects, and represent auto-saved drafts.

See the full example here.

Updating a CoValue

To update a CoValue, we simply assign the new value directly as changes happen. These changes are synced to the server, so we don't need to handle form submissions either.

<input
  type="text"
  value={order.name}
  onChange={(e) => order.name = e.target.value}
/>

This means we can write update forms in fewer lines of code.

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 class class BubbleTeaOrderBubbleTeaOrder extends class CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.
@categoryDescriptionDeclaration Declare your own CoMap schemas by subclassing `CoMap` and assigning field schemas with `co`. Optional `co.ref(...)` fields must be marked with `{ optional: true }`. ```ts import { co, CoMap } from "jazz-tools"; class Person extends CoMap { name = co.string; age = co.number; pet = co.ref(Animal); car = co.ref(Car, { optional: true }); } ```@categoryDescriptionContent You can access properties you declare on a `CoMap` (using `co`) as if they were normal properties on a plain object, using dot notation, `Object.keys()`, etc. ```ts person.name; person["age"]; person.age = 42; person.pet?.name; Object.keys(person); // => ["name", "age", "pet"] ```@categoryCoValues
CoMap
{
BubbleTeaOrder.name: co<string>name =
const co: {
    string: co<string>;
    number: co<number>;
    boolean: co<boolean>;
    null: co<null>;
    Date: co<Date>;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>;
    json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>;
    encoded<T>(arg: Encoder<T>): co<T>;
    ref: {
        ...;
    };
    items: ItemsSym;
    optional: {
        ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
        json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>;
        encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>;
        string: co<string | undefined>;
        number: co<number | undefined>;
        boolean: co<boolean | undefined>;
        null: co<null | undefined>;
        Date: co<Date | undefined>;
        literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
    };
}
@categorySchema definition@categorySchema definition
co
.string: co<string>string;
} export class class DraftBubbleTeaOrderDraftBubbleTeaOrder extends class CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.
@categoryDescriptionDeclaration Declare your own CoMap schemas by subclassing `CoMap` and assigning field schemas with `co`. Optional `co.ref(...)` fields must be marked with `{ optional: true }`. ```ts import { co, CoMap } from "jazz-tools"; class Person extends CoMap { name = co.string; age = co.number; pet = co.ref(Animal); car = co.ref(Car, { optional: true }); } ```@categoryDescriptionContent You can access properties you declare on a `CoMap` (using `co`) as if they were normal properties on a plain object, using dot notation, `Object.keys()`, etc. ```ts person.name; person["age"]; person.age = 42; person.pet?.name; Object.keys(person); // => ["name", "age", "pet"] ```@categoryCoValues
CoMap
{
DraftBubbleTeaOrder.name: co<string | undefined>name =
const co: {
    string: co<string>;
    number: co<number>;
    boolean: co<boolean>;
    null: co<null>;
    Date: co<Date>;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>;
    json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>;
    encoded<T>(arg: Encoder<T>): co<T>;
    ref: {
        ...;
    };
    items: ItemsSym;
    optional: {
        ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
        json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>;
        encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>;
        string: co<string | undefined>;
        number: co<number | undefined>;
        boolean: co<boolean | undefined>;
        null: co<null | undefined>;
        Date: co<Date | undefined>;
        literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
    };
}
@categorySchema definition@categorySchema definition
co
.
optional: {
    ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
    ... 7 more ...;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
}
optional
.string: co<string | undefined>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: BubbleTeaOrder | DraftBubbleTeaOrder;
    onSave?: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element
OrderForm
({
order: BubbleTeaOrder | DraftBubbleTeaOrderorder, onSave: ((e: React.FormEvent<HTMLFormElement>) => void) | undefinedonSave, }: { order: BubbleTeaOrder | DraftBubbleTeaOrderorder: class BubbleTeaOrderBubbleTeaOrder | class DraftBubbleTeaOrderDraftBubbleTeaOrder; 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: BubbleTeaOrder | DraftBubbleTeaOrderorder.name: string | (string & CoMarker) | undefinedname} React.InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefinedonChange={(e: React.ChangeEvent<HTMLInputElement>e) => (order: BubbleTeaOrder | DraftBubbleTeaOrderorder.name: string | (string & CoMarker) | 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: ID<BubbleTeaOrder>;
}): React.JSX.Element | undefined
EditOrder
(
props: {
    id: ID<BubbleTeaOrder>;
}
props
: { id: ID<BubbleTeaOrder>id: type ID<T> = `co_z${string}` & IDMarker<T>
IDs are unique identifiers for `CoValue`s. Can be used with a type argument to refer to a specific `CoValue` type.
@example```ts type AccountID = ID<Account>; ```@categoryCoValues
ID
<class BubbleTeaOrderBubbleTeaOrder> }) {
const const order: BubbleTeaOrder | null | undefinedorder =
useCoState<BubbleTeaOrder, true>(Schema: CoValueClass<BubbleTeaOrder>, id: ID<CoValue> | undefined, options?: {
    resolve?: RefsToResolve<...> | undefined;
} | undefined): BubbleTeaOrder | ... 1 more ... | undefined
useCoState
(class BubbleTeaOrderBubbleTeaOrder,
props: {
    id: ID<BubbleTeaOrder>;
}
props
.id: ID<BubbleTeaOrder>id);
if (!const order: BubbleTeaOrder | null | undefinedorder) return; return <
function OrderForm({ order, onSave, }: {
    order: BubbleTeaOrder | DraftBubbleTeaOrder;
    onSave?: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element
OrderForm
order: BubbleTeaOrder | DraftBubbleTeaOrderorder={const order: BubbleTeaOrderorder} />;
}

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: JazzAccountme } = 
useAccount<JazzAccount>(): {
    me: JazzAccount;
    logOut: () => void;
} (+1 overload)
useAccount
();
const [const draft: DraftBubbleTeaOrder | undefineddraft, const setDraft: React.Dispatch<React.SetStateAction<DraftBubbleTeaOrder | undefined>>setDraft] = useState<DraftBubbleTeaOrder>(): [DraftBubbleTeaOrder | undefined, React.Dispatch<React.SetStateAction<DraftBubbleTeaOrder | 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
<class DraftBubbleTeaOrderDraftBubbleTeaOrder>();
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<DraftBubbleTeaOrder | undefined>) => voidsetDraft(class DraftBubbleTeaOrderDraftBubbleTeaOrder.
CoMap.create<DraftBubbleTeaOrder>(this: CoValueClass<...>, init: {
    name?: string | (string & CoMarker) | null | undefined;
}, options?: {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | Account | Group): DraftBubbleTeaOrder
Create a new CoMap with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoMap will immediately be persisted and synced to connected peers.
@example```ts const person = Person.create({ name: "Alice", age: 42, pet: cat, }, { owner: friendGroup }); ```@categoryCreation
create
({}));
}, [const me: JazzAccountme?.Account.id: ID<JazzAccount>
@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: DraftBubbleTeaOrder | undefineddraft) return; const const order: BubbleTeaOrderorder = const draft: DraftBubbleTeaOrderdraft as class BubbleTeaOrderBubbleTeaOrder; 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: BubbleTeaOrderorder);
}; if (!const draft: DraftBubbleTeaOrder | undefineddraft) return; return <
function OrderForm({ order, onSave, }: {
    order: BubbleTeaOrder | DraftBubbleTeaOrder;
    onSave?: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element
OrderForm
order: DraftBubbleTeaOrder | BubbleTeaOrderorder={const draft: DraftBubbleTeaOrderdraft} 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 method.

// schema.ts
export class class DraftBubbleTeaOrderDraftBubbleTeaOrder extends class CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.
@categoryDescriptionDeclaration Declare your own CoMap schemas by subclassing `CoMap` and assigning field schemas with `co`. Optional `co.ref(...)` fields must be marked with `{ optional: true }`. ```ts import { co, CoMap } from "jazz-tools"; class Person extends CoMap { name = co.string; age = co.number; pet = co.ref(Animal); car = co.ref(Car, { optional: true }); } ```@categoryDescriptionContent You can access properties you declare on a `CoMap` (using `co`) as if they were normal properties on a plain object, using dot notation, `Object.keys()`, etc. ```ts person.name; person["age"]; person.age = 42; person.pet?.name; Object.keys(person); // => ["name", "age", "pet"] ```@categoryCoValues
CoMap
{
DraftBubbleTeaOrder.name: co<string | undefined>name =
const co: {
    string: co<string>;
    number: co<number>;
    boolean: co<boolean>;
    null: co<null>;
    Date: co<Date>;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>;
    json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>;
    encoded<T>(arg: Encoder<T>): co<T>;
    ref: {
        ...;
    };
    items: ItemsSym;
    optional: {
        ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
        json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>;
        encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>;
        string: co<string | undefined>;
        number: co<number | undefined>;
        boolean: co<boolean | undefined>;
        null: co<null | undefined>;
        Date: co<Date | undefined>;
        literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
    };
}
@categorySchema definition@categorySchema definition
co
.
optional: {
    ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
    ... 7 more ...;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
}
optional
.string: co<string | undefined>string;
DraftBubbleTeaOrder.validate(): {
    errors: string[];
}
validate
() {
const const errors: string[]errors: string[] = []; if (!this.DraftBubbleTeaOrder.name: co<string | undefined>name) { const errors: string[]errors.Array<string>.push(...items: string[]): number
Appends new elements to the end of an array, and returns the new length of the array.
@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: JazzAccountme } = 
useAccount<JazzAccount>(): {
    me: JazzAccount;
    logOut: () => void;
} (+1 overload)
useAccount
();
const [const draft: DraftBubbleTeaOrder | undefineddraft, const setDraft: React.Dispatch<React.SetStateAction<DraftBubbleTeaOrder | undefined>>setDraft] = useState<DraftBubbleTeaOrder>(): [DraftBubbleTeaOrder | undefined, React.Dispatch<React.SetStateAction<DraftBubbleTeaOrder | 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
<class DraftBubbleTeaOrderDraftBubbleTeaOrder>();
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<DraftBubbleTeaOrder | undefined>) => voidsetDraft(class DraftBubbleTeaOrderDraftBubbleTeaOrder.
CoMap.create<DraftBubbleTeaOrder>(this: CoValueClass<...>, init: {
    name?: string | (string & CoMarker) | null | undefined;
}, options?: {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | Account | Group): DraftBubbleTeaOrder
Create a new CoMap with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoMap will immediately be persisted and synced to connected peers.
@example```ts const person = Person.create({ name: "Alice", age: 42, pet: cat, }, { owner: friendGroup }); ```@categoryCreation
create
({}));
}, [const me: JazzAccountme?.Account.id: ID<JazzAccount>
@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: DraftBubbleTeaOrder | undefineddraft) return; const
const validation: {
    errors: string[];
}
validation
= const draft: DraftBubbleTeaOrderdraft.
DraftBubbleTeaOrder.validate(): {
    errors: string[];
}
validate
();
if (
const validation: {
    errors: string[];
}
validation
.errors: string[]errors.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
> 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: BubbleTeaOrderorder = const draft: DraftBubbleTeaOrderdraft as class BubbleTeaOrderBubbleTeaOrder; 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: BubbleTeaOrderorder);
}; if (!const draft: DraftBubbleTeaOrder | undefineddraft) return; return <
function OrderForm({ order, onSave, }: {
    order: BubbleTeaOrder | DraftBubbleTeaOrder;
    onSave?: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element
OrderForm
order: DraftBubbleTeaOrder | BubbleTeaOrderorder={const draft: DraftBubbleTeaOrderdraft} 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 class class BubbleTeaOrderBubbleTeaOrder extends class CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.
@categoryDescriptionDeclaration Declare your own CoMap schemas by subclassing `CoMap` and assigning field schemas with `co`. Optional `co.ref(...)` fields must be marked with `{ optional: true }`. ```ts import { co, CoMap } from "jazz-tools"; class Person extends CoMap { name = co.string; age = co.number; pet = co.ref(Animal); car = co.ref(Car, { optional: true }); } ```@categoryDescriptionContent You can access properties you declare on a `CoMap` (using `co`) as if they were normal properties on a plain object, using dot notation, `Object.keys()`, etc. ```ts person.name; person["age"]; person.age = 42; person.pet?.name; Object.keys(person); // => ["name", "age", "pet"] ```@categoryCoValues
CoMap
{
BubbleTeaOrder.name: co<string>name =
const co: {
    string: co<string>;
    number: co<number>;
    boolean: co<boolean>;
    null: co<null>;
    Date: co<Date>;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>;
    json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>;
    encoded<T>(arg: Encoder<T>): co<T>;
    ref: {
        ...;
    };
    items: ItemsSym;
    optional: {
        ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
        json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>;
        encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>;
        string: co<string | undefined>;
        number: co<number | undefined>;
        boolean: co<boolean | undefined>;
        null: co<null | undefined>;
        Date: co<Date | undefined>;
        literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
    };
}
@categorySchema definition@categorySchema definition
co
.string: co<string>string;
} export class class DraftBubbleTeaOrderDraftBubbleTeaOrder extends class CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.
@categoryDescriptionDeclaration Declare your own CoMap schemas by subclassing `CoMap` and assigning field schemas with `co`. Optional `co.ref(...)` fields must be marked with `{ optional: true }`. ```ts import { co, CoMap } from "jazz-tools"; class Person extends CoMap { name = co.string; age = co.number; pet = co.ref(Animal); car = co.ref(Car, { optional: true }); } ```@categoryDescriptionContent You can access properties you declare on a `CoMap` (using `co`) as if they were normal properties on a plain object, using dot notation, `Object.keys()`, etc. ```ts person.name; person["age"]; person.age = 42; person.pet?.name; Object.keys(person); // => ["name", "age", "pet"] ```@categoryCoValues
CoMap
{
DraftBubbleTeaOrder.name: co<string | undefined>name =
const co: {
    string: co<string>;
    number: co<number>;
    boolean: co<boolean>;
    null: co<null>;
    Date: co<Date>;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>;
    json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>;
    encoded<T>(arg: Encoder<T>): co<T>;
    ref: {
        ...;
    };
    items: ItemsSym;
    optional: {
        ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
        json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>;
        encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>;
        string: co<string | undefined>;
        number: co<number | undefined>;
        boolean: co<boolean | undefined>;
        null: co<null | undefined>;
        Date: co<Date | undefined>;
        literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
    };
}
@categorySchema definition@categorySchema definition
co
.
optional: {
    ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
    ... 7 more ...;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
}
optional
.string: co<string | undefined>string;
} export class class AccountRootAccountRoot extends class CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.
@categoryDescriptionDeclaration Declare your own CoMap schemas by subclassing `CoMap` and assigning field schemas with `co`. Optional `co.ref(...)` fields must be marked with `{ optional: true }`. ```ts import { co, CoMap } from "jazz-tools"; class Person extends CoMap { name = co.string; age = co.number; pet = co.ref(Animal); car = co.ref(Car, { optional: true }); } ```@categoryDescriptionContent You can access properties you declare on a `CoMap` (using `co`) as if they were normal properties on a plain object, using dot notation, `Object.keys()`, etc. ```ts person.name; person["age"]; person.age = 42; person.pet?.name; Object.keys(person); // => ["name", "age", "pet"] ```@categoryCoValues
CoMap
{
AccountRoot.draft: co<DraftBubbleTeaOrder | null>draft =
const co: {
    string: co<string>;
    number: co<number>;
    boolean: co<boolean>;
    null: co<null>;
    Date: co<Date>;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>;
    json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>;
    encoded<T>(arg: Encoder<T>): co<T>;
    ref: {
        ...;
    };
    items: ItemsSym;
    optional: {
        ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
        json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>;
        encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>;
        string: co<string | undefined>;
        number: co<number | undefined>;
        boolean: co<boolean | undefined>;
        null: co<null | undefined>;
        Date: co<Date | undefined>;
        literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
    };
}
@categorySchema definition@categorySchema definition
co
.
ref: <typeof DraftBubbleTeaOrder>(arg: typeof DraftBubbleTeaOrder | ((_raw: RawCoMap<{
    [key: string]: JsonValue | undefined;
}, JsonObject | null>) => typeof DraftBubbleTeaOrder), options?: never) => co<...> (+1 overload)
ref
(class DraftBubbleTeaOrderDraftBubbleTeaOrder);
} export class class JazzAccountJazzAccount extends class Account
@categoryIdentity & Permissions
Account
{
JazzAccount.root: co<AccountRoot | null>root =
const co: {
    string: co<string>;
    number: co<number>;
    boolean: co<boolean>;
    null: co<null>;
    Date: co<Date>;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>;
    json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>;
    encoded<T>(arg: Encoder<T>): co<T>;
    ref: {
        ...;
    };
    items: ItemsSym;
    optional: {
        ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
        json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>;
        encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>;
        string: co<string | undefined>;
        number: co<number | undefined>;
        boolean: co<boolean | undefined>;
        null: co<null | undefined>;
        Date: co<Date | undefined>;
        literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
    };
}
@categorySchema definition@categorySchema definition
co
.
ref: <typeof AccountRoot>(arg: typeof AccountRoot | ((_raw: RawCoMap<{
    [key: string]: JsonValue | undefined;
}, JsonObject | null>) => typeof AccountRoot), options?: never) => co<...> (+1 overload)
ref
(class AccountRootAccountRoot);
JazzAccount.migrate(this: JazzAccount, creationProps?: {
    name: string;
}): void
migrate
(this: JazzAccountthis: class JazzAccountJazzAccount,
creationProps: {
    name: string;
} | undefined
creationProps
?: { name: stringname: string }) {
if (this.JazzAccount.root: co<AccountRoot | null>root === var undefinedundefined) { const const draft: DraftBubbleTeaOrderdraft = class DraftBubbleTeaOrderDraftBubbleTeaOrder.
CoMap.create<DraftBubbleTeaOrder>(this: CoValueClass<...>, init: {
    name?: string | (string & CoMarker) | null | undefined;
}, options?: {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | Account | Group): DraftBubbleTeaOrder
Create a new CoMap with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoMap will immediately be persisted and synced to connected peers.
@example```ts const person = Person.create({ name: "Alice", age: 42, pet: cat, }, { owner: friendGroup }); ```@categoryCreation
create
({});
this.JazzAccount.root: co<AccountRoot | null>root = class AccountRootAccountRoot.
CoMap.create<AccountRoot>(this: CoValueClass<...>, init: {
    draft: (DraftBubbleTeaOrder | NonNullable<DraftBubbleTeaOrder & CoMarker>) & (DraftBubbleTeaOrder | ... 1 more ... | undefined);
}, options?: {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | Account | Group): AccountRoot
Create a new CoMap with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoMap will immediately be persisted and synced to connected peers.
@example```ts const person = Person.create({ name: "Alice", age: 42, pet: cat, }, { owner: friendGroup }); ```@categoryCreation
create
({ draft: (DraftBubbleTeaOrder | NonNullable<DraftBubbleTeaOrder & CoMarker>) & (DraftBubbleTeaOrder | NonNullable<...> | undefined)draft });
} } }

Let's not forget to update the AccountSchema.

import { function JazzProvider<Acc extends Account = JazzAccount>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, }: JazzProviderProps<Acc>): JSX.Element
@categoryContext & Hooks
JazzProvider
} from "jazz-react";
import { class JazzAccountJazzAccount } 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<Acc extends Account = JazzAccount>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, }: JazzProviderProps<Acc>): 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?: AccountClass<JazzAccount> | undefinedAccountSchema={class JazzAccountJazzAccount} > {children: React.ReactNodechildren} </function JazzProvider<Acc extends Account = JazzAccount>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, }: JazzProviderProps<Acc>): JSX.Element
@categoryContext & Hooks
JazzProvider
>
); } // Register the Account schema so `useAccount` returns our custom `JazzAccount` declare module "jazz-react" { interface Register { Register.Account: JazzAccountAccount: class JazzAccountJazzAccount; } }

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: DraftBubbleTeaOrder;
    } & AccountRoot;
} & JazzAccount) | null | undefined
me
} =
useAccount<JazzAccount, {
    root: {
        draft: true;
    };
}>(options?: {
    resolve?: RefsToResolve<JazzAccount, 10, []> | undefined;
} | undefined): {
    me: ({
        root: {
            ...;
        } & AccountRoot;
    } & JazzAccount) | null | undefined;
    logOut: () => void;
} (+1 overload)
useAccount
({
resolve?: RefsToResolve<JazzAccount, 10, []> | undefinedresolve: { root?: RefsToResolve<AccountRoot, 10, [0]> | undefinedroot: { draft?: RefsToResolve<DraftBubbleTeaOrder, 10, [0, 0]> | undefineddraft: true } }, }); if (!
const me: ({
    root: {
        draft: DraftBubbleTeaOrder;
    } & AccountRoot;
} & JazzAccount) | null | undefined
me
?.
JazzAccount.root: ({
    draft: DraftBubbleTeaOrder;
} & AccountRoot & co<AccountRoot | null>) | 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: DraftBubbleTeaOrder & co<DraftBubbleTeaOrder | null>draft =
const me: {
    root: {
        draft: DraftBubbleTeaOrder;
    } & AccountRoot;
} & JazzAccount
me
.
JazzAccount.root: {
    draft: DraftBubbleTeaOrder;
} & AccountRoot & co<AccountRoot | null>
root
.AccountRoot.draft: DraftBubbleTeaOrder & co<DraftBubbleTeaOrder | null>draft;
if (!const draft: DraftBubbleTeaOrder & co<DraftBubbleTeaOrder | null>draft) return; const
const validation: {
    errors: string[];
}
validation
= const draft: DraftBubbleTeaOrder & co<DraftBubbleTeaOrder | null>draft.
DraftBubbleTeaOrder.validate(): {
    errors: string[];
}
validate
();
if (
const validation: {
    errors: string[];
}
validation
.errors: string[]errors.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
> 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: BubbleTeaOrderorder = const draft: DraftBubbleTeaOrder & co<DraftBubbleTeaOrder | null>draft as class BubbleTeaOrderBubbleTeaOrder; 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: BubbleTeaOrderorder);
// create a new empty draft
const me: {
    root: {
        draft: DraftBubbleTeaOrder;
    } & AccountRoot;
} & JazzAccount
me
.
JazzAccount.root: {
    draft: DraftBubbleTeaOrder;
} & AccountRoot & co<AccountRoot | null>
root
.AccountRoot.draft: DraftBubbleTeaOrder & co<DraftBubbleTeaOrder | null>draft = class DraftBubbleTeaOrderDraftBubbleTeaOrder.
CoMap.create<DraftBubbleTeaOrder>(this: CoValueClass<...>, init: {
    name?: string | (string & CoMarker) | null | undefined;
}, options?: {
    owner: Account | Group;
    unique?: CoValueUniqueness["uniqueness"];
} | Account | Group): DraftBubbleTeaOrder
Create a new CoMap with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoMap will immediately be persisted and synced to connected peers.
@example```ts const person = Person.create({ name: "Alice", age: 42, pet: cat, }, { owner: friendGroup }); ```@categoryCreation
create
(
{}, ); }; return <
function CreateOrderForm({ id, onSave, }: {
    id: ID<DraftBubbleTeaOrder>;
    onSave: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element | undefined
CreateOrderForm
id: ID<DraftBubbleTeaOrder>id={
const me: {
    root: {
        draft: DraftBubbleTeaOrder;
    } & AccountRoot;
} & JazzAccount
me
.
JazzAccount.root: {
    draft: DraftBubbleTeaOrder;
} & AccountRoot & co<AccountRoot | null>
root
.AccountRoot.draft: DraftBubbleTeaOrder & co<DraftBubbleTeaOrder | null>draft.CoMap.id: ID<DraftBubbleTeaOrder> | ID<DraftBubbleTeaOrder & CoMarker>
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: ID<DraftBubbleTeaOrder>;
    onSave: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element | undefined
CreateOrderForm
({
id: ID<DraftBubbleTeaOrder>id, onSave: (e: React.FormEvent<HTMLFormElement>) => voidonSave, }: { id: ID<DraftBubbleTeaOrder>id: type ID<T> = `co_z${string}` & IDMarker<T>
IDs are unique identifiers for `CoValue`s. Can be used with a type argument to refer to a specific `CoValue` type.
@example```ts type AccountID = ID<Account>; ```@categoryCoValues
ID
<class DraftBubbleTeaOrderDraftBubbleTeaOrder>;
onSave: (e: React.FormEvent<HTMLFormElement>) => voidonSave: (e: React.FormEvent<HTMLFormElement>e: React.interface React.FormEvent<T = Element>FormEvent<HTMLFormElement>) => void; }) { const const draft: DraftBubbleTeaOrder | null | undefineddraft =
useCoState<DraftBubbleTeaOrder, true>(Schema: CoValueClass<DraftBubbleTeaOrder>, id: ID<CoValue> | undefined, options?: {
    ...;
} | undefined): DraftBubbleTeaOrder | ... 1 more ... | undefined
useCoState
(class DraftBubbleTeaOrderDraftBubbleTeaOrder, id: ID<DraftBubbleTeaOrder>id);
if (!const draft: DraftBubbleTeaOrder | null | undefineddraft) return; return <
function OrderForm({ order, onSave, }: {
    order: BubbleTeaOrder | DraftBubbleTeaOrder;
    onSave?: (e: React.FormEvent<HTMLFormElement>) => void;
}): React.JSX.Element
OrderForm
order: DraftBubbleTeaOrder | BubbleTeaOrderorder={const draft: DraftBubbleTeaOrderdraft} 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 checker to your schema.

// schema.ts
export class class DraftBubbleTeaOrderDraftBubbleTeaOrder extends class CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.
@categoryDescriptionDeclaration Declare your own CoMap schemas by subclassing `CoMap` and assigning field schemas with `co`. Optional `co.ref(...)` fields must be marked with `{ optional: true }`. ```ts import { co, CoMap } from "jazz-tools"; class Person extends CoMap { name = co.string; age = co.number; pet = co.ref(Animal); car = co.ref(Car, { optional: true }); } ```@categoryDescriptionContent You can access properties you declare on a `CoMap` (using `co`) as if they were normal properties on a plain object, using dot notation, `Object.keys()`, etc. ```ts person.name; person["age"]; person.age = 42; person.pet?.name; Object.keys(person); // => ["name", "age", "pet"] ```@categoryCoValues
CoMap
{
DraftBubbleTeaOrder.name: co<string | undefined>name =
const co: {
    string: co<string>;
    number: co<number>;
    boolean: co<boolean>;
    null: co<null>;
    Date: co<Date>;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>;
    json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>;
    encoded<T>(arg: Encoder<T>): co<T>;
    ref: {
        ...;
    };
    items: ItemsSym;
    optional: {
        ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
        json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>;
        encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>;
        string: co<string | undefined>;
        number: co<number | undefined>;
        boolean: co<boolean | undefined>;
        null: co<null | undefined>;
        Date: co<Date | undefined>;
        literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
    };
}
@categorySchema definition@categorySchema definition
co
.
optional: {
    ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
    ... 7 more ...;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
}
optional
.string: co<string | undefined>string;
DraftBubbleTeaOrder.validate(): {
    errors: string[];
}
validate
() {
const const errors: string[]errors: string[] = []; if (!this.DraftBubbleTeaOrder.name: co<string | undefined>name) { const errors: string[]errors.Array<string>.push(...items: string[]): number
Appends new elements to the end of an array, and returns the new length of the array.
@paramitems New elements to add to the array.
push
("Plese enter a name.");
} return { errors: string[]errors }; } get DraftBubbleTeaOrder.hasChanges: numberhasChanges() { return 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
(this.CoMap._edits: { [Key in Exclude<keyof this & string, keyof CoMap>]: IfCo<this[Key], LastAndAllCoMapEdits<this[Key]>>; }
@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
;
} }

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: DraftBubbleTeaOrder;
    } & AccountRoot;
} & JazzAccount) | null | undefined
me
} =
useAccount<JazzAccount, {
    root: {
        draft: true;
    };
}>(options?: {
    resolve?: RefsToResolve<JazzAccount, 10, []> | undefined;
} | undefined): {
    me: ({
        root: {
            ...;
        } & AccountRoot;
    } & JazzAccount) | null | undefined;
    logOut: () => void;
} (+1 overload)
useAccount
({
resolve?: RefsToResolve<JazzAccount, 10, []> | undefinedresolve: { root?: RefsToResolve<AccountRoot, 10, [0]> | undefinedroot: { draft?: RefsToResolve<DraftBubbleTeaOrder, 10, [0, 0]> | undefineddraft: true } }, }); if (
const me: ({
    root: {
        draft: DraftBubbleTeaOrder;
    } & AccountRoot;
} & JazzAccount) | null | undefined
me
?.
JazzAccount.root: {
    draft: DraftBubbleTeaOrder;
} & AccountRoot & co<AccountRoot | null>
root
.AccountRoot.draft: (DraftBubbleTeaOrder & co<DraftBubbleTeaOrder | null>) | undefineddraft?.DraftBubbleTeaOrder.hasChanges: number | undefinedhasChanges) {
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 class class BubbleTeaOrderBubbleTeaOrder extends class CoMap
CoMaps are collaborative versions of plain objects, mapping string-like keys to values.
@categoryDescriptionDeclaration Declare your own CoMap schemas by subclassing `CoMap` and assigning field schemas with `co`. Optional `co.ref(...)` fields must be marked with `{ optional: true }`. ```ts import { co, CoMap } from "jazz-tools"; class Person extends CoMap { name = co.string; age = co.number; pet = co.ref(Animal); car = co.ref(Car, { optional: true }); } ```@categoryDescriptionContent You can access properties you declare on a `CoMap` (using `co`) as if they were normal properties on a plain object, using dot notation, `Object.keys()`, etc. ```ts person.name; person["age"]; person.age = 42; person.pet?.name; Object.keys(person); // => ["name", "age", "pet"] ```@categoryCoValues
CoMap
{
BubbleTeaOrder.baseTea: co<"Black" | "Oolong" | "Jasmine" | "Thai">baseTea =
const co: {
    string: co<string>;
    number: co<number>;
    boolean: co<boolean>;
    null: co<null>;
    Date: co<Date>;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>;
    json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>;
    encoded<T>(arg: Encoder<T>): co<T>;
    ref: {
        ...;
    };
    items: ItemsSym;
    optional: {
        ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
        json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>;
        encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>;
        string: co<string | undefined>;
        number: co<number | undefined>;
        boolean: co<boolean | undefined>;
        null: co<null | undefined>;
        Date: co<Date | undefined>;
        literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
    };
}
@categorySchema definition@categorySchema definition
co
.literal<["Black", "Oolong", "Jasmine", "Thai"]>(_lit_0: "Black", _lit_1: "Oolong", _lit_2: "Jasmine", _lit_3: "Thai"): co<"Black" | "Oolong" | "Jasmine" | "Thai">literal(...const BubbleTeaBaseTeaTypes: readonly ["Black", "Oolong", "Jasmine", "Thai"]BubbleTeaBaseTeaTypes);
BubbleTeaOrder.addOns: co<ListOfBubbleTeaAddOns | null>addOns =
const co: {
    string: co<string>;
    number: co<number>;
    boolean: co<boolean>;
    null: co<null>;
    Date: co<Date>;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>;
    json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>;
    encoded<T>(arg: Encoder<T>): co<T>;
    ref: {
        ...;
    };
    items: ItemsSym;
    optional: {
        ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
        json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>;
        encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>;
        string: co<string | undefined>;
        number: co<number | undefined>;
        boolean: co<boolean | undefined>;
        null: co<null | undefined>;
        Date: co<Date | undefined>;
        literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
    };
}
@categorySchema definition@categorySchema definition
co
.ref: <typeof ListOfBubbleTeaAddOns>(arg: typeof ListOfBubbleTeaAddOns | ((_raw: RawCoList<JsonValue, null>) => typeof ListOfBubbleTeaAddOns), options?: never) => co<...> (+1 overload)ref(class ListOfBubbleTeaAddOnsListOfBubbleTeaAddOns);
BubbleTeaOrder.deliveryDate: co<Date>deliveryDate =
const co: {
    string: co<string>;
    number: co<number>;
    boolean: co<boolean>;
    null: co<null>;
    Date: co<Date>;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>;
    json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>;
    encoded<T>(arg: Encoder<T>): co<T>;
    ref: {
        ...;
    };
    items: ItemsSym;
    optional: {
        ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
        json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>;
        encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>;
        string: co<string | undefined>;
        number: co<number | undefined>;
        boolean: co<boolean | undefined>;
        null: co<null | undefined>;
        Date: co<Date | undefined>;
        literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
    };
}
@categorySchema definition@categorySchema definition
co
.type Date: co<Date>Date;
BubbleTeaOrder.withMilk: co<boolean>withMilk =
const co: {
    string: co<string>;
    number: co<number>;
    boolean: co<boolean>;
    null: co<null>;
    Date: co<Date>;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>;
    json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>;
    encoded<T>(arg: Encoder<T>): co<T>;
    ref: {
        ...;
    };
    items: ItemsSym;
    optional: {
        ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
        json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>;
        encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>;
        string: co<string | undefined>;
        number: co<number | undefined>;
        boolean: co<boolean | undefined>;
        null: co<null | undefined>;
        Date: co<Date | undefined>;
        literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
    };
}
@categorySchema definition@categorySchema definition
co
.boolean: co<boolean>boolean;
BubbleTeaOrder.instructions: co<string | undefined>instructions =
const co: {
    string: co<string>;
    number: co<number>;
    boolean: co<boolean>;
    null: co<null>;
    Date: co<Date>;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number]>;
    json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T>;
    encoded<T>(arg: Encoder<T>): co<T>;
    ref: {
        ...;
    };
    items: ItemsSym;
    optional: {
        ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
        json<T extends CojsonInternalTypes.CoJsonValue<T>>(): co<T | undefined>;
        encoded<T>(arg: OptionalEncoder<T>): co<T | undefined>;
        string: co<string | undefined>;
        number: co<number | undefined>;
        boolean: co<boolean | undefined>;
        null: co<null | undefined>;
        Date: co<Date | undefined>;
        literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
    };
}
@categorySchema definition@categorySchema definition
co
.
optional: {
    ref: <C extends CoValueClass>(arg: C | ((_raw: InstanceType<C>["_raw"]) => C)) => co<InstanceType<C> | null | undefined>;
    ... 7 more ...;
    literal<T extends (string | number | boolean)[]>(..._lit: T): co<T[number] | undefined>;
}
optional
.string: co<string | undefined>string;
}