|
| 1 | +--- |
| 2 | +title: useFormStatus |
| 3 | +canary: true |
| 4 | +--- |
| 5 | + |
| 6 | +<Canary> |
| 7 | + |
| 8 | +The `useFormStatus` Hook is currently only available in React's canary and experimental channels. Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). |
| 9 | + |
| 10 | +</Canary> |
| 11 | + |
| 12 | +<Intro> |
| 13 | + |
| 14 | +`useFormStatus` is a Hook that gives you status information of the last form submission. |
| 15 | + |
| 16 | +```js |
| 17 | +const { pending, data, method, action } = useFormStatus(); |
| 18 | +``` |
| 19 | + |
| 20 | +</Intro> |
| 21 | + |
| 22 | +<InlineToc /> |
| 23 | + |
| 24 | +--- |
| 25 | + |
| 26 | +## Reference {/*reference*/} |
| 27 | + |
| 28 | +### `useFormStatus()` {/*use-form-status*/} |
| 29 | + |
| 30 | +The `useFormStatus` Hook provides status information of the last form submission. |
| 31 | + |
| 32 | +```js {4},[[1, 5, "status.pending"]] |
| 33 | +import action from './actions'; |
| 34 | + |
| 35 | +function Submit() { |
| 36 | + const status = useFormStatus(); |
| 37 | + return <button disabled={status.pending}>Submit</button> |
| 38 | +} |
| 39 | + |
| 40 | +export default App() { |
| 41 | + return ( |
| 42 | + <form action={action}> |
| 43 | + <Submit /> |
| 44 | + </form> |
| 45 | + ); |
| 46 | +} |
| 47 | +``` |
| 48 | + |
| 49 | +To get status information, the `Submit` component must be rendered within a `<form>`. The Hook returns information like the <CodeStep step={1}>`pending`</CodeStep> property which tells you if the form is actively submitting. |
| 50 | + |
| 51 | +In the above example, `Submit` uses this information to disable `<button>` presses while the form is submitting. |
| 52 | + |
| 53 | +[See more examples below.](#usage) |
| 54 | + |
| 55 | +#### Parameters {/*parameters*/} |
| 56 | + |
| 57 | +`useFormStatus` does not take any parameters. |
| 58 | + |
| 59 | +#### Returns {/*returns*/} |
| 60 | + |
| 61 | +A `status` object with the following properties: |
| 62 | + |
| 63 | +* `pending`: A boolean. If `true`, this means the parent `<form>` is pending submission. Otherwise, `false`. |
| 64 | + |
| 65 | +* `data`: An object implementing the [`FormData interface`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) that contains the data the parent `<form>` is submitting. If there is no active submission or no parent `<form>`, it will be `null`. |
| 66 | + |
| 67 | +* `method`: A string value of either `'get'` or `'post'`. This represents whether the parent `<form>` is submitting with either a `GET` or `POST` [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). By default, a `<form>` will use the `GET` method and can be specified by the [`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#method) property. |
| 68 | + |
| 69 | +[//]: # (Link to `<form>` documentation. "Read more on the `action` prop on `<form>`.") |
| 70 | +* `action`: A reference to the function passed to the `action` prop on the parent `<form>`. If there is no parent `<form>`, the property is `null`. If there is a URI value provided to the `action` prop, or no `action` prop specified, `status.action` will be `null`. |
| 71 | + |
| 72 | +#### Caveats {/*caveats*/} |
| 73 | + |
| 74 | +* The `useFormStatus` Hook must be called from a component that is rendered inside a `<form>`. |
| 75 | +* `useFormStatus` will only return status information for a parent `<form>`. It will not return status information for any `<form>` rendered in that same component or children components. |
| 76 | + |
| 77 | +--- |
| 78 | + |
| 79 | +## Usage {/*usage*/} |
| 80 | + |
| 81 | +### Display a pending state during form submission {/*display-a-pending-state-during-form-submission*/} |
| 82 | +To display a pending state while a form is submitting, you can call the `useFormStatus` Hook in a component rendered in a `<form>` and read the `pending` property returned. |
| 83 | + |
| 84 | +Here, we use the `pending` property to indicate the form is submitting. |
| 85 | + |
| 86 | +<Sandpack> |
| 87 | + |
| 88 | +```js App.js |
| 89 | +import { useFormStatus } from "react-dom"; |
| 90 | +import { submitForm } from "./actions.js"; |
| 91 | + |
| 92 | +function Submit() { |
| 93 | + const { pending } = useFormStatus(); |
| 94 | + return ( |
| 95 | + <button type="submit" disabled={pending}> |
| 96 | + {pending ? "Submitting..." : "Submit"} |
| 97 | + </button> |
| 98 | + ); |
| 99 | +} |
| 100 | + |
| 101 | +function Form({ action }) { |
| 102 | + return ( |
| 103 | + <form action={action}> |
| 104 | + <Submit /> |
| 105 | + </form> |
| 106 | + ); |
| 107 | +} |
| 108 | + |
| 109 | +export default function App() { |
| 110 | + return <Form action={submitForm} />; |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +```js actions.js hidden |
| 115 | +export async function submitForm(query) { |
| 116 | + await new Promise((res) => setTimeout(res, 1000)); |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +```json package.json hidden |
| 121 | +{ |
| 122 | + "dependencies": { |
| 123 | + "react": "canary", |
| 124 | + "react-dom": "canary", |
| 125 | + "react-scripts": "^5.0.0" |
| 126 | + }, |
| 127 | + "main": "/index.js", |
| 128 | + "devDependencies": {} |
| 129 | +} |
| 130 | +``` |
| 131 | +</Sandpack> |
| 132 | + |
| 133 | +<Pitfall> |
| 134 | + |
| 135 | +##### `useFormStatus` will not return status information for a `<form>` rendered in the same component. {/*useformstatus-will-not-return-status-information-for-a-form-rendered-in-the-same-component*/} |
| 136 | + |
| 137 | +The `useFormStatus` Hook only returns status information for a parent `<form>` and not for any `<form>` rendered in the same component calling the Hook, or child components. |
| 138 | + |
| 139 | +```js |
| 140 | +function Form() { |
| 141 | + // 🚩 `pending` will never be true |
| 142 | + // useFormStatus does not track the form rendered in this component |
| 143 | + const { pending } = useFormStatus(); |
| 144 | + return <form action={submit}></form>; |
| 145 | +} |
| 146 | +``` |
| 147 | + |
| 148 | +Instead call `useFormStatus` from inside a component that is located inside `<form>`. |
| 149 | + |
| 150 | +```js |
| 151 | +function Submit() { |
| 152 | + // ✅ `pending` will be derived from the form that wraps the Submit component |
| 153 | + const { pending } = useFormStatus(); |
| 154 | + return <button disabled={pending}>...</button>; |
| 155 | +} |
| 156 | + |
| 157 | +function Form() { |
| 158 | + // This is the <form> `useFormStatus` tracks |
| 159 | + return ( |
| 160 | + <form action={submit}> |
| 161 | + <Submit /> |
| 162 | + </form> |
| 163 | + ); |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +</Pitfall> |
| 168 | + |
| 169 | +### Read the form data being submitted {/*read-form-data-being-submitted*/} |
| 170 | + |
| 171 | +You can use the `data` property of the status information returned from `useFormStatus` to display what data is being submitted by the user. |
| 172 | + |
| 173 | +Here, we have a form where users can request a username. We can use `useFormStatus` to display a temporary status message confirming what username they have requested. |
| 174 | + |
| 175 | +<Sandpack> |
| 176 | + |
| 177 | +```js UsernameForm.js active |
| 178 | +import {useState, useMemo, useRef} from 'react'; |
| 179 | +import {useFormStatus} from 'react-dom'; |
| 180 | + |
| 181 | +export default function UsernameForm() { |
| 182 | + const {pending, data} = useFormStatus(); |
| 183 | + |
| 184 | + const [showSubmitted, setShowSubmitted] = useState(false); |
| 185 | + const submittedUsername = useRef(null); |
| 186 | + const timeoutId = useRef(null); |
| 187 | + |
| 188 | + useMemo(() => { |
| 189 | + if (pending) { |
| 190 | + submittedUsername.current = data?.get('username'); |
| 191 | + if (timeoutId.current != null) { |
| 192 | + clearTimeout(timeoutId.current); |
| 193 | + } |
| 194 | + |
| 195 | + timeoutId.current = setTimeout(() => { |
| 196 | + timeoutId.current = null; |
| 197 | + setShowSubmitted(false); |
| 198 | + }, 2000); |
| 199 | + setShowSubmitted(true); |
| 200 | + } |
| 201 | + }, [pending, data]); |
| 202 | + |
| 203 | + return ( |
| 204 | + <> |
| 205 | + <label>Request a Username: </label><br /> |
| 206 | + <input type="text" name="username" /> |
| 207 | + <button type="submit" disabled={pending}> |
| 208 | + {pending ? 'Submitting...' : 'Submit'} |
| 209 | + </button> |
| 210 | + {showSubmitted ? ( |
| 211 | + <p>Submitted request for username: {submittedUsername.current}</p> |
| 212 | + ) : null} |
| 213 | + </> |
| 214 | + ); |
| 215 | +} |
| 216 | +``` |
| 217 | +
|
| 218 | +```js App.js |
| 219 | +import UsernameForm from './UsernameForm'; |
| 220 | +import { submitForm } from "./actions.js"; |
| 221 | + |
| 222 | +export default function App() { |
| 223 | + return ( |
| 224 | + <form action={submitForm}> |
| 225 | + <UsernameForm /> |
| 226 | + </form> |
| 227 | + ); |
| 228 | +} |
| 229 | +``` |
| 230 | +
|
| 231 | +```js actions.js hidden |
| 232 | +export async function submitForm(query) { |
| 233 | + await new Promise((res) => setTimeout(res, 1000)); |
| 234 | +} |
| 235 | +``` |
| 236 | +
|
| 237 | +```json package.json hidden |
| 238 | +{ |
| 239 | + "dependencies": { |
| 240 | + "react": "canary", |
| 241 | + "react-dom": "canary", |
| 242 | + "react-scripts": "^5.0.0" |
| 243 | + }, |
| 244 | + "main": "/index.js", |
| 245 | + "devDependencies": {} |
| 246 | +} |
| 247 | +``` |
| 248 | +</Sandpack> |
| 249 | +
|
| 250 | +--- |
| 251 | +
|
| 252 | +## Troubleshooting {/*troubleshooting*/} |
| 253 | +
|
| 254 | +### `status.pending` is never `true` {/*pending-is-never-true*/} |
| 255 | +
|
| 256 | +`useFormStatus` will only return status information for a parent `<form>`. |
| 257 | +
|
| 258 | +If the component that calls `useFormStatus` is not nested in a `<form>`, `status.pending` will always return `false`. Verify `useFormStatus` is called in a component that is a child of a `<form>` element. |
| 259 | +
|
| 260 | +`useFormStatus` will not track the status of a `<form>` rendered in the same component. See [Pitfall](#useformstatus-will-not-return-status-information-for-a-form-rendered-in-the-same-component) for more details. |
0 commit comments