FediScanner

#react

← Back

The End of an Era: Why React 19 Killed forwardRef


For years, React developers have lived with a specific kind of frustration: the "ref-forwarding tax." If you wanted to pass a DOM ref through a functional component, you were forced into the forwardRef API. It was a necessary evil, but one that came with significant overhead—both in terms of syntax and, more importantly, in terms of TypeScript complexity.

With the release of React 19, that era has officially come to an end. React 19 treats ref as a standard prop, effectively killing the need for forwardRef in your day-to-day component development.

The Problem with forwardRef


To understand why this is a massive win for developer experience, we have to look at what we are leaving behind. The forwardRef API was designed to solve a specific problem: functional components don't receive ref as a prop by default. To make them "ref-able," you had to wrap your component in a higher-order function.

This led to several pain points:

  1. The Wrapper Tax: Every component needing a ref became a wrapper, increasing the nesting depth of your component tree.
  2. Flipped Argument Order: Instead of standard (props) => ..., you had to remember the specific signature (props, ref) => ....
  3. TypeScript Gymnastics: Typing a forwardRef component—especially one that uses generics—is notoriously difficult. It often leads to complex React.forwardRef<T, P>(...) signatures that are difficult to read and maintain.


The New Way: ref as a Prop


In React 19, ref is just another prop. This means your components can be standard, clean functions. You don't need to wrap them; you don't need to change your signature.

Here is what a standard, clean component looks like in the React 19 era:

interface Props {
label: string;
ref?: React.Ref<HTMLInputElement>;
}

const Input = ({ label, ref, ...props }: Props) => (
<div>
<label>{label}</label>
<input ref={ref} {...props} />
</div>
);

That is it. By defining ref as an optional property in your Props interface, you gain all the functionality that previously required forwardRef without any of the boilerplate.

Migrating Your Codebase


Before you start manually refactoring thousands of lines of code, take a deep breath. The React team and the broader ecosystem have provided tools to make this transition painless.

1. Use the Automated Codemod


You don't have to do this by hand. You can leverage automated codemods to handle the heavy lifting. If you are working in a Next.js environment (or using standard React), you can run the following command:

npx @next/codemod@canary react-19/replace-forwardref

This tool will scan your project, identify forwardRef usages, and convert them to the new standard prop syntax.

2. When to Keep forwardRef


While forwardRef is on a deprecation path for simple DOM forwarding, it hasn't been removed entirely. If you are using useImperativeHandle to expose custom imperative APIs (methods or values from your component to a parent), forwardRef is still required.

3. Watch Out for Ref Cleanup Functions


React 19 also introduces ref cleanup functions. When you pass a callback ref, you can now return a cleanup function that React will run when the component unmounts. Be aware that TypeScript will now reject implicit returns in these callbacks to prevent accidental bugs. Keep a close eye on your type checks during the migration.

Conclusion


Cleaning up technical debt isn't always about adding new features or optimizing performance; sometimes, the best commit is the one that removes unnecessary boilerplate. By simplifying how we handle refs, React 19 makes our code more readable, easier to type, and significantly more maintainable.

Are you planning to migrate your component libraries to React 19 soon, or are you waiting for the ecosystem to mature further? Let me know your thoughts in the comments.#react #typescript #webdev #frontend #software #coding #development #engineering #inclusive #community
React 19 Ref as Prop: Simplify TypeScript Components

Show Original PostReport

Next.js 16 makes Turbopack the default bundler and puts a number on it: builds 2 to 5 times faster than webpack. I wanted to know whether that holds on something bigger than the starter template, and where the time actually goes, so I built a 60-route app and timed every phase of the build with both bundlers.

The headline is true. It is also the least interesting thing I found. The claim describes the bundler, and on a warm build the bundler is about 4% of your build time. The rest is spent in places Turbopack does not touch, and one of them is a six-second phase with no progress bar that nobody talks about.

The app


Not a hello-world. Sixty route pages, each pulling in shared components: a Recharts line chart, a Markdown renderer, a client-side toolbar with lucide-react icons, date-fns, and a zod schema. About 2,000 lines across pages and components, on Next.js 16.3.4 and React 19.2. It builds cleanly and identically on both bundlers, which is the point: same input, swap only --turbopack for --webpack.

Every number below is a median of three runs, on the same machine, back to back.

Cold builds: the claim holds, at the low end


