Welcome Back, React Hooks Made Simple: The Complete Guide, Part-2
In [Part 1] https://dev.to/abrar_galib_5c0cf41ad3a3e/react-hooks-made-simple-the-complete-guide-part-1-2jln we covered the core Hooks (useState, useEffect, useContext), the additional Hooks (useRef, useMemo, useCallback, useReducer) and the advanced ones (useLayoutEffect, useImperativeHandle, useId). If you haven't read it yet, start there, because everything in this part builds on it. In Part 2: - Two Hooks that keep your app smooth when an update is heavy - A few more advanced Hooks for outside data and library authors - The new Hooks that came with React 19 and later - The Hooks that belong to Next.js - Custom Hooks, common mistakes, and a cheatsheet you can keep open while you code Everything below works on React 19. A few things need a newer minor version, and I point them out as we go: useEffectEvent needs React 19.2, and ViewTransition , Fragment Refs and use(browser()) need React 19.3, which is the current stable release. Table of Contents - Performance Hooks - More Advanced Hooks - New Hooks in React 19 and Later - Next.js Hooks - Custom Hooks - Common Mistakes to Avoid - Cheatsheet - Conclusion Performance Hooks Sometimes an update is heavy: a huge list, a slow tab, a big search result. If React does all the work at once, the page freezes. Typing feels laggy and buttons stop responding. These two Hooks let you tell React: "this update is not urgent, so do the important things first." What is an urgent update? Anything the user expects to see instantly, like typing in a box or clicking a button. Showing the search results or opening a heavy tab can wait a moment. React can handle the urgent update first and finish the slow one in the background. useTransition useTransition lets you mark a state update as low priority. It gives you two things: isPending , which is true while the slow update is happening, and startTransition , a function you wrap around the state update. 'use client'; import { useState, useTransition } from 'react'; function About() { return Hi, I write about React. ; } function SlowPosts() { // Pretend this tab is heavy: 300 rows, and each row takes a little time const rows = []; for (let i = 0; i ); } return {rows} ; } function SlowRow({ number }) { const start = performance.now(); while (performance.now() - start Post #{number} ; } export default function TabContainer() { const [tab, setTab] = useState('about'); const [isPending, startTransition] = useTransition(); function selectTab(nextTab) { // This update can wait, so the page stays clickable startTransition(() => { setTab(nextTab); }); } return ( <> selectTab('about')}>About selectTab('posts')}>Posts {isPending && Loading... } {tab === 'about' ? : } ); } Click "Posts" and the old tab stays on screen while React prepares the new one in the background. You can click "About" again at any time and React will drop the slow work and switch back. Without startTransition , the whole page would freeze for a moment. Note: In React 19 you can even pass an async function to startTransition , and isPending stays true until it finishes. This is the base for the form Hooks later in this guide. useDeferredValue useDeferredValue gives you a copy of a value that intentionally "lags behind". While the user keeps typing, your input stays fast, and the heavy part of the screen catches up a moment later. Use it when you can't wrap the state setter yourself, for example when the value arrives as a prop. 'use client'; import { memo, useDeferredValue, useState } from 'react'; const Results = memo(function Results({ items, query }) { const matches = items.filter((item) => item.toLowerCase().includes(query.toLowerCase()) ); return ( {matches.map((item) => ( {item} ))} ); }); export default function Search({ items }) { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); const isStale = query !== deferredQuery; return ( <> setQuery(e.target.value)} placeholder="Search..." /> ); } Two things to remember: - You must wrap the slow component in memo . If you don't, it re-renders on every keystroke anyway and you save nothing. - query !== deferredQuery tells you the list is still catching up. Here we use it to dim the old results. React 19 also lets you pass a starting value as the second argument, like useDeferredValue(query, '') . useTransition or useDeferredValue? | useTransition | useDeferredValue | | |---|---|---| | What it wraps | The code that sets state | The value itself | | Use it when | You own the state setter | The value comes from a prop or a Hook | | You get | isPending flag | A lagging copy of the value | Note: Don't wrap the setState of a text input in startTransition . The text in the box must update instantly, so the input state has to stay urgent. Defer the slow part instead, like we did above. More Advanced Hooks You will use these three much less often than the ones above. Still, you should know what they are, because you'll see them in libraries and in other people's code. useSyncExternalStore useSyncExternalStore connects React to data that lives outside of React and changes on its own, like the browser's online status, the window size, or a global store. What is an external store? It's any data source that React doesn't own. React can't know when it changes, so you have to tell React how to listen to it. Libraries like Redux and Zustand use this Hook under the hood. It takes three functions: - subscribe : starts listening for changes and returns a cleanup function - getSnapshot : reads the current value - getServerSnapshot : gives a starting value for server rendering, so the server HTML matches the browser HTML during hydration 'use client'; import { useSyncExternalStore } from 'react'; function subscribe(callback) { window.addEventListener('online', callback); window.addEventListener('offline', callback); return () => { window.removeEventListener('online', callback); window.removeEventListener('offline', callback); }; } function useOnlineStatus() { return useSyncExternalStore( subscribe, () => navigator.onLine, // getSnapshot: read the browser value () => true // getServerSnapshot: what the server should assume ); } export default function StatusBar() { const isOnline = useOnlineStatus(); return {isOnline ? 'Online' : 'Offline'} ; } Why not just use useEffect and useState ? Because this Hook also prevents a bug called tearing, where two parts of the screen show different values of the same data during a render. Two rules to remember: - Define subscribe outside your component. If it's created inside, React will unsubscribe and subscribe again on every render. - getSnapshot must return the same value when nothing changed. If it returns a brand-new object every time, you will get an infinite loop. In Next.js, getServerSnapshot is required if the component renders on the server. useInsertionEffect This one is built for people who write CSS-in-JS libraries. It runs before React changes the DOM, so a library can safely inject tags at the right moment. If you build normal apps, you will probably never need it. Here is the order in which the three effect Hooks run: - useInsertionEffect runs before React updates the DOM - useLayoutEffect runs after the DOM updates, before the browser paints - useEffect runs after the browser paints 'use client'; import { useInsertionEffect } from 'react'; export default function Highlight({ children }) { useInsertionEffect(() => { const style = document.createElement('style'); style.textContent = '.highlight { background: yellow; }'; document.head.appendChild(style); return () => document.head.removeChild(style); }, []); return {children} ; } Libraries like styled-components and Emotion already do this for you, so just use them. useDebugValue A tiny Hook that adds a label next to your custom Hook inside React DevTools. It only helps with debugging, and it only works inside custom Hooks. import { useDebugValue, useSyncExternalStore } from 'react'; // "subscribe" is the same function from the previous example function useOnlineStatus() { const isOnline = useSyncExternalStore( subscribe, () => navigator.onLine, () => true ); useDebugValue(isOnline ? 'Online' : 'Offline'); return isOnline; } Open DevTools, find the component that uses useOnlineStatus , and you will see the Online or Offline label right next to it. New Hooks in React 19 and Later React 19 was built around one idea: Actions. A few new Hooks make Actions easy to use, especially with forms. Then React 19.2 and 19.3 added a few more useful things on top. What is an Action? An Action is a function that changes something, usually by talking to a server. Submitting a form is the classic example. React runs it inside a transition, so it can track a pending state for you, and it updates the screen when the work is done. useActionState useActionState runs a function when a form is submitted and keeps track of the result. You get the latest result, a function to put on your form, and a isPending flag. 'use client'; import { useActionState } from 'react'; async function subscribe(previousState, formData) { const email = formData.get('email'); // Pretend we are talking to a server await new Promise((resolve) => setTimeout(resolve, 1000)); if (!email.includes('@')) { return { message: 'Please enter a valid email.' }; } return { message: Thanks! We will write to ${email}. }; } export default function NewsletterForm() { const [state, formAction, isPending] = useActionState(subscribe, { message: '', }); return ( {isPending ? 'Sending...' : 'Subscribe'} {state.message && {state.message} } ); } How it works: - You pass in your function and a starting state. - You put formAction on the form'saction prop. - When the form is submitted, React calls your function with the previous state and the form data. - Whatever your function returns becomes the new state . Notice that we don't need onSubmit , preventDefault or a useState for every input. React reads the form data for us. Note: After the action finishes, React resets the form's fie
Comments
No comments yet. Start the discussion.