Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: SSR/Next Server Actions Support #528

Merged
merged 31 commits into from
Dec 31, 2023
Merged

Conversation

crutchcorn
Copy link
Member

@crutchcorn crutchcorn commented Dec 13, 2023

This PR improves on our SSR story by implementing many of the ideas outlined in #480

IE; we now support Next/React Server Actions like so:

// action.ts
'use server'

import { formFactory } from './shared-code'

export default async function someAction(prev: unknown, formData: FormData) {
  return await formFactory.validateFormData(formData)
}
// shared-code.ts
import { createFormFactory } from '@tanstack/react-form'

export const formFactory = createFormFactory({
  defaultValues: {
    firstName: '',
    age: 0,
  },
  onServerValidate({ value }) {
    if (value.age < 12) {
      return 'Server validation: You must be at least 12 to sign up'
    }
  },
})
/// <reference types="../../node_modules/@types/react-dom/experimental" />
'use client'

import { useFormState } from 'react-dom'
import someAction from './action'
import { formFactory } from './shared-code'
import { useTransform, mergeForm, FormApi } from '@tanstack/react-form'

export const ClientComp = () => {
  const [state, action] = useFormState(someAction, formFactory.initialFormState)

  const form = formFactory.useForm({
    transform: useTransform(
      (baseForm: FormApi<any, any>) => mergeForm(baseForm, state),
      [state],
    ),
  })

  const formErrors = form.useStore((state) => state.errors)

  return (
    <form.Provider>
      <form action={action as never} onSubmit={() => form.handleSubmit()}>
        {formErrors.map((error) => (
          <p key={error as string}>{error}</p>
        ))}

        <form.Field
          name="age"
          validators={{
            onChange: ({ value }) =>
              value < 8
                ? 'Client validation: You must be at least 8'
                : undefined,
          }}
        >
          {(form) => {
            return (
              <div>
                <input
                  name="age"
                  type="number"
                  value={form.state.value}
                  onChange={(e) => form.handleChange(e.target.valueAsNumber)}
                />
                {form.state.meta.errors.map((error) => (
                  <p key={error as string}>{error}</p>
                ))}
              </div>
            )
          }}
        </form.Field>
        <form.Subscribe
          selector={(state) => [state.canSubmit, state.isSubmitting]}
          children={([canSubmit, isSubmitting]) => (
            <button type="submit" disabled={!canSubmit}>
              {isSubmitting ? '...' : 'Submit'}
            </button>
          )}
        />
      </form>
    </form.Provider>
  )
}

To do this, we've implemented:

  • Migrating to Vite over TSUp for all packages, fixing long-standing issues
  • Working Next.js demo
  • Working Remix demo (Need help with this, not blocking)
  • A transform API in core
    • Integration tests
    • Docs
  • A mergeForm API
    • Unit tests
    • Docs
  • useTransform API in React
    • Integration tests
    • Docs
  • A fix to a long-standing issue with Next.js usage (Closes "A component is changing a controlled input to be uncontrolled" in Next.js App router #523)
  • SSR/RSC Docs
  • Accompanying personal blog post about RSC/RSA

Copy link

codesandbox-ci bot commented Dec 13, 2023

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@codecov-commenter
Copy link

codecov-commenter commented Dec 13, 2023

Codecov Report

Attention: Patch coverage is 75.00000% with 25 lines in your changes missing coverage. Please review.

Project coverage is 90.79%. Comparing base (c2f9957) to head (cce56b1).
Report is 343 commits behind head on main.

Files Patch % Lines
packages/react-form/src/validateFormData.ts 16.66% 8 Missing and 2 partials ⚠️
packages/form-core/src/FormApi.ts 81.81% 4 Missing ⚠️
packages/form-core/src/utils.ts 55.55% 4 Missing ⚠️
packages/form-core/src/mergeForm.ts 85.71% 3 Missing ⚠️
packages/react-form/src/useTransform.ts 0.00% 2 Missing ⚠️
packages/form-core/src/FieldApi.ts 88.88% 1 Missing ⚠️
packages/react-form/src/useIsomorphicEffectOnce.ts 94.11% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #528      +/-   ##
==========================================
+ Coverage   84.55%   90.79%   +6.23%     
==========================================
  Files           9       25      +16     
  Lines         395      728     +333     
  Branches      109      180      +71     
==========================================
+ Hits          334      661     +327     
- Misses         52       62      +10     
+ Partials        9        5       -4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@lachlancollins
Copy link
Member

@crutchcorn I think I'm a bit closer to figuring out the types problem.

Firstly, the vue examples started failing on this commit: 6520695 (#528)

Removing export * from './mergeForm' allows the vue examples to build. This also works on the latest commit.

I don't really know what to make of this, but I hope it gives a bit more direction. I'm going to submit a few tweaks on main that may or may not help out.

This was referenced Dec 29, 2023
Copy link
Member

@lachlancollins lachlancollins left a comment

Choose a reason for hiding this comment

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

Looks great! Here's my review, only small stuff.

nx.json Outdated Show resolved Hide resolved
packages/form-core/package.json Outdated Show resolved Hide resolved
Copy link
Member

@lachlancollins lachlancollins left a comment

Choose a reason for hiding this comment

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

LGTM! 🚀

lachlancollins and others added 3 commits December 31, 2023 13:50
* chore: Update CI versions of node and pnpm (#538)

* Update node and pnpm for CI

* Update concurrency and run conditions

* docs(CONTRIBUTING.md): add instructions for previewing the docs locally (#537)

* chore: Update to Nx v17 (#539)

* Update CI run condition

* Update to Nx v17

* Attempt to fix scripts

* Fully utilise Nx for PR workflow

* chore: Use updated `publish.js` script (#540)

* Initial rename and copy

* Update relevant packages

* Remove ts-node

* Mark root as ESM

* Move getTsupConfig

* Remove eslint-plugin-compat

* Make codesandbox run Node 18

* chore: Add missing command to CI workflow (#541)

* chore: Enable Nx distributed caching (#542)

* chore: Update prettier config (#543)

* Update prettier config

* Run format

* Update gitignore

---------

Co-authored-by: fuko <[email protected]>
@lachlancollins lachlancollins changed the title [WIP] SSR/Next Server Actions Support feat: SSR/Next Server Actions Support Dec 31, 2023
@lachlancollins lachlancollins marked this pull request as ready for review December 31, 2023 04:57
@crutchcorn crutchcorn merged commit f0cd7c1 into main Dec 31, 2023
2 checks passed
@crutchcorn crutchcorn deleted the nextjs-server-actions branch December 31, 2023 05:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

"A component is changing a controlled input to be uncontrolled" in Next.js App router
3 participants