• Blog
  • Documentation
  • Courses
  • Changelog
  • AI Starters
  • UI Kit
  • FAQ
  • Supamode
    New
  • Pricing

Launch your next SaaS in record time with Makerkit, a React SaaS Boilerplate for Next.js and Supabase.

Makerkit is a product of Makerkit Pte Ltd (registered in the Republic of Singapore)Company Registration No: 202407149CFor support or inquiries, please contact us

About
  • FAQ
  • Contact
  • Verify your Discord
  • Consultation
  • Open Source
  • Become an Affiliate
Product
  • Documentation
  • Blog
  • Changelog
  • UI Blocks
  • Figma UI Kit
  • AI SaaS Starters
License
  • Activate License
  • Upgrade License
  • Invite Member
Legal
  • Terms of License

React 19.2: Upgrade Guide

Oct 19, 2025

React 19.2 introduces the Activity component, useEffectEvent hook, Partial Pre-rendering, and Chrome DevTools Performance Tracks. Learn about all the new features and improvements in this comprehensive guide.

React 19.2 marks the third major release within a year, bringing significant new features and improvements to the React ecosystem.

This release introduces new features and improvements to the React ecosystem:

  1. The <Activity> component to better manage the visibility of a tree
  2. The useEffectEvent hook to create a stable function that always has access to the latest props and state within a useEffect hook
  3. Partial Pre-rendering
  4. Enhanced Chrome DevTools integration

In general, there are no breaking changes in this release, but there are some new EsLint rules that are stricter and may help catch some bugs and anti-patterns, and may require some refactoring.

The official React documentation has a comprehensive list of all the new features and improvements in React 19.2. I highly recommend reading it.

In this article, I want to focus on the most important changes that affected the code in Makerkit.

EsLint rules: new strict rules about immutability and purity of React Hooks usage

This release also comes with an updated EsLint ruleset that is more strict and may help catch some bugs. However, it will surface some popular anti-patterns that were previously allowed, such as updating the state in a useEffect hook.

I will document here some of the changes that affected the code in Makerkit and how to fix them.

Activity Component

The <Activity> component allows developers to control the visibility of a tree based on the mode prop.

You can pretty much change your code from ternary operators and conditionals to the <Activity> component.

Before React 19.2:

{currentTab === 'home' && <HomePage />}

With React 19.2:

<Activity mode={currentTab === 'home' ? 'visible' : 'hidden'}>
<HomePage />
</Activity>

The difference between the two ways is that the <Activity> component will preserve the state of the component when it is hidden, while the ternary operator will not.

Depending on your use case, you may prefer one over the other, so they're not always interchangeable.

Here are the times when you want to use the <Activity> component:

  • pre-rendering a component that the user is likely to navigate to but is not currently visible
  • you want to preserve the state of a component when it is hidden (ex. a form that is not submitted when the user navigates away)

For a comprehensive documentation about the <Activity> component, check out the official React documentation.

useEffectEvent Hook

The useEffectEvent hook allows you to create a stable function that always has access to the latest props and state, and is supposed to be used within a useEffect hook.

This is very useful when you need to execute an event handler function within a useEffect hook that depends on the latest props and state. In this way, you don't need to pass these dependencies to the useEffect hook (which trigger the re-execution of the useEffect hook when they change) which prevents unnecessary re-executions of the useEffect hook. The linter won't bug you about the missing dependencies and you can still access the latest props and state within the event handler function.

In Makerkit, I migrated quite a few functions to use the useEffectEvent hook - which allowed me to remove a lot of dependencies from the useEffect hook, and make the code more readable and maintainable.

Homework: locate the useEffect hooks in your codebase and try to see if they use unnecessary dependencies. If you do, try to migrate them to use the useEffectEvent hook to reduce the number of re-renders and make your code more readable, performant and less error-prone.

ESLint Plugin v7

The latest version of the EsLint plugins for React introduce some strict rules about immutability and purity of React Hooks usage.

Makerkit uses strict EsLint and Typescript rules, so we make sure to follow these rules to the letter. Our high standards meant we couldn't just disable the rules - we had to refactor the code to follow the rules and the best practices in React.

In general, following these rules will make your code more stable, predictable and ready to be used with the upcoming React Compiler, which has recently been moved out of the experimental phase and stabilized for production use.

Setting state in the useEffect hook

If you've worked with React for a while, you may be used to settings state using props values:

function MyComponent({ value }) {
const [state, setState] = useState(value);
// ❌ This is not allowed by the EsLint rules
useEffect(() => {
setState(value);
}, [value]);
}

This was an anti-pattern for a long time, but it was allowed by the EsLint rules. Now, it's not allowed anymore, so your rules may break if you're using this pattern.

React suggests updating the state in the render function instead:

function MyComponent({ value }) {
const [state, setState] = useState(value);
// ✅ This is allowed by the EsLint rules
if (value !== state) {
setState(value);
}
return <div>{state}</div>;
}

There are many other scenarios where you may want to set state in the useEffect hook. You may not need an effect is a great article that covers many of these scenarios and provides a lot of guidance on when to use the useEffect hook and when not to use it.

Scenario: using useFetcher in React Router 7

For example, this required me to update a lot of code in the React Router 7 whereas it's suggested to use useEffect to react to the data fetching state. useFetcher famously does not return a Promise when the request executes, which can make things a bit more complex when, for example, you want to do something at the end of the request - such as displaying a notification, closing a dialog, etc.

