Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/react-form/src/useFieldGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ import type { LensFieldComponent } from './useField'

function LocalSubscribe({
lens,
selector,
selector = (state) => state,
children,
}: PropsWithChildren<{
lens: AnyFieldGroupApi
selector: (state: FieldGroupState<any>) => FieldGroupState<any>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mentioned that "the types already mark the selector as optional", but it wasn't marked here.

A subscription without a selector (or an identity selector) makes no sense, so we shouldn't encourage it. For the sake of semver, the identity function fallback should stay, but the types should enforce that a selector is required.

selector?: (state: FieldGroupState<any>) => FieldGroupState<any>
}>): ReturnType<FunctionComponent> {
const data = useStore(lens.store, selector)

Expand Down
4 changes: 2 additions & 2 deletions packages/react-form/src/useForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,11 @@ export type ReactFormExtendedApi<

function LocalSubscribe({
form,
selector,
selector = (state) => state,
children,
}: PropsWithChildren<{
form: AnyFormApi
selector: (state: AnyFormState) => AnyFormState
selector?: (state: AnyFormState) => AnyFormState
}>): ReturnType<FunctionComponent> {
const data = useStore(form.store, selector)

Expand Down
61 changes: 60 additions & 1 deletion packages/react-form/tests/createFormHook.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { render } from '@testing-library/react'
import { render, waitFor } from '@testing-library/react'
import { formOptions } from '@tanstack/form-core'
import userEvent from '@testing-library/user-event'
import { createFormHook, createFormHookContexts, useStore } from '../src'
Expand Down Expand Up @@ -699,4 +699,63 @@ describe('createFormHook', () => {
const button = getByText('Testing')
expect(button).toBeInTheDocument()
})

it('should render FieldGroup Subscribe without selector (default identity)', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, if the selector will be marked as required, these tests would type error.

Adjust the title to mention the related GH issue or that this is for semver.

const formOpts = formOptions({
defaultValues: {
person: {
firstName: 'FirstName',
lastName: 'LastName',
},
},
})

const ChildFormAsField = withFieldGroup({
defaultValues: formOpts.defaultValues.person,
render: ({ group }) => {
return (
<div>
<group.Field
name="lastName"
children={(field) => (
<label>
Last Name:
<input
data-testid="lastName"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
</label>
)}
/>
<group.Subscribe
children={(state) => (
<span data-testid="state-lastName">
{state.values.lastName}
</span>
)}
/>
</div>
)
},
})

const Parent = () => {
const form = useAppForm({
...formOpts,
})
return <ChildFormAsField form={form} fields="person" />
}

const { getByTestId } = render(<Parent />)
const input = getByTestId('lastName')
const stateLastName = getByTestId('state-lastName')

expect(stateLastName).toHaveTextContent('LastName')

await user.clear(input)
await user.type(input, 'Updated')
await waitFor(() => expect(stateLastName).toHaveTextContent('Updated'))
})
})
41 changes: 41 additions & 0 deletions packages/react-form/tests/useForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1075,4 +1075,45 @@ describe('useForm', () => {
await user.click(getByText('Rerender'))
await findByText('Another')
})

it('should render Subscribe without selector (default identity)', async () => {
function Comp() {
const form = useForm({
defaultValues: {
name: 'test',
},
})

return (
<>
<form.Field
name="name"
children={(field) => (
<input
data-testid="input"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
)}
/>

<form.Subscribe
children={(state) => (
<span data-testid="state-value">{state.values.name}</span>
)}
/>
</>
)
}

const { getByTestId } = render(<Comp />)
const input = getByTestId('input')
const stateValue = getByTestId('state-value')

expect(stateValue).toHaveTextContent('test')

await user.clear(input)
await user.type(input, 'updated')
await waitFor(() => expect(stateValue).toHaveTextContent('updated'))
})
})
Loading