Skip to content

Commit

Permalink
Merge pull request #28 from stevent-team/docs/improve-examples
Browse files Browse the repository at this point in the history
Improve examples
  • Loading branch information
GRA0007 committed Aug 9, 2023
2 parents a71f7c2 + 31fc4ff commit ed27e64
Show file tree
Hide file tree
Showing 24 changed files with 1,052 additions and 615 deletions.
5 changes: 5 additions & 0 deletions .changeset/cool-dodos-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@stevent-team/react-zoom-form": patch
---

Update types to allow any zod type as a schema
135 changes: 0 additions & 135 deletions example/App.tsx

This file was deleted.

94 changes: 94 additions & 0 deletions examples/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Route, Switch, Link, useLocation } from 'wouter'
import { SubmitHandler } from '@stevent-team/react-zoom-form'
import { repository } from '../package.json'

import Basic from './basic'
import Arrays from './arrays'
import Nested from './nested'
import Coerced from './coerced'
import Conditional from './conditional'
import Controlled from './controlled'
import KitchenSink from './kitchen-sink'

interface Example {
name: string
path: string
component: ({ onSubmit }: { onSubmit: SubmitHandler }) => JSX.Element
}

const EXAMPLES: Example[] = [
{
name: 'Basic',
path: '/basic',
component: Basic,
},
{
name: 'Array Field',
path: '/arrays',
component: Arrays,
},
{
name: 'Nested Fields',
path: '/nested',
component: Nested,
},
{
name: 'Coerced Fields',
path: '/coerced',
component: Coerced,
},
{
name: 'Conditional Field',
path: '/conditional',
component: Conditional,
},
{
name: '3rd Party & Controlled Fields',
path: '/controlled',
component: Controlled,
},
{
name: 'Kitchen Sink',
path: '/kitchen-sink',
component: KitchenSink,
},
]

// Shared submit handler
const onSubmit: SubmitHandler = values => {
console.log(values)
document.dispatchEvent(new CustomEvent('zoomSubmit', { detail: values }))
}

const App = () => {
const [location] = useLocation()

return <>
<nav>
{'🏎️ '}<a href={repository} target="_blank" rel="nofollow noreferrer">React Zoom Form</a>
<h2>Examples</h2>
<ul>
{EXAMPLES.map((example, i) => {
const active = location === (i === 0 ? '/' : example.path)

return <li key={example.path} className={active ? 'active' : undefined}>
{active ? <span>{example.name}</span> : <Link to={i === 0 ? '/' : example.path}>{example.name}</Link>}
{' '}(<a href={`${repository}/blob/main/examples${example.path}`} target="_blank" rel="nofollow noreferrer">code</a>)
</li>
})}
</ul>
</nav>

<main>
<Switch>
{EXAMPLES.map((example, i) => <Route key={example.path} path={i === 0 ? '/' : example.path}>
<example.component onSubmit={onSubmit} />
</Route>)}

<Route>Example not found</Route>
</Switch>
</main>
</>
}

export default App
32 changes: 32 additions & 0 deletions examples/Output.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Field, fieldErrors, getValue } from '@stevent-team/react-zoom-form'
import { useCallback, useEffect, useState } from 'react'

interface OutputProps {
isDirty: boolean
fields: Field
}

const Output = ({ isDirty, fields }: OutputProps) => {
const [submittedValue, setSubmittedValue] = useState()

const handleSubmit = useCallback((e: Event) => {
if (e instanceof CustomEvent) setSubmittedValue(e.detail)
}, [])

useEffect(() => {
document.addEventListener('zoomSubmit', handleSubmit)
return () => document.removeEventListener('zoomSubmit', handleSubmit)
})

return <output>
<div><strong>isDirty:</strong> {isDirty ? 'true' : 'false'}</div>
<div><strong>value:</strong> {JSON.stringify(getValue(fields), null, 2)}</div>
<div><strong>errors:</strong> {JSON.stringify(fieldErrors(fields), null, 2)}</div>
{submittedValue && <>
<div><strong>submitted value:</strong> {JSON.stringify(submittedValue, null, 2)}</div>
<div><em>Also view the submitted value in the console</em></div>
</>}
</output>
}

export default Output
41 changes: 41 additions & 0 deletions examples/arrays/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { SubmitHandler, useForm, Errors, getValue, setValue } from '@stevent-team/react-zoom-form'
import { z } from 'zod'
import Output from '../Output'

export const schema = z.object({
flowers: z.array(z.string().min(1)).min(1).max(10),
})

const Arrays = ({ onSubmit }: { onSubmit: SubmitHandler<typeof schema> }) => {
const { fields, handleSubmit, isDirty } = useForm({ schema, initialValues: { flowers: [''] } })

return <>
<form onSubmit={handleSubmit(onSubmit)}>
<label>Flower names</label>

{getValue(fields.flowers)?.map((_value, i) =>
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 20 }}>
<input {...fields.flowers[i].register()} placeholder={`Flower ${i + 1}`} type="text" />
<button
type="button"
onClick={() => setValue(fields.flowers, value => value?.filter((_, j) => j !== i))}
style={{ margin: 0, width: 100 }}
>Delete</button>
</div>
)}

<button
type="button"
onClick={() => setValue(fields.flowers, value => [...value ?? [], ''])}
>Add flower</button>

<Errors field={fields.flowers} className="error" />

<button>Save changes</button>
</form>

<Output isDirty={isDirty} fields={fields} />
</>
}

export default Arrays
69 changes: 69 additions & 0 deletions examples/basic/basic.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import { z } from 'zod'
import { SubmitHandler } from '@stevent-team/react-zoom-form'
import userEvent from '@testing-library/user-event'
import Basic, { schema } from '.'


describe('Simple flat form with text values', () => {
const setup = (onSubmit?: SubmitHandler<typeof schema>) => ({
user: userEvent.setup(),
...render(<Basic onSubmit={onSubmit ?? (() => void {})} />)
})

it('renders inputs with the given names', async () => {
// Render the form
const { container } = setup()

// Look for the inputs
for (const inputName of Object.keys(schema.shape)) {
const input = container.querySelector(`#${inputName}`)
expect(input?.getAttribute('name')).toBe(inputName)
}
})

it('submit recieves registered values', async () => {
// Render form + track values
let formValues = null
const { user, container } = setup((values: z.infer<typeof schema>) => {
formValues = values
})

const submitButton = screen.getByRole('button')
const nameInput = container.querySelector('#name')
const ageInput = container.querySelector('#age')

expect(nameInput).not.toBeNull()
expect(ageInput).not.toBeNull()

await user.type(nameInput as Element, 'john')
await user.type(ageInput as Element, '18')
await user.click(submitButton)

expect(formValues).toBeTruthy()
expect(formValues).toEqual({
name: 'john',
age: 18,
})
})

it('validates missing fields', async () => {
// Render form + spy on submissions
const onSubmit = vi.fn()
const { user, container } = setup(onSubmit)

// Enter a name but not an age
const submitButton = screen.getByRole('button')
const nameInput = container.querySelector('#name')
await user.type(nameInput as Element, 'john')
await user.click(submitButton)

// Validation should fail because missing age
// thus shouldn't call on submit
expect(onSubmit).not.toHaveBeenCalled()

// Should show errors
expect(container.querySelectorAll('.error')).toHaveLength(1)
})
})
Loading

0 comments on commit ed27e64

Please sign in to comment.