Before, we would use useFetcher to fetch data and use useEffect to react to the data fetching state:

function MyComponent() {
const fetcher = useFetcher();
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
if (fetcher.data?.success) {
setIsSuccess(true);
} else if (fetcher.data?.error) {
setIsError(true);
}
}, [fetcher.data]);
return (
<div>
<If condition={isSuccess}>
<div>Success</div>
</If>
<If condition={isError}>
<div>Error</div>
</If>
<fetcher.Form method="post">
<input type="text" name="name" />
<button type="submit">Submit</button>
</fetcher.Form>
</div>
);
}

The issue is we're setting the state in the useEffect hook, which is not allowed by the EsLint rules - and is a poor practice in general which may lead to infinite re-renders.

The fix is to simply use the state in the render function:

function MyComponent() {
const fetcher = useFetcher();
const isSuccess = fetcher.data?.success;
const isError = fetcher.data && !fetcher.data.success;
return (
<div>
<If condition={isSuccess}>
<div>Success</div>
</If>
<If condition={isError}>
<div>Error</div>
</If>
<fetcher.Form method="post">
<input type="text" name="name" />
<button type="submit">Submit</button>
</fetcher.Form>
</div>
);
}

This still doesn't solve the issue where we want to display a notification at the end of the request. In this case, I suggest to split the component into two: one is a form, and the other is a container that receives notifications from the form.

function MyForm({
onSuccess,
onError,
}) {
const fetcher = useFetcher();
useEffect(() => {
if (fetcher.data) {
if (fetcher.data.success) {
onSuccess?.();
} else {
onError?.();
}
}
}, [fetcher.data, onSuccess, onError]);
return <fetcher.Form method="post">
<input type="text" name="name" />
<button type="submit">Submit</button>
</fetcher.Form>
}

The container component can then receive the notifications from the form and display them, using the useCallback hook to memoize the functions:

import { useCallback } from 'react';
import { toast } from 'sonner';
function MyContainer({
onSuccess,
onError,
}) {
const onSuccess = useCallback(() => {
toast.success('Success');
}, []);
const onError = useCallback(() => {
toast.error('Error');
}, []);
return (
<MyForm
onSuccess={onSuccess}
onError={onError}
/>
);
}

Reading or writing to references in the render function

Reading or writing the value of a reference in the render function is not allowed by the EsLint rules and is generally considered a bad practice.

function MyComponent() {
const ref = useRef(0);
// ❌ This is not allowed by the EsLint rules
return <div>{ref.current}</div>;
}

React suggests to use useState instead:

function MyComponent() {
const [state, setState] = useState(0);
return <div>{state}</div>;
}

Using a ref allows you to:

  • Persist data across re-renders - unlike regular variables, which reset every time your component renders.
  • Avoid unnecessary re-renders - updating a ref’s value doesn’t cause your component to re-render, unlike updating state.
  • Keep data isolated per component instance - each component gets its own ref, unlike external variables that are shared globally.

However, because changing a ref doesn’t trigger a re-render, refs aren’t suitable for values that need to appear on the screen. Use useState instead for anything that affects what your component renders and for reading or writing to data in the render function.

Disabling the EsLint rules

If you feel like the EsLint rules are too strict, you can disable them on a per-line basis:

// eslint-disable-next-line react-hooks/set-state-in-effect

Or you can disable it globally by adding it to the main eslint config file:

tooling/eslint/base.js

rules: {
'react-hooks/set-state-in-effect': 'off',
},

Conclusion

React 19.2 is a refinement release that rewards disciplined developers - not a flashy overhaul, but a tightening of how React wants you to think about effects, state, and visibility.

The new <Activity> component and useEffectEvent hook move React closer to a declarative, predictable model where components express intent, not mechanics. The stricter ESLint rules push teams toward more deterministic and pure code - enforcing practices that long-time React devs have known intuitively but often ignored for convenience.

If you maintain a large React codebase, take this update as an opportunity to:

  • Audit your effects - remove unnecessary ones and replace them with useEffectEvent where possible.
  • Revisit your state logic - ensure that you’re not mutating values or using effects to mirror props.
  • Embrace <Activity> for smarter pre-rendering and preserved component state.

React 19.2 doesn’t just modernize the API surface - it nudges the ecosystem toward a more functional, predictable, and resilient architecture. Upgrade deliberately, refactor cleanly, and your codebase will come out more maintainable, faster, and future-ready.

The Makerkit SaaS Boilerplate is already using React 19.2 and all the new features and improvements. Make sure to upgrade to the latest version to get the benefits of the new features and improvements!

Resources

  • React 19.2 Release Announcement
  • You may not need an effect
  • React Compiler
Some other posts you might like...
Oct 22, 2025Next.js 16: what's new?Next.js 16 is a major release that includes several new features and improvements. In this article, we will cover the new features and improvements in Next.js 16.
Oct 13, 2025Build a Production Supabase Blog in Next.js with Supamode CMSLearn how to design a Supabase blog schema, build a Next.js UI, and wire up Supamode CMS so your team can publish fast without touching SQL
Sep 25, 2025Mastering AI-Driven Development: Claude Code & Makerkit Best PracticesA comprehensive guide to building production-ready SaaS features using Claude Code's intelligent agents and Makerkit's PRD functionality