React is turning 19

nik72619c

 

          @niksharma1997

React 19 is in beta!

React 19 is in beta!

Async transitions

Server Components

use()

New form hooks

Actions

API updates

AAnd what not!!!!
              🎉🎉🎉

React Compiler

Server Components

use()

New form APIs

Actions

API updates

Its the react team and you

Hey, I'm Nikhil 👋

I love to talk about performance, react and design systems

nik72619c

 

          @niksharma1997

1

1

Out of the box

React compiler

Its not a feature of react 19

React compiler

Babel plugin

Compiler core

Eslint plugin

Code optimiser at build time

React compiler

Babel plugin

Compiler core

Eslint plugin

Code optimiser at build time

useMemo()

useCallback()

React compiler

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",
  },
}

React compiler

2

2

Server Components

Server CompoNents

SSR now...

Server CompoNents

/GET

SSR now...

Server CompoNents

/GET

SSR now...

Server CompoNents

/GET

SSR now...

Server CompoNents

/GET

SSR now...

Server CompoNents

/GET

SSR now...

Server CompoNents

  • Only initial load is fast, you need to re-build the app on each re-load
  • JS bundle still needs to be executed, even for non-functional components

Problems with SSR

JS bundle ⚠️

Server CompoNents

JS bundle ⚠️

?

Server components render entirely on the server

Server CompoNents

Server components render entirely on the server

Server CompoNents

/GET

Server components render entirely on the server

Server CompoNents

/GET

Server components render entirely on the server

Server CompoNents

/GET

Server CompoNents

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

Server CompoNents

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

Server CompoNents

'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

Server CompoNents

'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>;
}

+0.0KB

Server CompoNents

JS bundle ⚠️

Server CompoNents

JS bundle

3

3

Form actions

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;

Form Actions

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;

Form Actions

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;

Form Actions

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;

Form Actions

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;

Form Actions

useActionState

Helps in managing the form states as well

function Submit() {
  const { pending, formData, method, action } = useFormStatus();
  return <button disabled={pending}>Submit</button>
}

Form Actions

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>
}

Form Actions

4

4

use()

use()

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

use()

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

use()

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

  • Todo 1
  • Todo 2
  • Todo 3

use()

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>
  )
}

5

5

Improvements

Improvements

Ref as a prop

function CustomInput({placeholder, ref}) {
  return <input placeholder={placeholder} ref={ref} />
}

//...
<CustomInput ref={ref} />

Improvements

Ref as a prop

<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);
    };
  }}
/>

Improvements

Context api

// Before
function App({children}) {
  return (
    <ThemeContext.Provider value="dark">
      {children}
    </ThemeContext.Provider>
  );  
}


// After
function App({children}) {
  return (
    <ThemeContext value="dark">
      {children}
    </ThemeContext>
  );  
}

Improvements

Better error reporting

BEFORE

Improvements

Better error reporting

AFTER

Improvements

Document metadata

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>

Improvements

Document metadata

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>

Improvements

Document metadata

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>
  );

}

Improvements

Document metadata

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!!

Improvements

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>

Document metadata

6

6

The upgrade

The upgrade

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"
  }
}

The upgrade

// 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 />);

The upgrade

The upgrade

The upgrade

  • propTypes, defaultProps
  • Legacy Context using contextTypes and getChildContext
  • string refs
  • React.createFactory
  • react-dom/test-utils
  • ReactDOM.render
  • ReactDOM.hydrate

Removed apis

The upgrade

  • propTypes, defaultProps
  • Legacy Context using contextTypes and getChildContext
  • string refs
  • React.createFactory
  • react-dom/test-utils
  • ReactDOM.render
  • ReactDOM.hydrate

Removed apis

npx codemod@latest react/19/migration-recipe

Codemods

The upgrade

v19  18.3

Happy coding
                 ✌️

nik72619c

 

          @niksharma1997

React is turning 19

By nik72619c

React is turning 19

This talk explains the new features coming to React 19

  • 31