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