27 ms·
PSA: React 18 calls code twice in strict dev mode to detect side effects
- tomduncalf 5y agoI thought I’d share this in the hope that other developers see it and don’t lose a bunch of time wondering why e.g. code in a useEffect(…, []) block is being called twice. I created a new Next.js project and was very confused to see my console.logs appearing twice. It might be nice for React to log a warning that it is going to do this as it’s quite confusing if you aren’t aware of it!
- rektide 5y agoThis seems like a super aggressive move, giving up lots of performance & complexity for some pretty com ppl lex consistency checking. Is tbis in production build too?
- technion 5y agoNo, this is document as only occurring in development mode.
- tomduncalf 5y agoYea sadly there weren’t enough characters in the title field for me to put “strict dev mode” so that nuance was lost Edit: dang updated the post title to include this
- tomstuart 5y agoAside from being development-only, note that you also need to wrap a component in `<React.StrictMode>…</React.StrictMode>` to opt it into this.
- tomduncalf 5y agoYeah, good point - I thought it could be that Next.js changed their default behaviour to use strict mode when they upgraded to React 18 but another comment says React 17 silenced the duplicate logs(!) so maybe the change is older than that.
- beart 5y agothis sounds similar to angular which performs multiple passes in dev mode and warns if values have not settled
- Mogzol 5y ago> code in a useEffect(…, []) block is being called twice Shouldn't that be one of the few cases where the code isn't being called twice? useEffect is meant for side-effects, so it isn't included in the list of functions that React will call twice.
- danabramov 5y agoThis new check is in preparation for an upcoming feature that lets you preserve state between unmounts. For example, when switching a between routes or changing a top-level tab. The check simulates unmounting and immediately remounting (with state/DOM preserved), which is why effects run twice. It simulates what happens when you switch away to another tab and then back, like a stress-test for this scenario. Since in most cases, switching to another tab and then immediately switching back shouldn’t be observable behavior. You might fire an extra impression log in development, but from the user perspective the behavior should be the same. And if it’s not, you’ll see the same result as if the user actually switched tabs there and back. So it doesn’t just “run the code twice” but it simulates what happens when a tree remounts with existing state. Most components are already resilient to this, but the feature is hard to adopt if this resiliency isn’t being tested. This check helps find the remaining cases where remounting wouldn’t be safe, early. Hope this clarifies it a bit! https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#updates-to-strict-mode https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.h...
- joeframbach 5y agoWe already have Providers for that.
- Gravey 5y agoAfaik this has been the behavior of StrictMode since it was introduced in React 16. One thing that’s called out in the linked docs is that duplicate console.logs were _deliberately_ silenced in React 17. I have no idea what they were thinking when they made this decision, but it sounds like it has been walked back in 18.
- tomduncalf 5y agoInteresting and an odd choice of behaviour in 17! I tested in a codesandbox with Next.js using React 17 and 18 and observed that only 18 did the double logging I was seeing, so assumed it was new behaviour in 18. Actually I guess I was seeing 17 silencing the double log.
- jitl 5y agoReact also already ran components twice when devtools is open, even without strict mode. This happens in my React 16 codebase with silenced console.log and no StrictMode in sight.
- joshribakoff 5y agoI think you mean it logged log messages twice? I don’t believe having devtools opens changes the runtime semantics of the app.that would defeat the point of the devtools. I do know it annoyingly logs things twice
- jitl 5y agoIt doesn't log things twice, because DevTools replaces `console.log` and similar with no-op versions when they re-play the render. But it does call your component more times than you expect to power the DevTools hook inspector and other features. Here's the code that turns off `console.log`: https://github.com/facebook/react/blob/b8cfda15e1232554487c7285fb464f22705a23ce/packages/shared/ConsolePatchingDev.js#L10-L13 https://github.com/facebook/react/blob/b8cfda15e1232554487c7...
- 5y ago
- leros 5y agoWell that explains a lot of things.
- TAKEMYMONEY 5y ago> This only applies to development mode.
- tomduncalf 5y agodang was kind enough to update the post title for me to make this clear!
- thomasfromcdnjs 5y agoPontificating a little here, but it reminds me of "The road to hell is paved with good intentions" quote. Any external code that purports to be reusable should not be concerned with the idea of an "environment". Positing that reducers should be pure is fine and dandy, telling me I can't throw in a side effect is none of your business. Instead of running it twice, just make a warning log and be done with it.
- arcbyte 5y agoWith React, it's your code that is the external code. Your reason is quite sound. Your reducers shouldn't be concerned with any jind of "environment" or side effects.
- the8472 5y ago> Positing that reducers should be pure is fine and dandy, telling me I can't throw in a side effect is none of your business. API contracts work in both ways. The user has to uphold it just as much as the provider. If you don't breakage ensues. In lower-level languages this can include the nasal demons from UB.
- brundolf 5y agoIt can't log a warning because it can't actually detect side-effects. The only way to "detect" them is to push them to cause breakage in the app logic
- bob1029 5y agoBlazor does something like this but for different reasons (ServerPrerendered mode for lower latency UX). It took me a solid day to realize the behavior was intentional. There were no problems but it was one of those "I cannot build an empire on these uncertain foundations" moments.
- dataangel 5y agoIf they don't want people to just end up depending on this behavior in a stateful way instead they should actually deliberately rely on non-determinism, call the functions a small random number of times.
- dymk 5y agoIt's not meant to thwart adversarial cases or situations where programmers are trying to "trick" strict mode. It's a safety net for those who know the "rules" of react, but still might accidentally introduce side effects and want to catch it.
- brundolf 5y agoEven just as a safety-net, there's precedent for this. Golang intentionally randomizes hashmap key iteration order to prevent people from relying on it: https://stackoverflow.com/questions/9619479/go-what-determines-the-iteration-order-for-map-keys#9621526 https://stackoverflow.com/questions/9619479/go-what-determin...
- supermatt 5y agoPretty sure its been doing this in dev mode since 0.14. Maybe earlier...
- tomduncalf 5y agoYeah I think what might be going on is a combination of Next.js switching to strict mode at some point, and React 17 hiding the double logs. I’d never seen the behaviour before (but had never explicitly used strict mode - to be honest I didn’t know it existed!) and switching between React 17 and 18 was enough to make it happen, so I assumed that was the responsible change, but actually it’s a bit more subtle. Thought it might be useful to share for people who are using Next.js anyway, as for me it just started happening when I created a new project with no explanation which led to me thinking I was losing it for a while, lol.
- joshribakoff 5y agothe hacker news title just isn’t precise about what part of it changed and doesn’t even link to the relevant article https://reactjs.org/blog/2022/03/29/react-v18.html#new-strict-mode-behaviors https://reactjs.org/blog/2022/03/29/react-v18.html#new-stric...
- marcus_cemes 5y agoIf anyone is curious as to why, I believe it's the only way that React can "poke" your code to see whether it really is side-effect free, and therefore concurrent mode/suspense compatible. The idea is that React can re-render your component whenever and however often it likes whilst getting the same results for the same input state, without this assumption, new patterns such as concurrent mode and suspense become very difficult to implement. Double rendering is a rudimentary dev-time test to see whether the output is actually the same to try and help you avoid bugs later on (data races for example), without enforcing a language, such as ReasonML or Elm. Alternatives would require really complex static analysis or strict ESLint rules. There may be other reasons as well. Sometimes there's a need for side effects, the DOM is very stateful and unfortunately React is not quite fast enough for smooth animations at 60fps on most devices, it's also a lot easier to use global variables (no judgement) rather than properly organising state hooks and component props. React started to move away from OOP and more towards FP, starting with hooks and function components. One of the driving reasons behind this was that function components could be minified a lot better than classes. Reducers, side effects, pure, memo, are terms that are more known in the FP land. I also think it gave them a lot more control as a framework to implement patterns such as concurrent mode as functions are stateless unlike classes, moving the state up into the React framework itself. This means several versions of the state can exist at once, and React just runs them through your function to generate the view. Side effects introduce unpredictability and nondeterminism using this pattern. This FP style can be confusing to new programmers (anyone feel like trying to explain useEffect() to a 5-year-old? Anyone?), especially those that are used to global variables or manipulating the DOM directly (JQuery), but understanding the OOP render cycle had its own learning curve. Frameworks such as Elm are met with much love, even if React's hook-based approach is a little different. Small aside, I also think that's where useEffect() got it's name from: side effects. It's where you put all those nasty network calls and setTimeout()s, and then scratch your head when you forgot to cancel/unregister them when the component unmounts and React complains that you tried to change the state on an unmounted componment. Suspense (hence the need for side-effect free functions, hence this double-rendering problem) should help with avoiding putting "if (stillMounted) { ... }" everywhere in useEffects with async code by giving React more control over the render/update process. Full disclaimer, I haven't actually written React in a while, not since I found out about Svelte, I'm curious, how to authors of libraries such as React/Framer Motion handle the double rendering issue when interacting with the DOM directly? Other frameworks that I know of that avoid the complexities of concurrent mode/suspense either very stricly enforce this FP style, such as Elm, or try to be fast enough to forgo the virtual DOM and embrace mutable state, events and two-way data binding such as Solid and Svelte.
- DustinBrett 5y agonew-strict-mode-behaviors https://reactjs.org/blog/2022/03/29/react-v18.html#new-strict-mode-behaviors https://reactjs.org/blog/2022/03/29/react-v18.html#new-stric...
- cpeterso 5y agoTo further restrict these component methods from having side effects, could React call these methods using a Proxy or eval() context that blocks these methods from reading or setting variables in the global context?
- brundolf 5y agoWould be extremely hard, especially given we're not just talking about core APIs like fetch: React code uses closures like crazy, and you couldn't wrap and probably couldn't eval-shadow every single object in scope that might get mutated You might be able to catch some common cases, but that might give a false sense of security Enforcing against side-effects in JavaScript is pretty much a lost cause. Even merely accessing a property can cause side-effects, thanks to getters and proxies
- sam0x17 5y agooh god, the horror of the fact that this is necessary! As someone who has largely and happily sidestepped the whole reactivity thing for the last decade, it really sounds like that whole world is starting to implode and contradict the very things it promised in the first place (simplicity). Please let web assembly deliver us out of this js-ridden mess \o/
- scarmig 5y agoI'm a skeptic of the promises of reactivity and a fan of web assembly, but the main reason reactivity is a pain is because UIs are inherently a painful domain, not because of JS itself.
- dolni 5y agoWeb browsers have had callbacks for DOM elements since... forever. Why did we need to add an entire component framework like React on top of it? Everything on a web page does not need to be interactive. It's a document first. Web applications are cool. Maybe React makes sense there. _But every site does not need to be a web application!_ And yet somehow, React and company became the industry default.
- culi 5y agoReact doesn't have to be your whole website. Even the starter tutorial in the docs starts off by showing you how to just add in React through a CDN and make a react app as a single component of a bigger webpage Obviously that's not at all how it's used in practice (or even in the create-react-app template), but I do think there's a place for something like React
- eyelidlessness 5y agoI agree with you to the extent that components = interactive. But there are other benefits of a component model, even for pages which are mostly static. They allow you to develop the same page and its various (reusable) parts with the same code for rendering the static parts and the dynamic parts. This is especially nice on sites where the dynamic-to-static ratio may vary significantly between pages, or between areas of the same page. This use case hasn’t been particularly well supported by many component libraries (React among them; Marko being a long time notable exception), but it’s an area that’s currently having a bit of a renaissance: there is of course Marko which has supported this model for years, joined by other notable frameworks like Astro and Qwik. Even React is moving more in this direction with Server Components, just targeting other use cases. The component model will continue to thrive, not because everything is interactive, but because components are an excellent abstraction for developing for the web (and a lot of other formats).
- malkia 5y agoThat sounds really reactionary!
- azangru 5y agoUm, I remember StrictMode was running the code twice since forever. Probably since the time it was introduced in React 16-something. Has anything changed in React 18?
- franciscop 5y agoYes, they used suppressed the double logging with the double render, but now in 18 they show the second logging as well, but with dimmed colors: https://twitter.com/dan_abramov/status/1507750167390400512 https://twitter.com/dan_abramov/status/1507750167390400512
- mbell 5y ago> In React 17, React automatically modifies the console methods like console.log() to silence the logs in the second call to lifecycle functions. However, it may cause undesired behavior in certain cases where a workaround can be used. I feel like when you've gotten to the point that something like this has been proposed and accepted as a good solution to a problem your framework is facing, it may be a good time to stop and reconsider the approach of the framework.
- btown 5y agoThis gets at something deeper: React has a policy of “in the dev environment anything goes; we will mess with your code and its runtime performance to help detect bugs or future bugs; you want us to do this, even if you think you don’t.” But this assumes good QA and staging environments exist and are used correctly. In reality, many users of React test on local and immediately deploy into production. The more the environments differ, the less friendly React is to these users. Timing issues and race conditions may surface in production. And thank goodness this isn’t Python where a log statement can consume an iterable if you allow it to do so! And for those saying “Strict Mode is opt-in” - your coworker may opt in your whole app without you knowing it. Hopefully you see the PR or the Slack thread. So much in React now enables this “spooky action at a distance.” The React team has put a lot of thought into balancing the performance needs of large codebases with the stability needs of smaller teams with less robust SDLC. I can’t think of a better workaround. But it’s still going to cause chaos in the wild.
- andrewingram 5y agoI agree with the sentiment of not being comfortable with dev and prod differents. That said, with React these dev mode differences are intentionally about making obscure production issues (such as timing ones) rear their ugly heads in development. The goal is that if your code works without issues in dev, it’ll work even better in production. It’s a thin line for sure, but I broadly support the way they’ve done it so far.
- kmlx 5y agocan be solved this way: - npm start when developing - deploy on dev using the artefacts from npm run build builds take a bit longer this way, but it guarantees QA will test using a non-dev build
- deleted 5y ago[deleted]
- baxuz 5y agoIt already did that since v16
- tmpfs 5y agoI recently wasted time figuring out why my code was getting executed twice and found this out the hard way. I think this is a particularly good example of overreach by Javascript developers and at the very least a console.warn could have notified me of this. I just removed strict mode as i think this is an awful side effect to implement. In my case, instead of helping me find bugs it had me hunting non-existent bugs, sigh.
- LAC-Tech 5y agoThe fact that so many devs think react is a fundamental part of browser based applications has really pushed me away from front end. I used it for years and I just don't see it.
- sibit 5y agoThis, 100%. I love doing UX/UI work but everyone (I've worked with) is always pushing to use React for everything and it has led me to moving to more of a back-end / full-stack role over the last few years. A job I recently left was building a B2B site where customers could log in and order files that they needed printed & delivered. The back-end was going to be Laravel but instead of simply using Blade (Laravel's PHP templating engine) we had to make a RESTful API so we could use NextJS and React. IMHO it was beyond overkill for a site where users would basically just be checking boxes and clicking an "order" button. I just assume nowadays that whenever anyone pushes for React, NextJS, or any AWS Lambda based over-engineered auto scaling microservice solution to a simple problem they're just looking to pad their resume before jumping to their next job.
- MarcelOlsz 5y agoSame boat here. Once you go Vue you never go back. Miss me with hooks.
- FractalHQ 5y agoVue was a stepping stone to Svelte, once you try it you never go back ;)
- MarcelOlsz 5y agoReally? I've seen it a couple times here but I wasn't really sold on it. Vue to me is already top tier, what does Svelte do even better? Would love to give it a shot at some point but not really dying to experiment outside of work right now.
- sandwichinvest 5y agoThis, while unexpected, is not the end of the world.
- deleted 5y ago[deleted]
- IceDane 5y agoITT a bunch of clowns who don't know anything about React, haven't written a single line of it in their lives and don't care one whit for spending 10 seconds to understand why this is a useful feature, or how it isn't even a new feature. Never change, HN.
- jiggawatts 5y agoI actually wish more languages did this kind of thing down to the compiler level. I see a lot of C code especially go through an "evolution" where it starts out being compatible with only one platform (typically x64) and then it slooooowly acquires wider compatibility one issue ticket at a time. Wouldn't it be great if all that english text in the language standard documents were enforced and/or exercised at both compile time and (dev) runtime? Instead of just "dev" and "release", I would love to see the default be "evil". As in... in "evil" mode the compiler will abuse every rule, randomise everything that is not explicitly specified, and run your code on a virtual machine that has 19 bit ints, 23-bit pointers, bizarre memory models, or whatever to make sure that your code is truly robust, not just "mostly robust", which is like being "almost pregnant". For a real life example, there was a bug lurking in the Java standard library (and several other languages!) where "sort()" wouldn't work on maximum-length arrays because they used the naive (a+b)/2 algorithm to find the mid-point of a segment and hit an integer overflow. If there was an evil runtime with tiny integer sizes, they could have hit this with small array sizes and realised their mistake early instead of shipping broken code that blows up and crashes on the biggest and hence most important servers in the world... sometimes. Taken to the n-th degree, haven't you wondered how many similar bugs lurk in standard libraries? Which "algorithms" work on max-length arrays? Which only work on MAX/2? Which on MAX/4? Which on (MAX/8)-53? Who knows! I don't. You don't either. Some or all of these could blow up in your face at the worst possible time and you have no way to test this[1]. An evil VM could be devised to test things like: "Throw any exception that could be triggered a small percentage of the time, or at command". "Pretend memory is far more constrained than on the specific physical host where the code is being developed." "Introduce network latency, UDP packet reordering, random packet drops, and every possible IO error that the host operating system might throw." "Use a different memory consistency model to the current physical CPU." "Pause threads randomly, and otherwise randomize timing and interleaving behaviour to the maximum extent allowed by the spec." "Simulate processors with different bitness, such as combinations of integer size vs pointer size." "If the spec doesn't require any particular boolean representation, then run the code with different ones randomly. E.g.: 1, -1, and even random non-zero values for true." "Database queries that don't specify the sort order are often returned reversed or randomized." "i18n settings that aren't explicitly specified are randomized. Your UI language is Turkish, the keyboard is French, and the region is Japan." "All external environment variables are randomized and/or substituted with similar but potentially invalid values. You must explicitly specify the whitelist of environment variables that you require to be stable."[2] [1] Except for running code intended for 64-bit in 32-bit mode and testing with 2-4GB data sizes. However, there are other potential issues for which such tricks don't work. Anyway, this is just a "poor man's evil VM". What you want is a full flexible evil VM that can constrain anything. [2] Sounds like a pain in the backside for you? Now put yourself in the shoes of the poor admin that has to make this stupid thing work from a compiled binary 17 years later and has to reverse engineer the possible environment variables using a hex editor and a glass of scotch.
- davman 5y agoRelated: I have an outstanding Bounty on a SO question for how to write a reusable useRef hook that only runs on the very first mount under StrictMode conditions: https://stackoverflow.com/questions/71695213/equivalent-isfirstrender-hook-for-strictmode-in-development-mode https://stackoverflow.com/questions/71695213/equivalent-isfi...
- reverseblade2 5y agoOff topic but anyway To all react devs, If you are not using web components (not mutually exclusively) then you are doing it wrong.