# `iceform` Use forms with or without client-side JavaScript--no code duplication required! Powered by [formxtra](https://code.modal.sh/TheoryOfNekomata/formxtra). ## Why? * Remove dependence from client-side JavaScript (graceful degradataion) * Allow scriptability of forms * Improve accessibility ## Features * Emulate client-side behavior in a purely server-side manner * Observe HTTP compliance (respects status code semantics) * Allow easy setup (adapts to how receiving requests are made in the framework of choice) ## Usage `iceform` defines two routes in the backend component: the API route used by client-side JavaScript, and the action route used by the form when client-side JavaScript is not available. ### Next.js First, define the common business logic: ```ts // [src/]handlers/greet.ts import {NextApiHandler} from 'next'; export const greet: NextApiHandler = async (req, res) => { const { name } = req.body; res.status(202).json({ name: `Hello ${name}`, }); }; ``` Then define the API route: ```ts // [src/]pages/api/greet.ts import * as Iceform from '@modal-sh/iceform-next'; import { greet } from '@/handlers/greet'; const handler = Iceform.action.wrapApiHandler({ fn: greet }); // you can extend the route config by passing an extra argument export const config = Iceform.action.getApiConfig(); export default handler; ``` Next, define the action route: ```ts // [src/]pages/a/greet.ts // // (we use `/a/**` routes but feel free to use something else) import {NextPage} from 'next'; import * as Iceform from '@modal-sh/iceform-next'; import {greet} from '@/handlers/greet'; // render anything while form is being processed, or just return an empty component here const ActionGreetPage: NextPage = () => null; export default ActionGreetPage; export const getServerSideProps = Iceform.action.getServerSideProps({ fn: greet }); ``` Lastly, define the form page: ```tsx // [src/]pages/form.tsx import * as React from 'react'; import * as Iceform from '@modal-sh/iceform-next'; const FormPage: Iceform.NextPage = ({ req, res, }) => { const {response, ...isoformProps} = Iceform.useResponse(res); // you may access the server-side body on `res.body` const [responseData, setResponseData] = React.useState(); React.useEffect(() => { // response.bodyUsed might be undefined, so we use a strict comparison if (response?.bodyUsed === false) { response?.json().then((responseData) => { setResponseData(responseData); }); } }, [response]); // set up your form fields return ( ) }; export const getServerSideProps = Iceform.destination.getServerSideProps(); export default GreetPage; ``` In theory, any API route may have a corresponding action route. ## TODO - [X] Content negotiation (custom request data) - [ ] `
` (on hold, see https://github.com/whatwg/html/issues/9625) - [ ] Tests - [X] Form with redirects - [X] Form with files - [ ] Documentation - [ ] Remix support - [ ] `accept-charset=""` attribute support - [X] Method override support - [ ] Integration with Next router (iceform-next) - [ ] Investigate bug of not cleaning response body cache and flash messages via cookies