A cold build is no .next directory at all, the state your CI runs in unless you cache between jobs.

TurbopackwebpackratioCompile step2.9s7.2s2.5xTotal build (wall)7.1s18.8s2.6x


So 2.5x on a cold compile. That is real, it is repeatable, and it sits at the bottom of the advertised 2-to-5x range rather than the top. If you were promised 5x on your CI and you do not cache anything between runs, you will feel closer to 2.5.

Warm builds: the cache is the real story


A warm build keeps .next/cache from a previous run and rebuilds the rest. This is what your laptop does all day, and what CI does if you cache the directory.

TurbopackwebpackratioCompile step143ms2.75s19xTotal build (wall)3.45s14.0s4.1x


The compile ratio jumps to nineteen. Turbopack's persistent cache takes the bundling step from three seconds to under a sixth of one. This is the number worth caring about, because it is the one you hit dozens of times a day, and it is well outside the range on the tin.

But look at the two rows. The compile got 19x faster and the total build only got 4x faster. That gap is the whole point of this post.

The bundler is 4% of your build


On the warm Turbopack build, the compile is 143 milliseconds out of a 3.45 second build. Here is where the rest goes:

PhaseTurbopack warmDoes the bundler own it?Compile143msyesTypeScript check950msnoStatic generation (64 pages)380msnoPage data, finalizing, process startup~2sno


Turbopack made the one part it controls almost free, and the build is still three and a half seconds because type checking, static generation and Next's own orchestration do not get faster. Make the bundler infinitely fast and this build still takes over three seconds.

This is the honest reading of "2-5x faster builds". The bundler is 2.5x to 19x faster depending on cache state, but the total build time you actually wait for improves less, and improves less the more pages you have to statically generate. On this app it was 4x warm. On an app with 600 pages instead of 60, the static generation phase grows and the bundler's share shrinks further.

The six seconds nobody mentions


The strangest result was in webpack's total, which did not add up from its phases. Compile 2.8s, TypeScript 1s, static generation 0.4s: that is about four seconds accounted for, out of a fourteen second build. I timestamped every line to find the missing ten.

Most of it is one phase: Collecting build traces, 6.6 seconds, with no progress bar and no mention in any benchmark I have seen.

[ 4.1s] ✓ Compiled successfully in 2.7s
[ 5.2s] Finished TypeScript in 1069ms
[ 6.8s] ✓ Generating static pages (64/64) in 429ms
[ 7.3s] Collecting build traces ...
[ 13.9s] Route (app) <- 6.6 seconds later

Build traces are the file-dependency graph Next computes so a deployment knows which files each route needs. On webpack it cost 6.6 seconds on this app. Turbopack emits no such phase at all: I grepped its entire output and there is no "Collecting build traces" line. Whatever it does instead is folded in and invisible.

So half of webpack's warm build is a single phase that has nothing to do with compiling your code, and switching to Turbopack removes it. That is a bigger share of the win than the compile speedup, and no one advertises it.

Two more things worth knowing


The cache is bundler-specific. The first time I measured a warm Turbopack build, the compile came back at 3.1 seconds instead of 143 milliseconds. The cause was that the previous build had been webpack, and its cache does not carry over. Turbopack rebuilt from cold while believing it was warm. If you switch bundlers, or bounce between them in CI, your first build pays the full cold cost and there is no warning that it has.

The output is half the size. Turbopack's .next came out at 81 MB against webpack's 168 MB for the same app. I did not go chasing why, but if you cache the build output between CI runs, that is half the bytes to store and move.

What I got wrong on the way


I contaminated my own cache measurement and nearly reported the wrong number. My benchmark cleared everything in .next except the cache directory between runs, which is correct, but I ran Turbopack immediately after a webpack build and got a "warm" compile of 3.1 seconds. I almost wrote that Turbopack's persistent cache barely helped.

The cache is per-bundler. My reset was fine; my ordering was not. Once I primed each bundler against its own cache before timing it, the real 143 millisecond warm compile appeared. The lesson is the same one as always: when a result looks flat, suspect the harness before the thing you are measuring. A warm build that is secretly cold looks exactly like a cache that does not work.

Run it yourself


Scaffold an app, then the comparison is two flags:

npx create-next-app@latest app --ts --app
cd app
rm -rf .next && time npx next build --webpack # old default
rm -rf .next && time npx next build --turbopack # new default

