What's new in React 18?

Automatic Batching

// Before: only React events were batched
setTimeout(() => {
  setSize((oldSize) => oldSize + 1);
  setOpen((oldOpen) => !oldOpen);
  // React will render twice, once for each state update (no batching)
}, 1000);
 
// After: updates inside of timeouts, promises,
// native event handlers or any other event are batched
setTimeout(() => {
  setSize((oldSize) => oldSize + 1);
  setOpen((oldOpen) => !oldOpen);
  // React will only re-render once at the end (that is batching)
}, 1000);

Automatic Batching

React 17 and older

React 18

Automatic Batching

import { flushSync } from "react-dom";
// Note: we are importing from react-dom, not react
function handleSubmit() {
  flushSync(() => {
    setSize((oldSize) => oldSize + 1);
  });
 
  // React has updated the DOM by now
  flushSync(() => {
    setOpen((oldOpen) => !oldOpen);
  });
 
  // React has updated the DOM by now
}

flushSync

Client and Server Rendering APIs

// Before
import { render } from "react-dom";
 
const container = document.getElementById("app");
render(<App tab="home" />, container);
 
// After
import { createRoot } from "react-dom/client";
 
const container = document.getElementById("app");
const root = createRoot(container);
root.render(<App tab="home" />);

createRoot

Client and Server Rendering APIs

// Before
import { hydrate } from "react-dom";
const container = document.getElementById("app");
hydrate(<App tab="home" />, container);
 
// After
import { hydrateRoot } from "react-dom/client";
const container = document.getElementById("app");
const root = hydrateRoot(container, <App tab="home" />);
// Unlike with createRoot, 
// you don't need a separate root.render() call here.

hydrateRoot

Client and Server Rendering APIs

What hydration is?      -       CSR

Client and Server Rendering APIs

What hydration is?      -       SSR

Transitions

import { startTransition } from "react";
 
// Urgent: Show what was typed in the input
setInputValue(newInputValue);
 
// Mark any state updates inside as transitions 
// and mark them as non-urgent
startTransition(() => {
  // Transition: Show the results
  setSearchQuery(newInputValue);
});

Transitions

function App() {
  const [isPending, startTransition] = useTransition();
  const [count, setCount] = useState(0);
 
  function handleClick() {
    startTransition(() => {
      setCount((oldCount) => oldCount + 1);
    });
  }
 
  return (
    <div>
      <span>Current count: {count}</span>
 
      {isPending && <Spinner />}
 
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

Suspense On the Server

<Suspense fallback={<PageSkeleton />}>
  <RightColumn>
    <ProfileHeader />
  </RightColumn>
  <LeftColumn>
    <Suspense fallback={<LeftColumnSkeleton />}>
      <Comments />
      <Photos />
    </Suspense>
  </LeftColumn>
</Suspense>

Transitions

Just watch

New Hooks

function CodeOfConductField() {
  const id = useId();
 
  return (
    <>
      <label htmlFor={id}>Do you agree with our Code of Conduct?</label>
      <input id={id} type="checkbox" name="coc" />
    </>
  );
}

useId

New Hooks

function SearchResults() {
  const query = useSearchQuery("");
  const deferredQuery = useDeferredValue(query);
 
  // Memoizing tells React to only re-render when deferredQuery changes,
  // not when query changes.
  const suggestionResuls = useMemo(
    () => <SearchSuggestions query={deferredQuery} />,
    [deferredQuery]
  );
 
  return (
    <>
      <SearchInput query={query} />
      <Suspense fallback="Loading suggestion results...">
        {suggestionResuls}
      </Suspense>
    </>
  );
}

useDeferredValue

New Hooks

useSyncExternalStore

useInsertionEffect

useTransition

React 18

By Sergey Shalyapin

React 18

  • 362