Skip to content

Commit

Permalink
docs: add CodeSandbox demo to Modular Forms guide (#4095)
Browse files Browse the repository at this point in the history
* Add CodeSandbox demo to Modular Forms guide

* Sync CodeSandbox demo code of other guides

* Add ts-nocheck to allow unused variables in code

* Fix CodeSandbox demo of deep store and sync docs
  • Loading branch information
fabian-hiller committed Jun 6, 2023
1 parent 3daee3d commit e00340d
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 64 deletions.
1 change: 1 addition & 0 deletions packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"license": "MIT",
"private": true,
"dependencies": {
"@modular-forms/qwik": "^0.12.0",
"@supabase/supabase-js": "^2.24.0",
"@unpic/core": "^0.0.24",
"@unpic/qwik": "^0.0.19",
Expand Down
3 changes: 2 additions & 1 deletion packages/docs/src/routes/demo/demo-reset.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ demo h1 {
font-weight: bolder;
}

demo button {
demo button,
demo button[type='submit'] {
display: inline-block;
border: 1px solid #ccc;
padding: 0.5rem 1rem;
Expand Down
63 changes: 63 additions & 0 deletions packages/docs/src/routes/demo/integration/modular-forms/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// @ts-nocheck
/* eslint-disable @typescript-eslint/no-unused-vars */
import { $, component$ } from '@builder.io/qwik';
import { routeLoader$, z } from '@builder.io/qwik-city';
import type { InitialValues, SubmitHandler } from '@modular-forms/qwik';
import { formAction$, useForm, zodForm$ } from '@modular-forms/qwik';

const loginSchema = z.object({
email: z
.string()
.min(1, 'Please enter your email.')
.email('The email address is badly formatted.'),
password: z
.string()
.min(1, 'Please enter your password.')
.min(8, 'You password must have 8 characters or more.'),
});

type LoginForm = z.infer<typeof loginSchema>;

export const useFormLoader = routeLoader$<InitialValues<LoginForm>>(() => ({
email: '',
password: '',
}));

export const useFormAction = formAction$<LoginForm>((values) => {
// Runs on server
}, zodForm$(loginSchema));

export default component$(() => {
const [loginForm, { Form, Field }] = useForm<LoginForm>({
loader: useFormLoader(),
action: useFormAction(),
validate: zodForm$(loginSchema),
});

const handleSubmit: SubmitHandler<LoginForm> = $((values, event) => {
// Runs on client
console.log(values);
});

return (
<Form onSubmit$={handleSubmit}>
<Field name="email">
{(field, props) => (
<div>
<input {...props} type="email" value={field.value} />
{field.error && <div>{field.error}</div>}
</div>
)}
</Field>
<Field name="password">
{(field, props) => (
<div>
<input {...props} type="password" value={field.value} />
{field.error && <div>{field.error}</div>}
</div>
)}
</Field>
<button type="submit">Login</button>
</Form>
);
});
31 changes: 23 additions & 8 deletions packages/docs/src/routes/demo/state/counter-store-deep/index.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
import { component$, useStore } from '@builder.io/qwik';

export default component$(() => {
const store = useStore(
{
nested: { fields: { are: 'not tracked' } },
const store = useStore({
nested: {
fields: { are: 'also tracked' },
},
{ deep: true }
);
list: ['Item 1'],
});

return (
<>
<p>{store.nested.fields.are}</p>
<button onClick$={() => (store.nested.fields.are = 'tracked')}>
<button
onClick$={() => {
// Even though we are mutating a nested object, this will trigger a re-render
store.nested.fields.are = 'tracked';
}}
>
Clicking me works because store is deep watched
</button>
<br />
<button onClick$={() => (store.nested = { fields: { are: 'tracked' } })}>
Click me still works
<button
onClick$={() => {
// Because store is deep watched, this will trigger a re-render
store.list.push(`Item ${store.list.length}`);
}}
>
Add to list
</button>
<ul>
{store.list.map((item) => (
<li>{item}</li>
))}
</ul>
</>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ export const Parent = component$(() => {
</section>
);
});

export default Parent;
```
</CodeSandbox>

Expand Down
41 changes: 23 additions & 18 deletions packages/docs/src/routes/docs/(qwik)/components/state/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -95,33 +95,37 @@ Because [`useStore()`](/docs/(qwik)/components/state/index.mdx#usestore) tracks
import { component$, useStore } from '@builder.io/qwik';

export default component$(() => {
const store = useStore(
{
nested: {
fields: { are: 'also tracked' }
},
list: [],
const store = useStore({
nested: {
fields: { are: 'also tracked' },
},
);
list: ['Item 1'],
});

return (
<>
<p>{store.nested.fields.are}</p>
<button onClick$={() => {
// Even though we are mutating a nested object, this will trigger a re-render
store.nested.fields.are = 'tracked'
}}>
<button
onClick$={() => {
// Even though we are mutating a nested object, this will trigger a re-render
store.nested.fields.are = 'tracked';
}}
>
Clicking me works because store is deep watched
</button>
<br />
<button onClick$={() => {
// Because store is deep watched, this will trigger a re-render
store.list.push(`Item ${store.list.length}`);
}}>
<button
onClick$={() => {
// Because store is deep watched, this will trigger a re-render
store.list.push(`Item ${store.list.length}`);
}}
>
Add to list
</button>
<ul>
{store.list.map((item) => <li>{item}</li>)}
{store.list.map((item) => (
<li>{item}</li>
))}
</ul>
</>
);
Expand All @@ -137,9 +141,9 @@ const shallowStore = useStore(
nested: {
fields: { are: 'also tracked' }
},
list: [],
list: ['Item 1'],
},
{ deep: false}
{ deep: false }
);
```

Expand Down Expand Up @@ -314,6 +318,7 @@ export default component$(() => {
<label>
Query: <input bind:value={query} />
</label>
<button>search</button>
<Resource
value={jokes}
onPending={() => <>loading...</>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,36 +72,37 @@ pnpm install qwik-image
<CodeSandbox src="/src/routes/demo/integration/img/qwik-image/index.tsx" style={{ height: '40em' }}>
```tsx {6} /Slot/
import { $, component$ } from '@builder.io/qwik';
import { Image, ImageTransformerProps, useImageProvider } from 'qwik-image';
import { Image, useImageProvider } from 'qwik-image';
import type { ImageTransformerProps } from 'qwik-image';

export default component$(() => {
const imageTransformer$ = $(
({ src, width, height }: ImageTransformerProps): string => {
// Here you can set your favourite image loaders service
return `https://cdn.builder.io/api/v1/${src}?height=${height}&width=${width}}&format=webp&fit=fill`;
}
);
const imageTransformer$ = $(
({ src, width, height }: ImageTransformerProps): string => {
// Here you can set your favourite image loader CDN
return `https://cdn.builder.io/api/v1/${src}?height=${height}&width=${width}}&format=webp&fit=fill`;
}
);

// Global Provider (required)
useImageProvider({
useImageProvider({
// You can set this prop to overwrite default values [3840, 1920, 1280, 960, 640]
resolutions: [640],
imageTransformer$,
});

return (
<Image
layout='constrained'
objectFit='fill'
width={400}
height={500}
alt='Tropical paradise'
placeholder='#e6e6e6'
src={
'image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fe5113e1c02db40e5bac75146fa46386f'
}
/>
);
imageTransformer$,
});

return (
<Image
layout="constrained"
objectFit="fill"
width={400}
height={500}
alt="Tropical paradise"
placeholder="#e6e6e6"
src={
'image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fe5113e1c02db40e5bac75146fa46386f'
}
/>
);
});
```
</CodeSandbox>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ contributors:
- fabian-hiller
---

import CodeSandbox from '../../../../components/code-sandbox/index.tsx';

# Modular Forms

[Modular Forms](https://modularforms.dev/) is a type-safe form library built natively on Qwik. The headless design gives you full control over the visual appearance of your form. The library takes care of state management and input validation.
Expand Down Expand Up @@ -98,7 +100,7 @@ Now you can proceed with the fields of your form. With the [`Field`](https://mod
<input {...props} type="password" value={field.value} />
)}
</Field>
<input type="submit" />
<button type="submit">Login</button>
</Form>
```

Expand Down Expand Up @@ -156,6 +158,78 @@ export default component$(() => {
});
```

## Final form

If we now put all the building blocks together, we get a working login form. Below you can see the assembled code and try it out in the attached sandbox.

<CodeSandbox src="/src/routes/demo/integration/modular-forms/index.tsx">
```tsx
// @ts-nocheck
/* eslint-disable @typescript-eslint/no-unused-vars */
import { $, component$ } from '@builder.io/qwik';
import { routeLoader$, z } from '@builder.io/qwik-city';
import type { InitialValues, SubmitHandler } from '@modular-forms/qwik';
import { formAction$, useForm, zodForm$ } from '@modular-forms/qwik';

const loginSchema = z.object({
email: z
.string()
.min(1, 'Please enter your email.')
.email('The email address is badly formatted.'),
password: z
.string()
.min(1, 'Please enter your password.')
.min(8, 'You password must have 8 characters or more.'),
});

type LoginForm = z.infer<typeof loginSchema>;

export const useFormLoader = routeLoader$<InitialValues<LoginForm>>(() => ({
email: '',
password: '',
}));

export const useFormAction = formAction$<LoginForm>((values) => {
// Runs on server
}, zodForm$(loginSchema));

export default component$(() => {
const [loginForm, { Form, Field }] = useForm<LoginForm>({
loader: useFormLoader(),
action: useFormAction(),
validate: zodForm$(loginSchema),
});

const handleSubmit: SubmitHandler<LoginForm> = $((values, event) => {
// Runs on client
console.log(values);
});

return (
<Form onSubmit$={handleSubmit}>
<Field name="email">
{(field, props) => (
<div>
<input {...props} type="email" value={field.value} />
{field.error && <div>{field.error}</div>}
</div>
)}
</Field>
<Field name="password">
{(field, props) => (
<div>
<input {...props} type="password" value={field.value} />
{field.error && <div>{field.error}</div>}
</div>
)}
</Field>
<button type="submit">Login</button>
</Form>
);
});
```
</CodeSandbox>

## Summary

You have learned the basics of Modular Forms and are ready to create your first simple form. For more info and details you can find more guides and the API reference on our website: [modularforms.dev](https://modularforms.dev/)
Expand Down

0 comments on commit e00340d

Please sign in to comment.