Use forms with or without client-side JavaScript--no code duplication required!
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

README.md 3.0 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. # `iceform`
  2. Use forms with or without client-side JavaScript--no code duplication required!
  3. Powered by [formxtra](https://code.modal.sh/TheoryOfNekomata/formxtra).
  4. ## Why?
  5. * Remove dependence from client-side JavaScript (graceful degradataion)
  6. * Allow scriptability of forms
  7. * Improved accessibility
  8. ## Features
  9. * Emulate client-side behavior in a purely server-side manner
  10. * HTTP compliant (respects status code semantics)
  11. * Easy setup (adapts to how receiving requests are made in the framework of choice)
  12. ## Usage
  13. `iceform` defines two routes in the backend component: the API route used by client-side JavaScript, and the action
  14. route used by the form when client-side JavaScript is not available.
  15. ### Next.js
  16. First, define the common business logic:
  17. ```ts
  18. // [src/]handlers/greet.ts
  19. import {NextApiHandler} from 'next';
  20. export const greet: NextApiHandler = async (req, res) => {
  21. const { name } = req.body;
  22. res.status(202).json({
  23. name: `Hello ${name}`,
  24. });
  25. };
  26. ```
  27. Then define the API route:
  28. ```ts
  29. // [src/]pages/api/greet.ts
  30. import * as Iceform from '@modal-sh/iceform-next';
  31. import { greet } from '@/handlers/greet';
  32. const handler = Iceform.action.wrapApiHandler(greet);
  33. // you can extend the route config by passing an extra argument
  34. export const config = Iceform.action.getApiConfig();
  35. export default handler;
  36. ```
  37. Next, define the action route:
  38. ```ts
  39. // [src/]pages/a/greet.ts
  40. //
  41. // (we use `/a/**` routes but feel free to use something else)
  42. import {NextPage} from 'next';
  43. import * as Iceform from '@modal-sh/iceform-next';
  44. import {greet} from '@/handlers/greet';
  45. // render anything while form is being processed, or just return an empty component here
  46. const ActionGreetPage: NextPage = () => null;
  47. export default ActionGreetPage;
  48. export const getServerSideProps = Iceform.action.getServerSideProps(greet);
  49. ```
  50. Lastly, define the form page:
  51. ```ts
  52. // [src/]pages/form.tsx
  53. import * as React from 'react';
  54. import * as Iceform from '@modal-sh/iceform-next';
  55. const FormPage: Iceform.NextPage = ({
  56. req,
  57. res,
  58. }) => {
  59. const {response, ...isoformProps} = Iceform.useResponse(res);
  60. // you may access the server-side body on `res.body`
  61. const [responseData, setResponseData] = React.useState<unknown>();
  62. React.useEffect(() => {
  63. // response.bodyUsed might be undefined, so we use a strict comparison
  64. if (response?.bodyUsed === false) {
  65. response?.json().then((responseData) => {
  66. setResponseData(responseData);
  67. });
  68. }
  69. }, [response]);
  70. // set up your form fields
  71. return (
  72. <Iceform.Form
  73. {...isoformProps}
  74. method="post"
  75. action="/a/greet"
  76. clientAction="/api/greet"
  77. >
  78. <input
  79. type="text"
  80. name="name"
  81. />
  82. <button
  83. type="submit"
  84. >
  85. Submit
  86. </button>
  87. </Iceform.Form>
  88. )
  89. };
  90. export const getServerSideProps = Iceform.destination.getServerSideProps();
  91. export default GreetPage;
  92. ```
  93. In theory, any API route may have a corresponding action route.
  94. ## TODO
  95. - [X] Content negotiation (custom request data)
  96. - [ ] Tests
  97. - [ ] Remix support