|
|
@@ -14,11 +14,114 @@ Powered by [formxtra](https://code.modal.sh/modal-soft/formxtra). |
|
|
|
|
|
|
|
* 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 Next) |
|
|
|
* 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(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(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(() => { |
|
|
|
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 |
|
|
|
|
|
|
|
* Content negotiation (custom request data) |
|
|
|
* Tests |
|
|
|
* Remix support |
|
|
|
- [X] Content negotiation (custom request data) |
|
|
|
- [ ] Tests |
|
|
|
- [ ] Remix support |