Async transitions
Server Components
use()
New form hooks
Actions
API updates
React Compiler
Server Components
use()
New form APIs
Actions
API updates
I love to talk about performance, react and design systems
nik72619c
@niksharma1997
Babel plugin
Compiler core
Eslint plugin
Babel plugin
Compiler core
Eslint plugin
Source code Functions or hooks
AST
IR CODE
IR CODE
Optimised Code
Babel plugin
Compiler core
Eslint plugin
Source code Functions or hooks
AST
IR CODE
IR CODE
Optimised Code
Babel plugin
Compiler core
Eslint plugin
Babel plugin
Compiler core
Eslint plugin
Source code Functions or hooks
AST
IR CODE
IR CODE
Optimised Code
Babel plugin
Compiler core
Eslint plugin
useMemo()
useCallback()
npx react-compiler-healthcheck@experimental
npm install eslint-plugin-react-compiler@experimental
// Add the config
module.exports = {
plugins: [
'eslint-plugin-react-compiler',
],
rules: {
'react-compiler/react-compiler': "error",
},
}
/GET
/GET
/GET
/GET
/GET
JS bundle ⚠️
JS bundle ⚠️
?
/GET
/GET
/GET
import db from './database';
async function Note({id}) {
// NOTE: loads *during* render.
const note = await db.notes.get(id);
return (
<div>
<Author id={note.authorId} />
<p>{note}</p>
</div>
);
}
async function Author({id}) {
// NOTE: loads *after* Note,
// but is fast if data is co-located.
const author = await db.authors.get(id);
return <span>By: {author.name}</span>;
}
Can access db or other server resources
import db from './database';
async function Note({id}) {
// NOTE: loads *during* render.
const note = await db.notes.get(id);
return (
<div>
<Author id={note.authorId} />
<p>{note}</p>
</div>
);
}
async function Author({id}) {
// NOTE: loads *after* Note,
// but is fast if data is co-located.
const author = await db.authors.get(id);
return <span>By: {author.name}</span>;
}
Cannot use interactive behaviour
'use client';
import db from './database';
async function Note({id}) {
// NOTE: loads *during* render.
const note = await db.notes.get(id);
return (
<div>
<Author id={note.authorId} />
<p>{note}</p>
</div>
);
}
async function Author({id}) {
// NOTE: loads *after* Note,
// but is fast if data is co-located.
const author = await db.authors.get(id);
return <span>By: {author.name}</span>;
}
Specify what parts are interactive
'use client';
import db from './database';
async function Note({id}) {
// NOTE: loads *during* render.
const note = await db.notes.get(id);
return (
<div>
<Author id={note.authorId} />
<p>{note}</p>
</div>
);
}
async function Author({id}) {
// NOTE: loads *after* Note,
// but is fast if data is co-located.
const author = await db.authors.get(id);
return <span>By: {author.name}</span>;
}
JS bundle ⚠️
JS bundle
import React, { useState } from 'react';
const SimpleForm = () => {
const [inputValue, setInputValue] = useState('');
const handleChange = (e) => {
setInputValue(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
alert(`Submitted value: ${inputValue}`);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={handleChange}
/>
<button type="submit">Submit</button>
</form>
);
};
export default SimpleForm;
Current state
import React, { useState } from 'react';
const SimpleForm = () => {
const [inputValue, setInputValue] = useState('');
const [isPending, setPending] = useState(false);
const handleChange = (e) => {
setInputValue(e.target.value);
};
const handleSubmit = (e) => {
setPending(true);
e.preventDefault();
alert(`Submitted value: ${inputValue}`);
setPending(false);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={handleChange}
/>
<button disabled={isPending} type="submit">Submit</button>
</form>
);
};
export default SimpleForm;
Current state
More complexity can add in over time
import React, { useState } from 'react';
const SimpleForm = () => {
const [isPending, setPending] = useState(false);
const handleSubmit = (formData) => {
setPending(true);
let inputValue = formData.get('username');
alert(`Submitted value: ${inputValue}`);
setPending(false);
};
return (
<form action={handleSubmit}>
<input
type="text"
name="username"
/>
<button disabled={isPending} type="submit">Submit</button>
</form>
);
};
export default SimpleForm;
Actions!!
Actions reduce the need of boilerplate code for managing input states
import React, { useState } from 'react';
const SimpleForm = () => {
const [isPending, startTransition] = useTransition();
const handleSubmit = (formData) => {
startTransition(async () => {
let inputValue = formData.get('username');
alert(`Submitted value: ${inputValue}`);
});
};
return (
<form action={handleSubmit}>
<input
type="text"
name="username"
/>
<button disabled={isPending} type="submit">Submit</button>
</form>
);
};
export default SimpleForm;
Async functions in useTransition supported now
import React, { useActionState } from 'react';
const SimpleForm = () => {
const [state, action, isPending] = useActionState(handleSubmit, {
user: '',
error: null
});
const handleSubmit = async (formData) => {
try {
let inputValue = formData.get('username');
let response = await loginUser(inputValue);
alert(`Submitted value: ${inputValue}`);
return { user: inputValue, error: null };
} catch (error) {
return { user: null, error: error };
}
};
return (
<form action={action}>
<input
type="text"
name="username"
/>
<button disabled={isPending} type="submit">Submit</button>
</form>
);
};
export default SimpleForm;
useActionState
Helps in managing the form states as well
function Submit() {
const { pending, formData, method, action } = useFormStatus();
return <button disabled={pending}>Submit</button>
}
useFormStatus
Helps in knowing the status of the last submitted form
function Submit() {
const { pending, formData, method, action } = useFormStatus();
return <button disabled={pending}>Submit</button>
}
function Todos({todosPromise}) {
// `use` will suspend until the promise resolves.
const todos = use(todosPromise);
return todos.map(todo => <p key={todo.id}>{todo.title}</p>);
}
function Page({todosPromise}) {
// When `use` suspends in Todos,
// this Suspense boundary will be shown.
return (
<Suspense fallback={<div>Loading...</div>}>
<Todos todosPromise={todosPromise} />
</Suspense>
)
}
promises
function Todos({todosPromise}) {
// `use` will suspend until the promise resolves.
const todos = use(todosPromise);
return todos.map(todo => <p key={todo.id}>{todo.title}</p>);
}
function Page({todosPromise}) {
// When `use` suspends in Todos,
// this Suspense boundary will be shown.
return (
<Suspense fallback={<div>Loading...</div>}>
<Todos todosPromise={todosPromise} />
</Suspense>
)
}
promises
function Todos({todosPromise}) {
// `use` will suspend until the promise resolves.
const todos = use(todosPromise);
return todos.map(todo => <p key={todo.id}>{todo.title}</p>);
}
function Page({todosPromise}) {
// When `use` suspends in Todos,
// this Suspense boundary will be shown.
return (
<Suspense fallback={<div>Loading...</div>}>
<Todos todosPromise={todosPromise} />
</Suspense>
)
}
promises
promises
function Todo({children}) {
if (children == null) {
return null;
}
// This would not work with useContext
// because of the early return.
const theme = use(ThemeContext);
return (
<h1 style={{color: theme.color}}>
{children}
</h1>
);
}
Context
function Todos({todosPromise}) {
// `use` will suspend until the promise resolves.
const todos = use(todosPromise);
return todos.map(todo => <p key={todo.id}>{todo.title}</p>);
}
function Page({todosPromise}) {
// When `use` suspends in Todos,
// this Suspense boundary will be shown.
return (
<Suspense fallback={<div>Loading...</div>}>
<Todos todosPromise={todosPromise} />
</Suspense>
)
}
function CustomInput({placeholder, ref}) {
return <input placeholder={placeholder} ref={ref} />
}
//...
<CustomInput ref={ref} />
<input
ref={(ref) => {
// ref created
// NEW: return a cleanup function to reset
// the ref when element is removed from DOM.
return () => {
// ref cleanup
ref.removeEventHandler('change', handleOnChange);
};
}}
/>
// Before
function App({children}) {
return (
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>
);
}
// After
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
BEFORE
AFTER
function MyComponent (props) {
return (
<div>
<title>React is turning 19!</title>
<meta name='Author' content='Nikhil Sharma' />
<meta
property='og:image'
content='https://dummy-img.com'
/>
<h1>React 19 is super awesome!!</h1>
</div>
);
}
<html>
<head>
<title>React is turning 19!</title>
<meta name='Author' content='Nikhil Sharma' />
<meta
property='og:image'
content='https://dummy-img.com'
/>
</head>
<body>
<div>
<h1>React 19 is super awesome!!</h1>
</div>
</body>
</html>
function MyComponent (props) {
return (
<div>
<title>React is turning 19!</title>
<meta name='Author' content='Nikhil Sharma' />
<meta
property='og:image'
content='https://dummy-img.com'
/>
<link
rel='stylesheet'
href='/styles/modal.css'
precedence='default'
/>
<h1>React 19 is super awesome!!</h1>
</div>
);
}
<html>
<head>
<title>React is turning 19!</title>
<meta name='Author' content='Nikhil Sharma' />
<meta
property='og:image'
content='https://dummy-img.com'
/>
<link
rel='stylesheet'
href='/styles/modal.css'
/>
</head>
<body>
<div>
<h1>React 19 is super awesome!!</h1>
</div>
</body>
</html>
function MyComponent (props) {
return (
<div>
<title>React is turning 19!</title>
<meta name='Author' content='Nikhil Sharma' />
<meta
property='og:image'
content='https://dummy-img.com'
/>
<Suspense fallback='loading contents'>
<link
rel='stylesheet'
href='/styles/modal.css'
precedence='default'
/>
</Suspense>
<h1>React 19 is super awesome!!</h1>
</div>
);
}
function MyComponent (props) {
return (
<div>
<title>React is turning 19!</title>
<meta name='Author' content='Nikhil Sharma' />
<meta
property='og:image'
content='https://dummy-img.com'
/>
<Suspense fallback='loading contents'>
<link
rel='stylesheet'
href='/styles/modal.css'
precedence='default'
/>
</Suspense>
<h1>React 19 is super awesome!!</h1>
</div>
);
}
React 19 is super awesome!!
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom'
function MyComponent() {
preinit('https://.../path/to/some/script.js', {as: 'script' })
preload('https://.../path/to/font.woff', { as: 'font' })
preload('https://.../path/to/stylesheet.css', { as: 'style' })
prefetchDNS('https://...')
preconnect('https://...')
}
<html>
<head>
<!-- links/scripts are prioritized by their utility to early loading, not call order -->
<link rel="prefetch-dns" href="https://...">
<link rel="preconnect" href="https://...">
<link rel="preload" as="font" href="https://.../path/to/font.woff">
<link rel="preload" as="style" href="https://.../path/to/stylesheet.css">
<script async="" src="https://.../path/to/some/script.js"></script>
</head>
<body>
...
</body>
</html>
npm install --save-exact react@rc react-dom@rc
{
"dependencies": {
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc"
},
"overrides": {
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc"
}
}
// Before
import {render} from 'react-dom';
render(<App />, document.getElementById('root'));
// After
import {createRoot} from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
npx codemod@latest react/19/migration-recipe
nik72619c
@niksharma1997