|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- # `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<unknown>();
- 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 (
- <Iceform.Form
- {...isoformProps}
- method="post"
- action="/a/greet"
- clientAction="/api/greet"
- >
- <input
- type="text"
- name="name"
- />
- <button
- type="submit"
- >
- Submit
- </button>
- </Iceform.Form>
- )
- };
-
- 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)
- - [ ] `<form method="dialog">` (on hold, see https://github.com/whatwg/html/issues/9625)
- - [ ] Tests
- - [ ] Form with redirects
- - [ ] Form with files
- - [ ] Documentation
- - [ ] Remix support
- - [ ] `accept-charset=""` attribute support
|