Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
March 31, 2022 09:32 pm GMT

Why React 18 Broke Your App

Youve just gotten done with your React 18 upgrade, and, after some light QA testing, dont find anything. An easy upgrade, you think.

Unfortunately, down the road, you receive some internal bug reports from other developers that make it sound like your debounce hook isnt working quite right. You decide to make a minimal reproduction and create a demo of said hook.

You expect it to throw an alert dialog after a second of waiting, but weirdly, the dialog never runs at all.

See and run the related code sample in the sandbox

This is strange because it was working just last week on your machine! Why did this happen? What changed?

The reason your app broke in React 18 is that youre using StrictMode.

Simply go into your index.js (or index.ts) file, and change this bit of code:

render(  <StrictMode>    <App />  </StrictMode>);

To read like this:

render(    <App />);

All of the bugs that were seemingly introduced within your app in React 18 are suddenly gone.

Only one problem: These bugs are real and existed in your codebase before React 18 - you just didnt realize it.

Proof of broken component

Looking at our example from before, were using React 18s createRoot API to render our App inside of a StrictMode wrapper in lines 56 - 60.

See and run the related code sample in the sandbox

Currently, when you press the button, it doesnt do anything. However, if you remove the

StrictMode and reload the page, you can see an Alert after a second of being debounced.

Looking through the code, lets add some console.logs into our useDebounce, since thats where our function is supposed to be called.

function useDebounce(cb, delay) {  const inputsRef = React.useRef({ cb, delay });  const isMounted = useIsMounted();  React.useEffect(() => {    inputsRef.current = { cb, delay };  });  return React.useCallback(    _.debounce((...args) => {        console.log("Before function is called", {inputsRef, delay, isMounted: isMounted()});          if (inputsRef.current.delay === delay && isMounted())                      console.log("After function is called");                  inputsRef.current.cb(...args);        }, delay),    [delay]  );}
Before function is called Object { inputsRef: {}, delay: 1000, isMounted: false }

Oh! It seems like isMounted is never being set to true, and therefore the inputsRef.current callback is not being called: thats our function we wanted to be debounced.

Lets take a look at the useIsMounted() codebase:

function useIsMounted() {  const isMountedRef = React.useRef(true);  React.useEffect(() => {    return () => {          isMountedRef.current = false;    };  }, []);  return () => isMountedRef.current;}

This code, at first glance, makes sense. After all, while were doing a cleanup in the return function of useEffect to remove it at first render, useRef's initial setter runs at the start of each render, right?

Well, not quite.

What changed in React 18?

In older versions of React, you would mount a component once and that would be it. As a result, the initial value of useRef and useState could almost be treated as if they were set once and then forgotten about.

In React 18, the React developer team decided to change this behavior and re-mount each component more than once in strict mode. This is in strong part due to the fact that Reacts concurrent features have exactly that behavior.

Concurrent features work by having a component potentially halting its mounting mid-render and re-mount in order to avoid cache invalidation problems. This halting can cause a component to re-mount at any time, the same as strict mode.

However, this behavior shift isnt some save-face from the React team to make you think their concurrent features arent broken: theyre a reminder to follow Reacts rules properly and to clean up your actions as expected.

After all, the React team themselves have been warning that an empty dependent array ([] as the second argument) should not guarantee that it only runs once for ages now.

In fact, this article may be a bit of a misnomer - the React team says theyve upgraded thousands of components in Facebooks core codebase without significant issues. More than likely, a majority of applications out there will be able to upgrade to the newest version of React without any problems.

All that said, these React missteps crawl their way into our applications regardless. While the React team may not anticipate many breaking apps, these errors seem relatively common enough to warrant an explanation.

How to fix the remounting bug

The code I linked before was written by me in a production application and it's wrong. Instead of relying on useRef to initialize the value once, we need to ensure the initialization runs on every instance of useEffect.

function useIsMounted() {  const isMountedRef = React.useRef(true);  React.useEffect(() => {  isMountedRef.current = true; // Added this line    return () => {      isMountedRef.current = false;    };  }, []);  return () => isMountedRef.current;}

This is true for the inverse as well! We need to make sure to run cleanup on any components that we may have forgotten about before.

Many ignore this rule for App and other root elements that they dont intend to re-mount, but with new concurrency features, that guarantee is no longer a safe bet.

To solve this application across your app, look for the following signs:

  • Side effects with cleanup but no setup (like our example)
  • A side effect without proper cleanup
  • Utilizing [] in useMemo and useEffect to assume that said code will only run once

One this code is eliminated, you should be back to a fully functioning application and can re-enable StrictMode in your application!

Conclusion

React 18 brings many amazing features to the table, such as new suspense features, the new useId hook, automatic batching, and more. While refactor work to support these features may be frustrating at times, its important to remember that they a serve real-world benefit to the user.

For example, React 18 also introduces some functionality to debounce renders in order to create a much nicer experience when rapid user input needs to be processed.

For more on the React 18 upgrade process, take a look at our instruction guide on how to upgrade to React 18


Original Link: https://dev.to/this-is-learning/why-react-18-broke-your-app-4730

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To