-
-
Notifications
You must be signed in to change notification settings - Fork 355
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
[Tracking] SSR & RSC Usage #480
Comments
Love this. Right now we have a blocking bug via #470, but this is a good addition |
Removing the import { useFormState } from "react-dom";
import {
createFormFactory,
// Proposed API
useTransform,
// Proposed API
mergeForm
} from "@tanstack/react-form";
const {
useForm,
// Proposed API
validateFormData,
// Proposed API
// This allows us to ensure that the types of `useFormState`'s `state`
// is the same as returned from onServerValidate without having to force the
// user to manually replicate
initialFormState,
} = createFormFactory({
defaultValues: {
name: "",
age: 0,
},
// Proposed API
onServerValidate: async (values) => {
if (values.name.includes("server_error")) {
return {
name: "This is a server error",
};
}
},
});
async function submitForm(formData) {
"use server";
const results = await validateFormData({ formData });
if (results) return results;
}
export default function CreatePerson() {
const [state, dispatch] = useFormState(submitForm, initialFormState);
const form = useForm({
/**
* Proposed API
*
* Transforms under-the-hood to a non-framework agnostic:
* { fn: formBase => mergeForm(formBase, state),
* deps: [state] }
*
* Which would allow us to watch the deps changes in `form-core` and run the relevant functions ourselves
*
* But is a custom hook because it allows us to have auto-fixed deps without
* writing our own ESLint plugin:
*
* @see https://www.npmjs.com/package/eslint-plugin-react-hooks#advanced-configuration
*/
transform: useTransform((formBase) => mergeForm(formBase, state), [state]),
});
return (
<form.Provider>
<form action={dispatch}>
<form.Field
name="name"
onChange={(val) =>
val.includes("client_error") ? "This is a client error" : ""
}
>
{(field) => (
<>
<label htmlFor={field.name}>First Name:</label>
<input
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
{field.state.meta.errors ? (
<>
{field.state.meta.errors.map((error) => (
// Merge client errors and form errors
<div key={error}>{error}</div>
))}
</>
) : null}
</>
)}
</form.Field>
<button type="submit">Submit</button>
</form>
</form.Provider>
);
} Using this API, we believe we're able to support:
And support progressively enhanced (disable JS, still see form errors via page refresh), isomorphic (client and server validation support using the same API) form valdiation. I'd love to hear anyone's thoughts on this API or our approach. |
Just got off a call with @Fredkiss3 (Server actions superstar). Turns out we have a few problems with our potential API:
This means that we can change our usable API slightly to something like so, where we first define our shared state: // form-base.ts
import {
createFormFactory,
} from "@tanstack/react-form";
const {
useForm,
validateFormData,
initialFormState,
} = createFormFactory({
defaultValues: {
name: "",
age: 0,
},
// Proposed API
onServerValidate: async (values) => {
if (values.name.includes("server_error")) {
return {
name: "This is a server error",
};
}
},
});
export {
useForm,
validateFormData,
initialFormState
} Then migrate our action to a "use server"
// form-action.ts
import {
validateFormData,
} from "./form-base";
export default async function submitForm(formData) {
const results = await validateFormData({ formData });
if (results) return results;
} And finally we can use this in our client comp: "use client"
import { useFormState } from "react-dom";
import {
useTransform,
mergeForm
} from "@tanstack/react-form";
import {useForm, initialFormState} from "./form-base";
import { submitForm } from "./form-action";
export default function CreatePerson() {
const [state, dispatch] = useFormState(submitForm, initialFormState);
const form = useForm({
transform: useTransform((formBase) => mergeForm(formBase, state), [state]),
});
return (
<form.Provider>
<form action={dispatch}>
<form.Field
name="name"
onChange={(val) =>
val.includes("client_error") ? "This is a client error" : ""
}
>
{(field) => (
<>
<label htmlFor={field.name}>First Name:</label>
<input
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
{field.state.meta.errors ? (
<>
{field.state.meta.errors.map((error) => (
// Merge client errors and form errors
<div key={error}>{error}</div>
))}
</>
) : null}
</>
)}
</form.Field>
<button type="submit">Submit</button>
</form>
</form.Provider>
);
} However, there's a third issue here as well, that I'll outline in the next comment. |
@Fredkiss3 then informed me a major problem with this bit of our POC code: const {
// This will throw an error in a `"use server"` usage
useForm,
validateFormData,
initialFormState,
} = createFormFactory({
// ...
}); This is because when you have a setup that imports from "use client"
import { useFormState } from "react-dom"
import someAction from "./action";
export const ClientComp = () => {
const [data, action] = useFormState(someAction, "Hello client");
return <form action={action}>
<p>{data}</p>
<button type={"submit"}>Update data</button>
</form>
} "use server"
// action.ts
import {data} from "./shared-code";
export default async function someAction() {
return "Hello " + data.name;
} // shared-code.ts
import {useState} from "react";
export const data = {
useForm: <T>(val: T) => {
useState(val)
},
name: "server"
} You'll be presented with the following error:
This is because NextJS statistically analyzes usage of This has been written about here: https://phryneas.de/react-server-components-controversy And documented in this issue: apollographql/apollo-client#10974 It seems like there may be a workaround here: apollographql/apollo-client#10974 (comment) But it's unclear if that's still needed per: I'll reach out to some folks and see if there's anything else I can learn prior to prototyping and shipping |
The "safe" workaround currently suggested by the React team is if (Object(React).useState) { because the To be honest, I don't feel comfortable with that workaround (another future bundler could still detect it and we'd end up in an endless cat-and-mouse game), so Apollo Client will likely use rehackt as a wrapper around React. Small warning about that package: It has not been fully reviewed yet - reviews welcome :) All that said: the "official" solution would be to put an |
* docs: add initial NextJS RSC code sample * chore: replace react imports with rehackt imports #480 (comment) * chore: initial work to add server action support to React Form * feat: got initial demo of NextJS server action error to work * feat: add useTransform and mergeForm APIs * chore: WIP * feat: add transform array checking * fix: issues with canSubmit state issues when using server validation * fix: remove error when Field component is first ran * fix: correct failing tests * chore: fix ci/cd * chore: fix next server action typings * chore: fix various issues with templates and CI/CD * chore: upgrade node version * chore: change from tsup to manual rollup * Revert "chore: change from tsup to manual rollup" This reverts commit 994c85c. * chore: attempt to fix tsup issues * Revert "chore: attempt to fix tsup issues" This reverts commit b9b1f07. * chore: migrate form-core to use Vite * chore: migrate Vue package to Vite * docs: migrate form adapters to vite * chore: migrate solid and react packages to use vite * chore: refactor to single config generator * chore: remove typescript 4.8 * chore: fix issues with test ci * chore: fix PR CI * chore: fix clean script * Merge main into nextjs-server-actions (#545) * 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 <43729152+fulopkovacs@users.noreply.github.com> * Fix lockfile --------- Co-authored-by: Lachlan Collins <1667261+lachlancollins@users.noreply.github.com> Co-authored-by: fuko <43729152+fulopkovacs@users.noreply.github.com>
Closing, as we have documented and working support for SSR/Server Actions https://tanstack.com/form/latest/docs/framework/react/guides/ssr |
@cannap, there is no Vue support for SSR TanStack Usage at this time. It should be a relatively light lift, but I just don't have experience in Nuxt |
An increasing number of people use SSR & frameworks like NextJS (page router or app router) and Remix, so we should have a guide section covering that.
Some topics:
The text was updated successfully, but these errors were encountered: