Use forms with or without client-side JavaScript--no code duplication required!
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
TheoryOfNekomata 8230e6549b Implement adjustments to handlers 1年前
packages Implement adjustments to handlers 1年前
.editorconfig Initial commit 1年前
.gitignore Initial commit 1年前
README.md Implement form actions with resource locations 1年前
pnpm-lock.yaml Refactor, add tests 1年前
pnpm-workspace.yaml Initial commit 1年前

README.md

iceform

Use forms with or without client-side JavaScript--no code duplication required!

Powered by 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:

// [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:

// [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:

// [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:

// [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