`useOptimistic` to Make Your App Feel Instant
Make your React apps feel instant with the new useOptimistic hook from React 19. Improve UX by updating UI immediately while async actions complete.


Here's a form in JSX:
function UsernameForm({onSubmitUsername}) { function handleSubmit(event) { event.preventDefault() onSubmitUsername(event.currentTarget.elements.usernameInput.value) } return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="usernameInput">Username:</label> <input id="usernameInput" type="text" /> </div> <button type="submit">Submit</button> </form> )}
Let's type that handleSubmit function. Here's how some people do it (copying this approach from some blog posts and "semi-official guides" I've seen):
function handleSubmit(event: React.SyntheticEvent<HTMLFormElement>) { event.preventDefault() const form = event.currentTarget const formElements = form.elements as typeof form.elements & { usernameInput: {value: string} } onSubmitUsername(formElements.usernameInput.value)}
The reason we have to have the as there is because TypeScript isn't quite smart enough to know what elements we're rendering in our form, so we have to use the type cast which I'm not a fan of, but you gotta ship right?
My first improvement to this is to change that usernameInput type:
- usernameInput: {value: string}+ usernameInput: HTMLInputElement
Definitely take advantage of the type it actually is rather than just cherry-picking the values you need.
The second improvement is:
- function handleSubmit(event: React.SyntheticEvent<HTMLFormElement>) {+ function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
Incidentally, at the time of this writing, there's no substantive difference in those types, but I prefer to be more clear and accurate with the name of the type, so that's what we're going to go with.
So here we are now:
function handleSubmit(event: React.FormEvent<HTMLFormElement>) { event.preventDefault() const form = event.currentTarget const formElements = form.elements as typeof form.elements & { usernameInput: HTMLInputElement } onSubmitUsername(formElements.usernameInput.value)}
But even with those changes, I'm not a fan of this for three reasons:
form anywhere else, I'll either have to duplicate the code or extract the type and cast it everywhere I use the form. 3. I don't like seeing as in my code because it is a signal that I'm telling the TypeScript compiler to pipe down (be less helpful). So I try to avoid it when possible. And it is possible!Keep in mind that we're the ones telling TypeScript what that event.currentTarget type is. We tell TypeScript when we specify the type for our event. Right now that's set to React.FormEvent<HTMLFormElement>. So we're telling TypeScript that event.currentTarget is an HTMLFormElement but then we immediately tell TypeScript that this isn't quite right by using as. What if instead we just tell TypeScript more accurately what it is at the start? Yeah, let's do that.
interface FormElements extends HTMLFormControlsCollection { usernameInput: HTMLInputElement}interface UsernameFormElement extends HTMLFormElement { // now we can override the elements type to be an HTMLFormControlsCollection // of our own design... readonly elements: FormElements}
So in reality, our form is an HTMLFormElement with some known elements. So we extend HTMLFormElement and override the elements to have the elements we want it to. The HTMLFormElement['elements'] type is a HTMLFormControlsCollection, so make our own version of that interface as well.
With that, now we can update our type and get rid of all the type casting!
Here's the whole thing altogether:
import * as React from 'react'interface FormElements extends HTMLFormControlsCollection { usernameInput: HTMLInputElement}interface UsernameFormElement extends HTMLFormElement { readonly elements: FormElements}function UsernameForm({ onSubmitUsername,}: { onSubmitUsername: (username: string) => void}) { function handleSubmit(event: React.FormEvent<UsernameFormElement>) { event.preventDefault() onSubmitUsername(event.currentTarget.elements.usernameInput.value) } return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="usernameInput">Username:</label> <input id="usernameInput" type="text" /> </div> <button type="submit">Submit</button> </form> )}
So now:
handleSubmit code, allowing us to focus on what the function is doing. 2. If we need to use this form somewhere else, we can give it the UsernameFormElement type and get all the type help we need. 3. We don't have to use a type cast, so TypeScript can be more useful for us.I hope that's helpful to you!
Delivered straight to your inbox.
Make your React apps feel instant with the new useOptimistic hook from React 19. Improve UX by updating UI immediately while async actions complete.

Whether you're brand new to building dynamic web applications or you've been working with React for a long time, let's contextualize the most widely used UI framework in the world: React.

Master focus management in React with flushSync. Learn why batching matters, when to use flushSync, and how to create accessible, keyboard-friendly UIs.

A basic introduction memoization and how React memoization features work.
