Use forms with or without client-side JavaScript--no code duplication required!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
TheoryOfNekomata 0dfe9aa6bb Refactor, add tests 7 months ago
packages Refactor, add tests 7 months ago
.editorconfig Initial commit 7 months ago
.gitignore Initial commit 7 months ago
README.md Refactor code 7 months ago
pnpm-lock.yaml Refactor, add tests 7 months ago
pnpm-workspace.yaml Initial commit 7 months ago

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
  • Improved accessibility

Features

  • Emulate client-side behavior in a purely server-side manner
  • HTTP compliant (respects status code semantics)
  • 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