To see the phase breakdown that matters, timestamp each line and watch for the build-traces gap:

rm -rf .next
start=$(date +%s.%N)
npx next build --webpack 2>&1 | while IFS= read -r l; do
printf '[%5.1fs] %s\n' "$(echo "$(date +%s.%N) - $start" | bc)" "$l"
done

The difference is small on a starter template and grows with your route count, so measure your own app, not a fresh one. I ran mine on an ordinary Linux laptop with Node 24.

What to do about it


If you are on Next.js 16 the default already gives you this, so there is nothing to switch on. The thing to change is what you expect and what you cache. Cache .next/cache in CI, because the warm compile is where the real speedup lives and a cold build throws it away. Do not expect the total build time to fall as far as the bundler benchmark suggests, because on a warm build most of your time is type checking and static generation, and those are exactly as slow as they were. And if your CI ever runs both bundlers, give them separate caches or the fallback to cold will be silent.

The 2-to-5x number is not wrong. It is just answering a smaller question than the one you have, which is how long you wait for the whole build. On this app that answer was 4x, most of it from a phase the benchmarks do not name.#nextjs #react #webdev #performance #software #coding #development #engineering #inclusive #community
Next.js 16's Turbopack is 2.5x faster, but the bundler is only 4% of your build

Show Original PostReport

Shopify oznámil, že opouští React Native a přechází zpět na nativní vývoj ve Swiftu (iOS) a Kotlinu (Android). Firma přešla na React Native v roce 2020 a toto rozhodnutí bylo velmi úspěšné – ušetřilo čas při vývoji funkcí, umožnilo i vývojářům bez mobilních zkušeností přispívat do aplikací a zbavilo firmu nutnosti neustále dohánět shodu funkcí mezi […]

https://zdrojak.cz/zpravicky/shopify-se-vraci-od-react-native-zpet-k-nativnimu-vyvoji/

Show Original PostReport

Faiz (@FaizPalwala)

기존 `react-github-calendar` 프록시가 GitHub API 레이트 리밋에 걸려 포트폴리오가 깨지는 문제를 해결하기 위해, 런타임 서버·API 호출 없이 동작하는 Serverless GitHub Calendar를 만들었다고 발표했다. 정적 빌드 기반 GitHub 활동 캘린더 대안이다.

x.com/FaizPalwala/status/20980

#github #serverless #nextjs #react #staticsite

Show Original PostReport

Hi, im new here.

I write a Picture Site with NodeJS and ReactJS. I have 2 Nodes. First Server, second Client. On the server, I retrieve the information from the database via the client. Lately, I’ve been having a bit of trouble grasping how to retrieve and process data.

module.exports = function (app, db) {
const ut = require("./utils");

/* GET PROFILE DATA */
app.get("/account/:userID", async (req, res) => {
var userID = req.params.userID; // Get User-Hash from Client

db.query(
"SELECT * " +
"FROM pix_users u " +
"LEFT JOIN pix_images i ON i.user = u.user_hash " +
"WHERE username=?",
[userID],
(err, result) => {
if (err) {
res.send({ error: err });
}

if (result) {
var site_items = new Array();
result.forEach(function (value, index, items) {
site_items = site_item_model(value);
console.log(site_items);
// You can see all the results in the terminal.
});

return ut.handleResponse(req, res, 200, {
p_items: site_items,
});
}
},
);
});

/* MODEL */
function site_item_model(model) {

return {
id: model.id,
username: model.username,
userhash: model.user_hash,
firstname: model.user_first_name,
lastname: model.user_last_name,
email: model.user_email,
userpic: model.user_avatar_pic,
userprefix: model.user_prefix,
//useravatar: model.user_avatar,
userjson: model.user_json,
userregist: model.user_registered,
// ------ Push Data from pix_images ------
useruploads: {
[model.id]: {
hash: model.image_hash,
file: model.file
}
}
// ---------------------------------------

};
}
};

I have two tables: pix_user and pix_images. I want to retrieve the user record just once and use a LEFT JOIN to fetch all images belonging to that user. The database query works as expected. However, the model function only stores the last entry in useruploads, even though the user might have, for example, 10 images. What do I need to change so that all images are stored in useruploads?

Thanks !#react #software #coding #development #engineering #inclusive #community
React (newest) Server Left Join get all Results from 2. Table in first Query?

Show Original PostReport