Vladimir Klepov as a Coder2024-02-16T00:00:00Zhttps://blog.thoughtspile.tech/Vladimir Klepovv.klepov@gmail.comFrom engineer to manager: what I love, what I hate2024-02-16T00:00:00Zhttps://thoughtspile.github.io/2024/02/16/eng-to-em/<p>It's been almost 2 years since I moved to a team lead role, then to a full-time engineering management position after the expansion of our team. I've been a front-end developer for 7 years before that, and initially I took the "advanced individual contributor" career track before doing the management turnaround. How's it been? Bumpy, but fun. In this article, I'll share the things I love and hate about my current job.</p>
<h2>Love</h2>
<p>Let's start with the positive side of management positions. There's plenty to love, honestly.</p>
<h3>Impact</h3>
<p>First things first, I adore the power to improve the product we're building, and the overall team well-being that comes with a management position.</p>
<p>As an engineer, you'll sometimes find yourself in a tough spot with little to no power to change things. Early morning standups are a chore? Code quality sucks? The new feature makes no sense? As a manager, the power to change is yours: you get both the formal and informal authority to change things for the better, and to make yourself and your team happier.</p>
<p>Your words have weight. If you, an IC, say "guys, we really should write tests", everybody goes "oh, crazy old Vladimir, all grumpy again, haha, where do you see tests fit here?". If you, an EM, casually say "guys, we really should write tests", you might be surprised to find the tests unexpectedly growing in different places. Pleasant.</p>
<h3>Career opportunities</h3>
<p>Being an engineering manager is a more promising career opportunity than an engineering track. This might be controversial, but hear me out:</p>
<ol>
<li>The EM is normally higher-paid than the basic team-level engineering grades (junior / middle / senior).</li>
<li>There <em>are</em> higher IC grades (staff, principal, president of code, whatever) in the EM+ salary bands.</li>
<li>These staff+ IC jobs are concentrated in larger tech companies, because those have tougher technical challenges. Almost every software team in every company has a leadership position.</li>
<li>The management career ladder is "taller" than that of ICs. <em>Yes,</em> the chances of becoming a CTO are <em>slim,</em> but it's an opportunity that's just not there for a pure IC with no management experience.</li>
</ol>
<p>All in all, I believe the demand for EMs to be steadier than for staff+ engineers, and this path gives you more opportunities at the later stages of your career. On a related note...</p>
<h3>Transferable skills</h3>
<p>Management skills are more widely useful than an IC engineering role. A decent front-end engineer with React experience won't have trouble moving to another front-end framework, and can probably transition to a back-end / mobile engineering role with -1 grade (a year handicap or so). That's not bad.</p>
<p>What roles are available to someone with engineering management experience? First, you can easily take on a team with a wildly different focus — mobile developers, infrastructure, ML engineers. You'd need some time to get up to speed on the big-picture technical struggles of your new team, but most companies would take this shot. If you don't want to be an EM any more, you're well-positioned to move to a project or product management role.</p>
<p>If the entire tech market falls into decline, many management skills would still work for other industries. While I don't see a big flow of tech managers moving into construction business (tech <em>does</em> pay well), there's one alternate path to consider — entrepreneurship. Involvement with people and business decisions makes for great training before starting your own business.</p>
<p>So, being a manager gives you quite a bit of career flexibility, and makes you less vulnerable to future technological shifts.</p>
<h3>Less knowledge rot</h3>
<p>Suffering from <a href="https://www.smashingmagazine.com/2016/11/not-an-imposter-fighting-front-end-fatigue/">front-end fatigue?</a> Can't keep up with the newest shiniest frameworks and tools? Management's got you covered!</p>
<p>The "hot new" agile / kanban / scrum methodologies are 20–30 years old. The basic meeting types (demos, dailies, 1-on-1s) have been developing for centuries. At the core, you have teamwork and human interactions, which haven't changed that much since the beginning of humanity.</p>
<p>My grandfather was a big railroad boss in the 70s, and we can sensibly discuss some of my work challenges. "Oh, you have this talented slacker? Give him some big important task, let's see what he's worth." When it comes to computers, he's more like "I'd like a shovel big enough to throw all your silly gadgets into stratosphere."</p>
<p>So, if you're tired of keeping up with the latest hot thing in tech, a management role can provide a well-deserved relief. <em>Do</em> keep an eye on what's happening on the tech side of things, but there's no urgency, and no need to get real deep.</p>
<h3>New challenges</h3>
<p>Frankly, after 4–5 years of working in a particular tech area, you can solve the vast majority of practical problems well enough. If you want some work challenge, you can:</p>
<ol>
<li>Slightly alter your stack — say, a new FE framework. But it's unlikely to keep you engaged very long.</li>
<li>Make a broader career shift — e.g. frontend to backend. This would probably give you another couple years of fun, but such transitions are, in my experience, either random (e.g. your BE dev quits and someone has to fill the role), or hit your salary.</li>
<li>Invent problems out of thin air — rewrite everything using a new library, or handle 9000 RPS "for the future". Fun, but most of the time it's more harm than good for your team and business.</li>
</ol>
<p>Of all the possible career moves a seasoned engineer can make, switching to management gives you the most new challenges (years worth of new stuff to learn) without hitting your salary.</p>
<h2>Hate</h2>
<p>As much as I like the challenges and impact of my new role, and the practical career benefits, I'll be the first one to admit it has downsides as well.</p>
<h3>Corporate BS</h3>
<p>As an engineer, I hated bloody corporate BS: individual performance reviews, useless deadlines, company-enforced restrictions on processes and tech stack. Well, congratulations, as a manager you are the sheriff of these practices, whether you believe in them or not.</p>
<p>As a leader of a team in an org with performance calibrations, I must nominate 1 person who hasn't been <em>working hard enough</em> every 6 months. This human chess is soul-sucking, but I can't make it go away — if I don't offer a sacrificial teammate, someone will be picked randomly further down the process. Crazy shit.</p>
<p>Sometimes you can negotiate a bit, or hack the process, e.g. assign "below-expected performance" on a round-robin basis, but to your team you'll sometimes be the <em>corporate monster.</em> Sigh. And I haven't even been through the real tough stuff like layoffs, closures and reorganizations.</p>
<h3>Awkward social situations</h3>
<p>I've made it to an engineering management position by being good at building stuff. I've been prepared to help with technical decisions, give career guidance, tune processes and set up automation as needed. In fact, a large portion of my job is debugging social tensions and psychological insecurities of people.</p>
<p>Your junior engineer comments out a few tests to deploy a feature preview. The QA person sees this, and is very pissed because your whole team apparently does not respect the QA role and the value they provide. Restore trust.</p>
<p>A project manager makes an unsuccessful joke that hurts your designer, who's now crying. Make the PM apologize. Am I a kindergarten teacher or something?</p>
<p>Boy, I'm no psychologist, and I can't say I'm exceptionally good with people. This part of my job is quite hard, trying to fake it til I make it here.</p>
<h3>Office hours</h3>
<p>Life of an engineer is relatively relaxed. If you don't have anything urgent, you can go lay on the grass for half a day, thinking about the future of your project or something. You can miss a few meetings on short notice, no questions asked.</p>
<p>Now, you're an EM. Try going and lying on the grass for a few hours. You come back to a messenger full of problems: your intern can't work because she forgot how to npm install; a senior manager wants to discuss some potential feature; release has derailed. Also, you can't really skip a meeting you're supposed to facilitate / organize without some up-front preparation.</p>
<p>This <em>might</em> improve as your team matures and builds better processes, but in general you feel office hours much more as a manager, and your work-life balance directly depends on how good you are at your job.</p>
<h3>Long feedback loop</h3>
<p>The final thing I hate about management is the long feedback loop of your actions. Most engineering tasks show the result quite fast: new features take weeks to months, and if that's too long for you — fix a bug and see happy users the next day, or refactor some code and watch complexity decrease in a few hours. Amazing!</p>
<p>You're a manager? Well, very few of your actions produce a visible result in under a month. Suppose your team has grown too large, and you want to split it up. You must pick a well-rounded set of engineers for the new team, talk to everybody involved to see how they feel about such a change, arrange new regular meetings, set up processes and communications, do some jira magic, maybe isolate the codebases of sub-products. If you think it can be done in a week, well, you're wrong.</p>
<p>Then, even the right changes can make things get worse before they get better. Say you're understaffed, and you decide to hire. In the short term, you spend hours and hours interviewing, and a new team member won't get up to speed right away, sucking out precious time for onboarding. It's sometimes hard to see the long-term goal behind the short-term inconvenience.</p>
<p>So, while engineering problem-solving is often fairly straightforward, management changes are more similar to large-scale refactorings. You won't see any quick improvements, which can be frustrating.</p>
<hr />
<p>To sum up, moving from an IC engineering role to a management position has been a rollercoaster ride for me, with both bright and bleak spots. Here's what I love:</p>
<ul>
<li>The wider impact on the product and team.</li>
<li>Management is a great long-term career track: it gives you more job opportunities than a staff+ IC, the flexibility to move between different technical areas and roles, and skills that will be relevant across various industries for years to come.</li>
<li>If you're bored with your field of tech expertise, moving to a management role is a great way to bring the challenge back into your job.</li>
</ul>
<p>And here's what I hate:</p>
<ul>
<li>Enforcing corporate decisions and policies can be soul-sucking.</li>
<li>Dealing with social tensions and psychological insecurities of people isn't something I was ready for.</li>
<li>It's hard to go offline even for a few hours without preparing in advance.</li>
<li>Your actions have long and non-linear feedback loops with <em>very</em> delayed gratification.</li>
</ul>
<p>Now, is this career move the right one for you? If you enjoy challenge and responsibility, and you get an opportunity — I'd say go for it! Yes, management is not a fit for everybody (I'm not even sure it fits me TBH), but it's a great experience that would surely expand your skill set and make you see engineering work from a new angle. If you totally hate it, you have plenty of time to go back into coding =)</p>
The most useful programming language2024-01-09T00:00:00Zhttps://thoughtspile.github.io/2024/01/09/top-languages/<p>Aspiring developers often ask me what's the best programming language to learn. Personally, I mostly work with JS — solid choice, but everyone and their dog learns JS these days, so it might be time to add some diversity. I'm curious — which <em>single</em> programming language covers the most bases for you, and gives you <em>most</em> career opportunities for years to come? That's the question we'll try to answer today.</p>
<p>Here's the plan. I made a list of 8 tech specializations:</p>
<ul>
<li>2 web development areas: back- and front-end. Both pretty big areas, and ones I have most experience with.</li>
<li>Mobile and desktop native app development. Native app development (especially desktop apps) seems to have fallen out of favor, but there's still enough work in these areas.</li>
<li>Quality assurance automation. QA grows along with engineering, and increasingly relies on automated tests.</li>
<li>Embedded systems. We'll focus on microcontroller programming, not fat boxes with a full windows / linux OS. Quite a promising area with the growth of IoT.</li>
<li>Game development. Granted, I don't know much about this area, but I'll do my best to cover it as well, as many developers dream of building a fun game someday.</li>
<li>Data analysis and Machine Learning. One of the most hyped areas of the last decade.</li>
</ul>
<p>The contenders are the usual suspects from <a href="https://www.tiobe.com/tiobe-index/">TIOBE top 20</a>: python, C, C++, Java (grouped with Kotlin and other JVM languages), C# (again, throw in VB and other .NET languages), JavaScript (and TypeScript), PHP, Go, Swift, Ruby, Rust. I left out SQL and Scratch, because they're not general-purpose languages, and Fortan with Matlab, because they aren't really used outside of scientific / engineering computing.</p>
<p>A language scores 1 point by being the industry standard in the area — vast community and ecosystem, abundant jobs. Being useful for <em>certain</em> tasks in the area gets you 0.5 points.</p>
<p>So, let's see what languages will make you the most versatile engineer, shall we?</p>
<h2>Backend</h2>
<p>Let's start with the simple one — Java, C#, Python, PHP, Go and Ruby are all excellent back-end programming languages. Of these, I'd say PHP is slightly <em>more</em> useful as many low-code solutions rely on it, and Ruby is steadily declining. Still, all these languages have earned 1 point.</p>
<p>Next, 0.5 points go to:</p>
<ul>
<li>C++, used in high-load and time-critical scenarios,</li>
<li>JS — node.js is often used to support front-end, but there aren't that many strictly back-end jobs for JS developers.</li>
<li>Rust — still not that widely used, but growing fast.</li>
</ul>
<p>The only languages to fail here are Swift (technically usable on server via e.g. <a href="https://vapor.codes/">vapor</a>, but I couldn't find any jobs in this stack) and C.</p>
<h2>Frontend</h2>
<p>Obviously, JavaScript is <em>the</em> language for front-end developers, which runs natively in browsers. But, surprise, other languages still qualify!</p>
<p>All solid back-end languages (Java, C#, Python, PHP, Go, Ruby) get 0.5 points, because you can solve many UI problems by rendering HTML server-side the old-school way. C# has a slight edge here, since <a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor">blazor</a> is quite smart and popular.</p>
<p>C, C++, and Rust score 0.5 points because they can be compiled to WebAssembly and run in the browser — just look at <a href="https://www.figma.com/blog/webassembly-cut-figmas-load-time-by-3x/">figma.</a> Rust also powers some cool JS tooling, like <a href="https://biomejs.dev/">biome</a> and <a href="https://swc.rs/">swc</a></p>
<p>The only language to fail here is, again, Swift.</p>
<h2>QA automation</h2>
<p>The topic of QA automation is really simple.</p>
<p>Java and python get the cake — Allure, Selenium, JUnit, and pytest are the most sought-after automation tools on the market right now.</p>
<p>JS gets 0.5 points for playwright and cypress — the preferred tools for testing complex web front-ends. <em>A few</em> automation tools support C# — worth 0.2 points.</p>
<h2>Mobile apps</h2>
<p>Another straightforward area. Android apps are written in JVM languages (Java / Kotlin), iOS is integrated with Swift (finally).</p>
<p>JS scores 0.5 points, because you can effectively build apps with <a href="https://reactnative.dev/">React Native,</a> and you can get pretty far with <a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps">PWA</a> or a good old WebView.</p>
<p>Another 0.5 point for C#, thanks to <a href="https://dotnet.microsoft.com/en-us/apps/xamarin">Xamarin</a> and <a href="https://dotnet.microsoft.com/en-us/apps/maui">MAUI.</a></p>
<h2>Desktop apps (windows / linux / MacOS)</h2>
<p>The three kings here are C++, C#, and Java. JS gets 0.5 points, again, for <a href="https://www.electronjs.org/">electron</a> — disgusting or not, it's widely used. Another 0.5 points for Swift, because that's what you build MacOS apps with, but MacOS computers are relatively niche.</p>
<p>Rust has the highly-hyped <a href="https://tauri.app/">Tauri</a> project for building desktop apps, but it's not that widespread, and I'm not aware of any high-profile apps using it. Let's give each 0.2 points for the effort and check back later.</p>
<h2>Embedded systems</h2>
<p>Embedded systems are usually tight on resources, so compiled languages are the way to go here. Basically any embedded job requires C and C++.</p>
<p>Rust is, as usual, <em>very promising,</em> but not that popular yet, so 0.5 points. Another half-point for Python — used for edge computer vision and prototyping, but struggling with high memory requirements.</p>
<h2>Game development</h2>
<p>The primary languages in big gamedev are C++ (used in Unreal Engine) and C# (for Unity).</p>
<p>Since mobile games are a thing, Java and Swift get 0.5 points each, because that's what you'll likely use here. Another 0.5 points for JS (browser games).</p>
<p>Rust <em>should</em> be quite a good fit for games, but (as expected by now) it's not quite there yet.</p>
<h2>Data Analysis & Machine Learning</h2>
<p>It's no secret that Python is the language of choice for anything data-related, and most of the cutting edge stuff happens, well deserved 1 point here.</p>
<p>But do you know there's another top language to get your piece of Data & ML hype? Big companies have a lot of data, right? And big companies love Java. So, many big data tools (especially coming from Apache — Hadoop, Spark, Jena) work with Java, and most data jobs require experience with python <em>or</em> java, so another 1 point for java.</p>
<p>On to more surprises. Large chunks of data-heavy python libraries are actually written in C / C++ — e.g. over a third of <a href="https://github.com/numpy/numpy">numpy</a>, or most of <a href="https://github.com/ggerganov/llama.cpp">LlamaCPP</a> — which earns both half-a-point. As you'd expect, Rust is also gaining traction for this use case with stuff like <a href="https://github.com/pola-rs/polars">pola.rs</a>, so another 0.2 points!</p>
<p>The final half-a-point goes to JS for powering much of the UI / visualization stuff (see e.g. <a href="https://github.com/bokeh/bokeh">bokeh</a>).</p>
<hr />
<p>Before we reveal our final ranking, let's weigh the categories, because they're <em>not</em> the same size. I've used some back-of-the-napkin analysis of job postings and sizing of reddit / linkedin groups and my personal experience. With <em>backend</em> as our reference, I'd say <em>frontend</em> is roughly the same size. Mobile development is surprisingly sizable — let's give it a 0.6 weight. For QA, I'd say 0.2 makes sense, as 1 QA per 3–5 devs is a normal ratio, and manual QA is still a thing. Desktop is easily the smallest area, looks like a 0.1 to me. For gamedev, 0.5 is just my random guess. Finally, there are surprisingly many data people — with the good salaries, let's make it a 0.6.</p>
<p>Putting it all together:</p>
<p><img src="https://thoughtspile.github.io/images/top-languages.png" alt="" /></p>
<ol>
<li>Java takes the first spot by a good margin by topping 5 categories, and having some gamedev / frontend capabilities. Place other JVM languages (especially Kotlin) around here, but with a discount since they're not as widely used.</li>
<li>The next three are really close, but JS gets <em>slighly</em> ahead by being average at everything except embedded, even though it's only the top choice for front-end development.</li>
<li>Python and C# tie for the third place. Both are top-tier backend languages with other strong areas (QA / ML for python, desktop and gamedev for C#).</li>
<li>C++ is not that far behind either, as it's still the top language when it comes to efficiency. It also steps into other languages' realms when they need some speedup (WebAssembly / ML).</li>
<li>Next come "three backend friends" — Go, PHP, and Ruby. All top-notch languages for building web backends, but not much else beyond that. Of these, Ruby is on the decline, and PHP and Go both have their separate niches.</li>
<li>Rust does not score that well, but still makes it into the top 10 — not bad for such a new language. It has great growth potential by eating at the traditional C++ areas, super excited to see where it gets in 3–5 years.</li>
<li>We all love good old C, but C++ looks like a better fit for complex systems.</li>
<li>Swift comes in last — fair enough for a language that's only useful for the products of one single company.</li>
</ol>
<p>Perhaps surprisingly, the <em>single most useful</em> language is Java. Python and JS, beginner favorites, come strong, with a very different focus. C# perhaps deserves a bit more attention.</p>
<p>Overall, today we've learnt about many amazing technologies that allow languages to sneak into each other's territory. If you were to start anew, what language would you learn?</p>
I conducted 60 interviews in 2 months — here's what I learned2024-01-06T00:00:00Zhttps://thoughtspile.github.io/2024/01/06/60-interview-lessons/<p>It's hard to believe, but, starting mid-october 2023 I conducted 60 technical interviews and hired 10 people into our team. It's been <em>extremely</em> tiring: around 80 hours of <em>active</em> interviewing, plus writing interview reports, plus screening CVs and take-home assignments, plus onboarding new members — all while doing my normal work stuff. Still, I feel like I learnt a lot in the process — things that would help me as a candidate in the future, and might help you land your next job.</p>
<p>Note that I'm a fairly relaxed interviewer, and, as an internal startup of a large tech company, we generally have a more <em>humane</em> hiring process, so your mileage may vary. Still, I've done my best to pick the tips that I feel are universally applicable.</p>
<p>Here are nine insights I took out of this experience, in no particular order:</p>
<h2>Be generous with your "expected income".</h2>
<p>Say you're a solid higher-middle engineer, and you ask for a senior salary. My thought process: OK buddy, it's a bit more than reasonable <em>now,</em> but I won't have to fight for your promotion 8–12 months from now when you get there, and I don't have to spend another 12 hours of my own time (and leave my team understaffed for another few weeks) looking for a real hardcore senior, so I'll let you have it. Now suppose you ask for a junior salary. It's suspicious — <em>why is your bar so low?</em> Is there someting about your work performance you're not telling us? So, do your research on reasonable salaries for your level of experience, and aim <em>slightly</em> above that.</p>
<h2>Ask the right questions.</h2>
<p>I always leave time for the candidate to ask <em>me</em> questions — obviously, this lets the candidate probe what it's like to work at our team, but it's also the best opportunity for me to learn what really matters to the candidate. I've never been much of an asker myself, but now I see that "Thanks, I have no questions" does not look good — if anything, it paints you as someone who doesn't care. Here's a short list of good questions:</p>
<ol>
<li>What does the daily work in this role look like? Harmless.</li>
<li>What features are you building next? Caring about the overall product, nice. Sometimes the answer is "I can't disclose this secret", but not that often.</li>
<li>Anyting about processes or team structure: how many people are on the team? How often do you release? What regular meetings do you have? Interested in organization, might want to be a team lead someday, great.</li>
<li>Anything tech-related: which framework do you use? Why did you pick framework X? How do you test your app? Especially suitable for junior- to middle developers who are most involved in hands-on work.</li>
<li>What kind of tasks do you see me doing? Again, just a good neutral question, because responsibilities for any role differ wildly between companies.</li>
<li>What growth / promotion opportunities does this position have? Cool trick, flipping the feared "where do you see yourself in 5 years" question against the hiring manager.</li>
</ol>
<p>Here are a few questions that are <em>not</em> very good:</p>
<ol>
<li>Do you use jira and github? It's a minor detail, won't you be able to work with youtrack and gitlab?</li>
<li>Do you sometimes work late? Only if something breaks, but overall this question makes you seem a bit <em>lazy.</em> People on poor teams that routinely overtime aren't likely to answer this question honestly, at any rate.</li>
</ol>
<h2>Social skills matter.</h2>
<p>I understand that not everybody is super outgoing, but if we already feel awkward 1 hour into our acquaintance, why work together — to feel awkward for months to come? Just a few tips anyone can follow:</p>
<ul>
<li>Be energetic. You're tired, I'm tired, we're all tired of endless interviews. Are you just tired today, or generally always too tired to get anything done? I know it's easier said than done, but try and show me all the energy you have left.</li>
<li>Show respect. People enjoy being respected. Very easy one: <em>you have a great product. Sounds like you have a great engineering culture. This is one of the most interesting interviews I've ever seen.</em> Like, I know you don't necessarily mean that, but subconsciously I'm very pleased: "oh yes, I'm very proud of my interview process, thanks for noticing"</li>
</ul>
<p>On a related note...</p>
<h2>Provide conversation opportunities.</h2>
<p>Q: Do you use TDD? Bad answer: "no". Good answer: "no, but I've heard of it. Interesting approach. Does your team use TDD?" Now you get to spend 5 minutes talking on your terms instead of being bombarded with random questions, <em>and</em> you come off as someone curious about stuff.</p>
<p>On another related note...</p>
<h2>It's easy to hurt people.</h2>
<p>People normally ask you about stuff because they care about it. So, again, the interviewer askning "do you use TDD?", presumably, likes TDD and uses it. So, the worst answer: "no, TDD sucks, it's pure waste of time for idiots." A <em>rare</em> interviewer might appreciate you having a strong opinion on a topic, but to most this just paints you as a jerk, kinda like <em>"Here's a photo of my children — "I hate children, and yours are especialy horrible".</em> Not smart.</p>
<h2>Smart talk is not your friend.</h2>
<p>Saying stuff like "our front-end guild evaluated several cutting-edge approaches to testing universal applications" only makes you seem smart <em>if</em> you can elaborate on that topic: <em>what</em> these approaches were, the pros and cons you found, what tradeoffs you made for your final decision. If you can't answer a follow-up question beside "we settled on jest, not sure why", it was better to stay away from that topic altogether. Related: "in code reviews, I always consider the optimality of the algorithm selected" (proceeds to estimate the time complexity of comparison-based sorting as O(1). I never ask this unless the candidate boasts about her algo skillz).</p>
<h2>Admit your mistakes.</h2>
<p>Don't know an answer? Your code has a bug? It's always better to <em>admit it</em> and then try to come up with something at the spot than trying to talk your way out of it. <em>Event loop? Sure thing, I'm an expert on loops. It's the way events are looped. Uses logarithmic weighing.</em> Again, this makes you look like a candidate with big mouth and small hands. I <em>have</em> seen a couple of people who could talk their way out of any situation, but I honestly think with such skills you'd do better in a different line of work, like international relations, or selling financial services. Note that you really <em>should</em> give it your best shot — giving up at the first sign of trouble is not a good impression. If you genuinely have no idea — see <em>conversation opportunity:</em> "Event delegation? Tough luck, never heard of it. <em>Would you tell me about it so that I learn something new today?</em>"</p>
<h2>Make yourself memorable.</h2>
<p>It's hard to keep detailed profiles of 10 candidates in mind — after a good interview streak all I remember is the general impression (great / OK / horrible) and a <em>few</em> truly notable things. This guy worked for some crypto scam that went bust, that girl had a cute dog that was trying to eat the camera. The worst you can do is be a totally <em>neutral</em> candidate — we've had an interview, but I can't remember any details. So try and sneak some anecdote, or wear a silly scarf — <em>something</em> to remember. This point is especially important for intern / junior positions — online JS bootcamps do a good job of covering the basics, and it's really hard to differentiate these candidates. The memorable thing doesn't have to be professional, or even positive (even though it sure won't hurt) — your best bet would be some <em>original</em> personal project.</p>
<h2>Ask for feedback on the spot.</h2>
<p>Asking how you did at the end of the interview doesn't hurt. Yes, some interviewers will be hesitant to answer — at large companies, the feedback is normally sent through the recruiter, and you're never sure if sidestepping this process would get you into trouble. Besides, if the feedback is not complimentary, you're essentially asking for conflict at the spot, and people normally avoid conflict when possible. Still, it's a chance to adjust your expectations (if the interviewer says, looking you in the eyes, that you've done great, it's a good sign), and you <em>might</em> get actually useful tips that would probably get lost passing through the written report, and then through the non-technical recruiter.</p>
Svelte stores: the curious parts2023-04-22T00:00:00Zhttps://thoughtspile.github.io/2023/04/22/svelte-stores/<p>We've already <a href="https://blog.thoughtspile.tech/2023/04/22/svelte-state/">learnt a lot about svelte's reactivity system</a> — the primary way to work with state in svelte components. But not all state belongs in components — sometimes we want app-global state (think state manager), sometimes we just want to reuse logic between components. React has <a href="https://react.dev/reference/react">hooks,</a> Vue has <a href="https://vuejs.org/guide/reusability/composables.html">composables</a>. For svelte, the problem is even harder — reactive state <em>only</em> works inside component files, so the rest is handled by a completely separate mechanism — stores. The <a href="https://svelte.dev/tutorial/writable-stores">tutorial</a> does a decent job of covering the common use cases, but I still had questions:</p>
<ol>
<li>What's the relationship between the stores? Are they built on some common base?</li>
<li>Is it safe to use <code>{ set } = store</code> as a free function?</li>
<li>How does <code>get(store)</code> receive the current value if it's not exposed on the object?</li>
<li>Does <code>set()</code> trigger subscribers when setting the current value?</li>
<li>What's the order of subscriber calls if you <code>set()</code> inside a subscriber?</li>
<li>Does <code>derived</code> listen to the base stores when it's not observed?</li>
<li>Will changing two <code>dervied</code> dependencies trigger one or two derived computations?</li>
<li>Why does <code>subscribe()</code> have a second argument?</li>
<li>What is <code>$store</code> sytax compiled to?</li>
</ol>
<p>In this article, I explore all these questions (and find a few svelte bugs in the process).</p>
<p><img src="https://thoughtspile.github.io/images/svlete-stores.png?invert" alt="" /></p>
<h2>writable is the mother store</h2>
<p>Svelte has 3 built-in store types: <code>writable</code>, <code>readable</code>, and <code>derived</code>. However, they are neatly implemented in terms of one another, <a href="https://github.com/sveltejs/svelte/blob/master/src/runtime/store/index.ts">taking only 236 lines,</a> over half of which is TS types and comments.</p>
<p>The implementation of <code>readable</code> is remarkably simple — it creates a writable, and only returns its subscribe method. Let me show it <a href="https://github.com/sveltejs/svelte/blob/64b8c8b33c52cdb1ae9ee8b0148809237c5cb997/src/runtime/store/index.ts#L60">in its entirety:</a></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">readable</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">value<span class="token punctuation">,</span> start</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">subscribe</span><span class="token operator">:</span> <span class="token function">writable</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> start<span class="token punctuation">)</span><span class="token punctuation">.</span>subscribe<br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Moreover, <code>derived</code> is just <a href="https://github.com/sveltejs/svelte/blob/64b8c8b33c52cdb1ae9ee8b0148809237c5cb997/src/runtime/store/index.ts#L173">a special way</a> of constructing <code>readable</code>:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">derived</span><span class="token punctuation">(</span><span class="token parameter">stores<span class="token punctuation">,</span> fn<span class="token punctuation">,</span> initial_value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// ...some normalization</span><br /> <span class="token keyword">return</span> <span class="token function">readable</span><span class="token punctuation">(</span>initial_value<span class="token punctuation">,</span> <span class="token comment">/* some complex code */</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>While we're at it, note that <code>update</code> method of a writable store is a <a href="https://github.com/sveltejs/svelte/blob/64b8c8b33c52cdb1ae9ee8b0148809237c5cb997/src/runtime/store/index.ts#L94">very thin wrapper</a> over <code>set</code>: <code>fn => set(fn(value))</code>.</p>
<p>All in all:</p>
<ul>
<li><code>writable</code> is the OG store,</li>
<li><code>readable</code> just removes <code>set</code> & <code>update</code> methods from a writable,</li>
<li><code>derived</code> is just a predefined <code>readable</code> setup,</li>
<li><code>update</code> is just a wrapper over <code>set</code>.</li>
</ul>
<p>This greatly simplifies our analysis — we can just investigate <code>writable</code> arguments, <code>subscribe</code>, and <code>set</code> — and our findings also hold for other store types. Well done, svelte!</p>
<h2>Store methods don't rely on <code>this</code></h2>
<p>Writable (and, by extension, readable and derived) is implemented with objects and closures, and does not rely on <code>this</code>, so you can safely pass free methods around without dancing with <code>bind</code>:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">{</span> subscribe<span class="token punctuation">,</span> set <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> toggle <span class="token operator">=</span> <span class="token punctuation">{</span> subscribe<span class="token punctuation">,</span> <span class="token function-variable function">activate</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">set</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>However, arbitrary custom stores are not guaranteed to have this trait, so it's best to stay safe working with an unknown store-shaped argument — like <a href="https://github.com/sveltejs/svelte/blob/64b8c8b33c52cdb1ae9ee8b0148809237c5cb997/src/runtime/store/index.ts#L226">svelte itself</a> does with <code>readonly</code>:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">readonly</span><span class="token punctuation">(</span><span class="token parameter">store</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">subscribe</span><span class="token operator">:</span> store<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span>store<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h2>Subscriber is invoked immediately</h2>
<p>As svelte stores implement observable value pattern, you'd expect them to have a way to access current value via <code>store.get()</code> or <code>store.value</code> — but it's not there! Instead, you use the special <code>get()</code> helper function:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> get <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/store'</span><br /><span class="token keyword">const</span> value <span class="token operator">=</span> <span class="token function">get</span><span class="token punctuation">(</span>store<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>But, if the store does not expose a value, how can <code>get(store)</code> synchronously access it? Normally, the subscribers are only called on change, which can occur whenever. Well, svelte <code>subscribe</code> is not your average subscribe — calling <code>subscribe(fn)</code> not only starts listening to changes, but also synchronously calls <code>fn</code> with the current value. <code>get</code> subscribes to the store, extracts the value from this immediate invocation, and immediately unsubscribes — <a href="https://github.com/sveltejs/svelte/blob/64b8c8b33c52cdb1ae9ee8b0148809237c5cb997/src/runtime/internal/utils.ts#L77">like this:</a></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> value<span class="token punctuation">;</span><br /><span class="token keyword">const</span> unsub <span class="token operator">=</span> store<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> value <span class="token operator">=</span> v<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">unsub</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The official svelte tutorial <a href="https://svelte.dev/tutorial/custom-stores">section on custom stores</a> says: <em>as long as an object correctly implements the subscribe method, it's a store.</em> This might bait you into writing "custom stores" with <code>subscribe</code> method, not based off of <code>writable</code>. The trick word here is <em>correctly implements</em> — even based on the tricky <code>subscribe</code> self-invocation it's not an easy feat, so please stick to manipulations with readable / writable / derived.</p>
<h2>set() is pure for primitives</h2>
<p><code>writable</code> stores are pure in the same sense as svelte state — <a href="https://thoughtspile.github.io/2023/04/22/svelte-stores/">notifications are skipped</a> when state is primitive, and the next value is equal to the current one:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> s <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span><span class="token number">9</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// logs 9 because immediate self-invocation</span><br />s<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span>console<span class="token punctuation">.</span>log<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// does not log</span><br />s<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token number">9</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Object state <a href="https://svelte.dev/repl/eb1a787c9bce4ae8b88f85934d7ec37d?version=3.59.1">disables this optimization</a> — you can pass a shallow equal object, or the same (by reference) object, the subscribers will be called in any case:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> s <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token number">9</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />s<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span>console<span class="token punctuation">.</span>log<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// each one logs</span><br />s<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token parameter">s</span> <span class="token operator">=></span> s<span class="token punctuation">)</span><span class="token punctuation">;</span><br />s<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token function">get</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />s<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token number">9</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>On the bright side, you can mutate the state in update, and it works:</p>
<pre class="language-js"><code class="language-js">s<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token parameter">s</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> s<span class="token punctuation">.</span>value <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> s<br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h2>Subscriber consistency</h2>
<p>Normally, <code>store.set(value)</code> synchronously calls all subscribers with <code>value</code>. However, a naive implementation will shoot you in the foot when updating a store from within a subscriber (if you think it's a wild corner case — it's not, it's how <a href="https://github.com/sveltejs/svelte/blob/64b8c8b33c52cdb1ae9ee8b0148809237c5cb997/src/runtime/store/index.ts#L187">derived stores work</a>):</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> currentValue <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> store <span class="token operator">=</span> <span class="token function">naiveWritable</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />store<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// let's try to avoid 0</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>v <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> store<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><br />store<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> currentValue <span class="token operator">=</span> v<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>If we now call <code>set(0)</code>, we intuitively expect both the store's internal value and currentValue to be <code>1</code> after all callbacks settle. But in practice it can fail:</p>
<ol>
<li>Store value becomes 0;</li>
<li>First subscriber sees 0, calls <code>set(1)</code>, then:
<ol>
<li>Store value becomes 1;</li>
<li><code>set(1)</code> synchronously invokes all subscribers with 1;</li>
<li>First subscriber sees 1, does nothing;</li>
<li>Second subscriber is called with 1, sets <code>currentValue</code> to 1;</li>
<li>First subscriber run for 0 is completed, continuing with the initial updates triggered by <code>set(0)</code></li>
</ol>
</li>
<li>Second subscriber is called with 0, setting <code>currentValue</code> to 0;</li>
<li>Bang, inconsistent state!</li>
</ol>
<p>This is very dangerous territory — you're bound to either skip some values, get out-of-order updates, or have subscribers called with different values. Rich Harris has <a href="https://github.com/sveltejs/svelte/commit/a2ff93cb721b786f34e467b9bddfbf6eebcfde43">taken a lot of effort</a> to provide the following guarantees, regardless of where you <code>set</code> the value:</p>
<ol>
<li>Every subscriber always runs for every <code>set()</code> call (corrected for primitive purity).</li>
<li>Subscribers for one <code>set()</code> run, uninterrupted, after one another (in insertion order, but I wouldn't rely on this too much).</li>
<li>Subscribers are invoked <em>globally</em> (across all svelte stores) in the same order as <code>set</code> calls, even when set calls are nested (called from within a subscriber).</li>
<li>All subscribers are called synchronously within the outermost <code>set</code> call (the one outside any subscriber).</li>
</ol>
<p>So, in our example, the <a href="https://svelte.dev/repl/71f93e80c07c46f896a0ee44260b5ff1?version=3.59.1">actual callback order is:</a></p>
<ol>
<li>subscriber 1 sees 0, calls <code>set(1)</code></li>
<li>subscribers calls with <code>1</code> are enqueued</li>
<li>subscriber 2 sets <code>currentValue = 0</code></li>
<li>subscriber 1 runs with 1, does nothing</li>
<li>subscriber 2 sets <code>currentValue = 1</code></li>
</ol>
<p>Since the callback queue is global, this holds even when updating store B from a subscriber to store A. One more reason to stick with svelte built-in stores instead of rolling your own.</p>
<h2>Derived is lazy</h2>
<p><code>derived</code> looks simple on the surface — I thought it just <code>subscribe</code>s to all the stores passed, and keeps an up-to-date result of the mapper function. In reality, it's smarter than that — <a href="https://github.com/sveltejs/svelte/blob/64b8c8b33c52cdb1ae9ee8b0148809237c5cb997/src/runtime/store/index.ts#L173">subscription and unsubscription happens in the start / stop handler,</a> which yields some nice properties:</p>
<ol>
<li>Subscriptions to base stores are automatically removed once you stop listening to the derived store, no leaks.</li>
<li>Derived value and subscriptions are reused no matter how many times you subscribe to a derived store.</li>
<li>When nobody is actively listening to a derived store, the mapper does not run.</li>
<li>The value is automatically updated when someone first subscribes to the derived store (again, courtesy of subscribe self-invocation).</li>
</ol>
<p>Very, very tastefully done.</p>
<h2>Derived is not transactional</h2>
<p>While lazy, <code>derived</code> is <em>not</em> transactional, and <em>not</em> batched — synchronously changing 2 dependencies will trigger 2 derivations, and 2 subscriber calls — one after the first update, and one after the second one.</p>
<p>In this <a href="https://svelte.dev/repl/5ad19198c21642678e70a283f8bbdfef?version=3.58.0">code sample,</a> we'd expect <code>left + right</code> to always be 200 (we synchronously move 10 from left to right), there's a glimpse of <code>190</code> (remember, the subscribers are synchronously called during <code>set</code>):</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> left <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> right <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> total <span class="token operator">=</span> <span class="token function">derived</span><span class="token punctuation">(</span><span class="token punctuation">[</span>left<span class="token punctuation">,</span> right<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">[</span>x<span class="token punctuation">,</span> y<span class="token punctuation">]</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'derive'</span><span class="token punctuation">,</span> x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> x <span class="token operator">+</span> y<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />total<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token parameter">t</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'total'</span><span class="token punctuation">,</span> t<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> <span class="token function-variable function">update</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// try to preserve total = 200</span><br /> left<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token parameter">l</span> <span class="token operator">=></span> l <span class="token operator">-</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// ^^ derives, and logs "total 190"</span><br /> right<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token parameter">r</span> <span class="token operator">=></span> r <span class="token operator">+</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// ^^ derives, and logs "total 200"</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This isn't a deal breaker, svelte won't render the intermediate state, but it's something to keep in mind, or you <a href="https://svelte.dev/repl/73b0d8843045485897226cabf8efc682?version=3.58.0">get hurt</a>:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> obj <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">me</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">total</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> key <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span><span class="token string">'me'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> value <span class="token operator">=</span> <span class="token function">derived</span><span class="token punctuation">(</span><span class="token punctuation">[</span>obj<span class="token punctuation">,</span> key<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">[</span>obj<span class="token punctuation">,</span> key<span class="token punctuation">]</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> obj<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">.</span>total<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// throws, because { me: ... } has no 'order' field</span><br />key<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'order'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />obj<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">order</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">total</span><span class="token operator">:</span> <span class="token number">100</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h2>The mysteryous subscriber-invalidator</h2>
<p>Looking at <code>subscribe()</code> types, you may've noticed the <a href="https://github.com/sveltejs/svelte/blob/64b8c8b33c52cdb1ae9ee8b0148809237c5cb997/src/runtime/store/index.ts#L98">mysterious second argument — <code>invalidate</code> callback.</a> Unlike the subscriber, it's not queued, and is always called synchronously during <code>set()</code>. The only place I've seen an invalidator used in svelte codebase is <a href="https://github.com/sveltejs/svelte/blob/64b8c8b33c52cdb1ae9ee8b0148809237c5cb997/src/runtime/store/index.ts#L202">inside <code>derived</code></a> — and, TBH, I don't understand its purpose. I expected it to stabilize derived chains, but it's <a href="https://svelte.dev/repl/a7607d2cf8644da7aa4de1d1d2804205?version=3.59.1">not working.</a> Also, the <a href="https://github.com/sveltejs/svelte/blob/64b8c8b33c52cdb1ae9ee8b0148809237c5cb997/src/runtime/store/index.ts#L13">TS types are wrong</a> — the value is <a href="https://github.com/sveltejs/svelte/blob/64b8c8b33c52cdb1ae9ee8b0148809237c5cb997/src/runtime/store/index.ts#L81">never passed</a> to invalidator as an argument. Verdict: avoid.</p>
<h2>$-dereference internals</h2>
<p>As you probably know, svelte components have a special syntax sugar for accessing stores — just prefix the store name with a <code>$</code>, and you can read and even assign it like a regular reactive variable — very convenient:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> <span class="token punctuation">{</span> writable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/store'</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> value <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> $value <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>add<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>$value<span class="token punctuation">}</span><span class="token plain-text"><br /></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span></code></pre>
<p>I always thought that <code>$value</code> is compiled to <code>get</code>, <code>$value = v</code> to <code>value.set(v)</code>, and so on, with a subscriber triggering a re-render in some smart way, but it's not the case. Instead, <code>$value</code> becomes a regular svelte reactive variable, synchronized to the store, and the rest is handled by the standard svelte update mechanism. Here's <a href="https://svelte.dev/repl/73b0d8843045485897226cabf8efc682?version=3.59.1">the compilation result:</a></p>
<pre class="language-js"><code class="language-js"><span class="token comment">// the materialized $-variable</span><br /><span class="token keyword">let</span> $value<span class="token punctuation">;</span><br /><span class="token comment">// the store</span><br /><span class="token keyword">const</span> value <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// auto-subscription</span><br /><span class="token keyword">const</span> unsub <span class="token operator">=</span> value<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> <span class="token parameter">value</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">$$invalidate</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> $value <span class="token operator">=</span> value<span class="token punctuation">)</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">onDestroy</span><span class="token punctuation">(</span>unsub<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// assign to variable</span><br /> $value <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span><br /> <span class="token comment">// update store</span><br /> value<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>$value<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>In plain English:</p>
<ol>
<li><code>$store</code> is a real actual svelte reactive variable.</li>
<li><code>store.subscribe</code> updates the variable and triggers re-render.</li>
<li>The unsubscriber is stored and called <code>onDestroy</code>.</li>
<li>AFAIK, <code>store.update</code> is never used by svelte.</li>
<li>Assignments to <code>$store</code> simultaneously mutate <code>$store</code> variable <em>without</em> invalidating and triggering re-render <em>and</em> call <code>store.set</code>, which in turn enqueues the update via <code>$$invalidate</code></li>
</ol>
<p>The last point puts us in a double-source-of-truth situation: the current store value lives both in the <code>$store</code> reactive variable, and inside store itself. I expected this to cause some havok in an edge case, and so it does — if you patch <code>store.set</code> method to skip <em>some</em> updates, the $-variable updates before your custom <code>set</code> runs, and the two values <a href="https://svelte.dev/repl/50b2e2bb1a224a44ad51540e90978867?version=3.59.1">go out of sync</a> as of svelte@3.59.1:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> value <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span><span class="token function">writable</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token comment">// prevent updates</span><br /> <span class="token function-variable function">set</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> $value <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span><br /><span class="token keyword">let</span> rerender <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token literal-property property">$</span><span class="token operator">:</span> total <span class="token operator">=</span> $value <span class="token operator">+</span> <span class="token punctuation">(</span>rerender <span class="token operator">?</span> <span class="token number">0</span> <span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token punctuation">{</span>total<span class="token punctuation">}</span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>add<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">increment</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> rerender <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> rerender<br /></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span></code></pre>
<hr />
<p>To summarize:</p>
<ol>
<li>Both <code>readable</code> and <code>derived</code> are built on top of <code>writable</code> — readable only picks <code>subscribe</code> method, derived is a readable with a smart start / stop notifier.</li>
<li>Built-in stores don't rely on <code>this</code>, so you can safely use their methods as free functions.</li>
<li>Calling <code>subscribe(fn)</code> immediately invokes <code>fn</code> with the current value — used in <code>get(store)</code> to get the current value.</li>
<li>Calling <code>set()</code> with the current value of the store will skip notifying subscribers if the value is primitive. set() on object state always notifies, even if the object is same, by reference, as the current state.</li>
<li>The subscribers for a single <code>set()</code> run after one another. If a subscriber calls <code>set</code>, this update will be processed once the first <code>set()</code> is fully flushed.</li>
<li><code>derived</code> only subscribes to the base stores and maps the value when someone's actively listening to it.</li>
<li>When synchronously changing two dependencies of <code>derived</code>, the mapper <em>will</em> be called after the first change. There's no way to batch these updates.</li>
<li><code>subscribe()</code> has a second argument — a callback that's called synchronously during <code>set()</code>. I can't imagine a use case for it.</li>
<li><code>$store</code> syntax generates a regular svelte reactive variable called <code>$store</code>, and synchronizes it with the store in a subscriber.</li>
</ol>
<p>If you learn one thing from this article — svelte stores are thoughtfully done and help you with quite a few corner-cases. Please avoid excessive trickery, and build on top of the svelte primitives. In the next part of my svelte series, I'll show you some neat tricks with stores — <a href="https://twitter.com/thoughtspile">stay tuned on twitter!</a></p>
Svelte reactivity — an inside and out guide2023-04-22T00:00:00Zhttps://thoughtspile.github.io/2023/04/22/svelte-state/<p>I've been working with svelte exclusively for a year now, but I still manage to shoot myself in the foot every now and then when using reactive state. Some of the confusion is due to my prior experience with React, but some points are confusing on their own. Today, I dive into svelte internals to understand what's really going on.</p>
<ol>
<li>When you mutate svelte object state, do you really mutate the JS object?</li>
<li>Why doesn't <code>array.push</code> trigger an update?</li>
<li>How are $-dependencies determined?</li>
<li>Does setting a variable to its current value trigger an update?</li>
<li>Does a block using one object field, <code>state.field</code>, only run on changes to this field?</li>
<li>Why do $-variables behave a bit weirdly under JS scoping rules?</li>
</ol>
<p>We won't talk about stores too much, we still have a lot to discuss before we get there. If you're in a hurry, here's a customary cheat-sheet:</p>
<p><img src="https://thoughtspile.github.io/images/svelte-state.png?invert" alt="" /></p>
<p>Let's go!</p>
<h2>Object state is mutable</h2>
<p>One thing that caught me off guard, coming from react, is that object state is mutable — the exact object defined as the initial value will stay there as long as you don't reassign the variable:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// data is always the same object</span><br /><span class="token keyword">let</span> data <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">active</span><span class="token operator">:</span> <span class="token number">0</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">toggle</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> data<span class="token punctuation">.</span>active <span class="token operator">=</span> <span class="token operator">!</span>data<span class="token punctuation">.</span>active<span class="token punctuation">;</span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>toggle<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>data<span class="token punctuation">.</span>active <span class="token operator">?</span> <span class="token string">'on'</span> <span class="token operator">:</span> <span class="token string">'off'</span><span class="token punctuation">}</span><span class="token plain-text"><br /></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span></code></pre>
<p>See <a href="https://svelte.dev/repl/ef02a9c7eb26402da23949ac7740f3f4?version=3.58.0">repl.</a> By the way, this behavior also <a href="https://svelte.dev/repl/cef41b6a93494468824a2ce53cb5338e?version=3.58.0">applies to stores.</a> This is in contrast to react, where most optimizations rely on "purity" — a thing is only considered changed if its reference changes.</p>
<p>So, svelte state is not an <em>observable object</em> built by JS means, but a regular JS variable with an <em>update event bus</em> attached separately. So far so good — the svelte way is close to how JS works. But what actually triggers the update event?</p>
<h2>Updates are triggered by assignment operator</h2>
<p>The big selling point of svelte is its concise syntax. As a <a href="https://svelte.dev/repl/758ed96027a74bd68a8e8347ed799401?version=3.58.0">showcase example,</a> you often see what looks like simple JS, but changing the variable magically updates the component:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> isOn <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> isOn <span class="token operator">=</span> <span class="token operator">!</span>isOn<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>isOn <span class="token operator">?</span> <span class="token string">'on'</span> <span class="token operator">:</span> <span class="token string">'off'</span><span class="token punctuation">}</span><span class="token plain-text"><br /></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span></code></pre>
<p>The magic disappears once you get to mutable objects — calling <code>array.push()</code> or any other mutating method <a href="https://svelte.dev/repl/1b7494f2e05b490fad957931cbd3d429?version=3.58.0">does not trigger an update:</a></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> items <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">function</span> <span class="token function">addItem</span><span class="token punctuation">(</span><span class="token parameter">text</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> items<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token punctuation">{</span>items<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">' '</span><span class="token punctuation">)</span><span class="token punctuation">}</span><br /><span class="token operator"><</span>button on<span class="token operator">:</span>click<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">addItem</span><span class="token punctuation">(</span><span class="token string">'hehe'</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token operator">></span><br /> more<span class="token operator">!</span><br /><span class="token operator"><</span><span class="token operator">/</span>button<span class="token operator">></span></code></pre>
<p>This seems very familiar to react developers — of course, mutating the object does not change its reference, so further updates are skipped! To add to the confusion, react-style immutable update like <code>items = [...items, text]</code> <a href="https://svelte.dev/repl/3c710b40fec742dc88f22bb7d351459e?version=3.58.0">fixes</a> the problem. However, svelte state is mutable, and the real issue is the lack of an assignment operator which <em>actually</em> triggers the update, so the weird self-assignment pattern <a href="https://svelte.dev/repl/4f4272b255f9475d9bb138e4b013acc2?version=3.58.0">works just as well:</a></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> items <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">function</span> <span class="token function">addItem</span><span class="token punctuation">(</span><span class="token parameter">text</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> items<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// #enable_magic</span><br /> items <span class="token operator">=</span> items<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Any flavor of assignment operator (<code>+=</code>, <code>++</code>, and so on) works, but it must be present for the update to happen. Another thing to note is that update logic is dynamic, not a strict "if callback X runs, fire listeners to variable Y" rule. The assignments are compiled to a call like <code>$$invalidate(0, items)</code>, so conditional assignment only triggers updates if it's executed:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// only triggers an update if text is truthy</span><br /><span class="token keyword">function</span> <span class="token function">addItem</span><span class="token punctuation">(</span><span class="token parameter">text</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>text<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> items <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span>items<span class="token punctuation">,</span> text<span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><span class="token comment">// always triggers an update</span><br /><span class="token keyword">function</span> <span class="token function">addItemAlways</span><span class="token punctuation">(</span><span class="token parameter">text</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> items <span class="token operator">=</span> text <span class="token operator">?</span> <span class="token punctuation">[</span><span class="token operator">...</span>items<span class="token punctuation">,</span> text<span class="token punctuation">]</span> <span class="token operator">:</span> items<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h2>Dependencies are determined statically</h2>
<p>Unlike invalidation, which happens dynamically at runtime, $-blocks dependencies are determined statically at compile-time. If you have some logic that <em>only</em> depends on <code>text</code> <em>if</em> <code>isTracking</code> is true...</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> isTracking <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /><span class="token keyword">let</span> text <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span><br /><span class="token literal-property property">$</span><span class="token operator">:</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>isTracking<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>...the block runs on every <code>text</code> change regardless of <code>isTracking</code> value, because both referenced variables, <code>text</code> and <code>isTracking</code>, are recognized as the block dependencies. This is in contrast to runtime reactivity, as seen in MobX — there's no way to even see the dependency on <code>text</code> if the code accessing it does not run.</p>
<p>One <em>inconvenient</em> side-effect of this dependency detection mechanism is the lack of visibility into implicit function dependencies. Say you have this code:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> items <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'hello'</span><span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token string">'friend'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">getNonempty</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> items<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">x</span> <span class="token operator">=></span> <span class="token operator">!</span><span class="token operator">!</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token literal-property property">$</span><span class="token operator">:</span> nonempty <span class="token operator">=</span> <span class="token function">getNonempty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Since the $-expression defining <code>nonempty</code> does not explicitly mention <code>items</code>, it <a href="https://svelte.dev/repl/9ce86a7fc0de420d8886f509091d2620?version=3.58.0"><em>will not</em> run</a> on <code>items</code> change. Fix: avoid functions in $-blocks, or make sure they're pure (only depend on their arguments), or (worst case) make the function itself derived: <code>$: getNonempty = ...</code></p>
<h2>Primitive updates are pure</h2>
<p>Based on the points above, you'd assume that assigning to the variable <em>always</em> triggers an update, but that's not the case. When repeatedly setting a variable to <code>true</code>, only the first change <a href="https://svelte.dev/repl/5ad19198c21642678e70a283f8bbdfef?version=3.58.0">results in an update:</a></p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> isEnabled <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /><span class="token comment">// only run on the first click</span><br /><span class="token function">afterUpdate</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'updated'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token literal-property property">$</span><span class="token operator">:</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token punctuation">{</span> isEnabled <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> isEnabled <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>isEnabled <span class="token operator">?</span> <span class="token string">'clicked'</span> <span class="token operator">:</span> <span class="token string">'click me'</span><span class="token punctuation">}</span><span class="token plain-text"><br /></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span></code></pre>
<p>In general, setting a primitive variable to its current value does not trigger an update. Here's the <a href="https://svelte.dev/repl/5ad19198c21642678e70a283f8bbdfef?version=3.58.0">svelte code</a> that implements this optimization.</p>
<h2>Object updates are <em>not</em> pure</h2>
<p>But in that case, how on earth can this support object mutation and self-assignment that don't change the object reference? Well, the trick is simple — it doesn't. Wrapping <code>isEnabled</code> with an object removes the optimization, and now the update <a href="https://svelte.dev/repl/06962949791c4e1897bbb41714d71385?version=3.58.0">happens on every click:</a></p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> state <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">isEnabled</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token comment">// runs on every click</span><br /><span class="token function">afterUpdate</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'updated'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token literal-property property">$</span><span class="token operator">:</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>state<span class="token punctuation">.</span>isEnabled<span class="token punctuation">)</span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> state<span class="token punctuation">.</span>isEnabled <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>state<span class="token punctuation">.</span>isEnabled <span class="token operator">?</span> <span class="token string">'clicked'</span> <span class="token operator">:</span> <span class="token string">'click me'</span><span class="token punctuation">}</span><span class="token plain-text"><br /></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span></code></pre>
<p>The <a href="https://github.com/sveltejs/svelte/blob/6ba2f722518b3fb6904d6d566c3c1a00d61fe70a/src/runtime/internal/utils.ts#L41">code implementing the comparison</a> always says "it's changed" when it sees an object.</p>
<h2>Reactivity is variable-grained</h2>
<p>As we've just seen, object state is always treated as a whole — there is no <em>field-level reactivity.</em> If you have an $-block that only depends on a single field of the object, it <a href="https://svelte.dev/repl/689900fc527b41a79ed9e9efba9c93ed?version=3.58.0">will run on <em>any</em> change to the object:</a></p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> fields <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">clicks</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">''</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token comment">// runs on every "clicks" change</span><br /><span class="token literal-property property">$</span><span class="token operator">:</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>fields<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> fields<span class="token punctuation">.</span>clicks<span class="token operator">++</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> clicks: </span><span class="token punctuation">{</span>fields<span class="token punctuation">.</span>clicks<span class="token punctuation">}</span><span class="token plain-text"><br /></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name"><span class="token namespace">bind:</span>value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>fields<span class="token punctuation">.</span>name<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span></code></pre>
<p>If we only want the $-block to run on actual <code>name</code> change, we can achieve that by extracting <code>name</code> to a separate reactive variable. This variable will have its own "change event", independent of other object fields. In our example it's better to just define the fields as separate variables from the start, but in general this can be done <a href="https://svelte.dev/repl/a69f93746691429f8581cb16510a59ed?version=3.58.0">using a <em>$-variable:</em></a></p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> fields <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">clicks</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">''</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token comment">// uses primitive comparison under the hood:</span><br /><span class="token literal-property property">$</span><span class="token operator">:</span> name <span class="token operator">=</span> fields<span class="token punctuation">.</span>name<br /><span class="token comment">// runs on "name" change only</span><br /><span class="token literal-property property">$</span><span class="token operator">:</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> fields<span class="token punctuation">.</span>clicks<span class="token operator">++</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> clicks: </span><span class="token punctuation">{</span>fields<span class="token punctuation">.</span>clicks<span class="token punctuation">}</span><span class="token plain-text"><br /></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name"><span class="token namespace">bind:</span>value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>fields<span class="token punctuation">.</span>name<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span></code></pre>
<p>Note that object-to-object mapping does not optimize anything and just <a href="https://svelte.dev/repl/515680f84f8642108f8678a9f1bbfe29?version=3.58.0">runs on every dependency change.</a> This is because the "pure" optimization is only applied when all dependencies are primitive (computation is not triggered), or the output is primitive (further dependencies are not triggered).</p>
<h2>$-variables are an illusion</h2>
<p>If you've spent some time moving $-variables around, you know they behave weirdly from JS scoping perspective:</p>
<ol>
<li>You can reference the "scope" variables (declared with let / const) declared <em>after</em> the $-expression (in normal JS, this is TDZ)</li>
<li>You can access $-variable anywhere in the script, but the value outside $-blocks is <code>undefined</code></li>
</ol>
<p>Here's an example to illustrate this behavior:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// you can reference variables before they're declared</span><br /><span class="token literal-property property">$</span><span class="token operator">:</span> name <span class="token operator">=</span> state<span class="token punctuation">.</span>name<span class="token punctuation">;</span><br /><span class="token keyword">let</span> state <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">''</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token comment">// logs "undefined"</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The trick is, again, simple — $-variables are just sugar over a simple let declaration and an $-block that assigns to the variable. The variable declaration is hoisted to the top (which is funny, because this perfectly emulates a <code>var</code> declaration), and $-blocks become callbacks defined <em>after</em> the synchronous initialization code. The un-sugared equivalent of the code above will be:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// hoisted let declaration</span><br /><span class="token keyword">let</span> name<span class="token punctuation">;</span><br /><span class="token comment">// init block</span><br /><span class="token keyword">let</span> state <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">''</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// $-blocks</span><br /><span class="token literal-property property">$</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> name <span class="token operator">=</span> state<span class="token punctuation">.</span>name<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>If you want to go one level down, the output in both cases is:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> name<span class="token punctuation">;</span><br /><span class="token keyword">let</span> state <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">''</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br />$$self<span class="token punctuation">.</span>$$<span class="token punctuation">.</span><span class="token function-variable function">update</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>$$self<span class="token punctuation">.</span>$$<span class="token punctuation">.</span>dirty <span class="token operator">&</span> <span class="token comment">/*state*/</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// you can reference variables before they're declared</span><br /> <span class="token literal-property property">$</span><span class="token operator">:</span> name <span class="token operator">=</span> state<span class="token punctuation">.</span>name<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>One funny side effect of this is that you can <em>assign</em> to the $-variable from e.g. another $-block, or a callback. The variable is not strongly bound to its definition, the last assignment wins. This would be quite confusing, though, and I recommend against such trickery.</p>
<hr />
<p>To summarize, here's the result of my research:</p>
<ol>
<li>Svelte object state is truly mutable. "Reactive variables" are just regular JS variables with change event bus attached as a sidecar.</li>
<li>Updates are only triggered by assigning to reactive variables — assignment is compiled to <code>$$invalidate(var_index, value)</code> call.</li>
<li>Reactive block dependencies are determined at compile-time based on variables used inside the block.</li>
<li>"Pure" optimization only applies to primitive values.</li>
<li>Variable is the smallest unit of reactivity — changing a single object field triggers all the blocks using this object.</li>
<li>$-variables are compiled to a hoisted let declaration and a callback called after the setup code.</li>
</ol>
<p>Hope you've learnt something useful today — I sure have. Next time, we'll dissect svelte stores — <a href="https://twitter.com/thoughtspile">follow me on twitter</a> to stay updated!</p>
How I made banditypes, the smallest TS validation library2023-03-02T00:00:00Zhttps://thoughtspile.github.io/2023/03/02/banditypes-smallest-validation/<p>I open-sourced <a href="https://github.com/thoughtspile/banditypes">banditypes</a> — the smallest runtime validation library for TS / JS. It manages to fit all the basic functionality into an astounding 400 bytes. For reference, the popular <a href="https://zod.dev/">zod</a> and <a href="https://github.com/jquense/yup">yup</a> libraries are around 11KB, <a href="https://github.com/ianstormtaylor/superstruct">superstruct</a> measures 1.8KB for the same set of functionality.</p>
<p><img src="https://thoughtspile.github.io/images/banditypes-sample.png" alt="" /></p>
<p>Today, I'll tell you how I managed to pull this off. The article is divided into three parts. First, we discuss the downsides of traditional bundle size measurement techniques, and come up with a more realistic way to assess it. Then I explain the design process focused on minimizing bundle size — with this alone, the size clocked around 500 bytes. Finally, I share the extra optimizations and dirty hacks that allowed me to strip off another 20% and arrive at the current 385-byte size.</p>
<p><em>This article originally appeared <a href="https://habr.com/ru/company/ruvds/blog/719708/">in Russian</a> on habr.com</em></p>
<h2>Size measurement technique</h2>
<p>When people say that "library X is 40 kilobytes", they usually mean that the full build (usually UMD) of the library, minified and gzipped, is 40 kilobytes. This was perfect back in 2008, when you dropped the jQuery script from an obscure CDN onto your website; it was bearable in 2015 when <code>require</code> instead of copy-pasting code was enough to make us happy. But in 2023, with static imports end ES modules and stuff, this is just not a realistic measure (albeit a simple one to measure).</p>
<p>First off, we have tree shaking, and it's pretty good if you don't stand in its way. If a library is a collection of 200 utilities, of which I only use one, I don't really care about the total size of all helpers, as long as only one makes it into my bundle. Judging libraries by full size not only punishes feature-rich libraries (you're not strong, you're FAT, ha-ha), but also fails to assess the tree-shakability of the library. Besides, the standalone library bundle includes full exported names, like <code>export enums</code> or, while in real life the names will be <a href="https://terser.org/docs/api-reference#mangle-options">mangled</a> or even completely eliminated by inlining the functions into the call site.</p>
<p>Tools like <a href="https://bundlejs.com/">bundlejs.com</a> and <a href="https://github.com/ai/size-limit">size-limit</a> bundle a small sample app importing from your library, taking care of these problems. But classical single-pass analysis still contains some <em>artifacts</em> that skew the measurement, and for <em>tiny</em> libraries this interference can make the majority of the reported bundle size:</p>
<ul>
<li>Repetitions of common JS syntax — <code>const</code>, <code>function(</code>, <code>for (let</code> etc. Every client app most likely contains these already, so, thanks to gzip, a library gets them almost for free.</li>
<li>Wrappers and runtime generated by the bundler — it can be as small as an IIFE wrapper, but it's still there.</li>
<li>22 bytes of gzip service data called "End Of Central Directory record". Again, as long as your library is bundled together with the user code, this is not added to the bundle a second time.</li>
</ul>
<p>To address all of these issues, I build two versions of a small sample app. The first one, baseline, contains just a small chunk of JS relevant to the use case — an object we're validating, and some try / catch blocks. The second one adds banditype validation on top of that. The size difference of the bundles generated is the <em>real</em> size cost added by banditypes — not accounting for stuff that's already there (it's 218 bytes BTW), <em>but</em> including the user-defined schema. This last point is very important – otherwise, you can achieve an illusion of a smaller library by making the <em>user</em> type more code. Mental experiment: "I published a hot new 0-byte UI framework! Install it with npm i zerro and build UI using vanilla DOM API manually".</p>
<p>This approach also lets me measure the size with different subsets of functionality and assess the quality of tree shaking. 385 bytes is the size of a full build, while the common core (object, array, and primitives) sits at 207 bytes, and the "core" with no validations is 96 bytes. Different minifiers can be plugged in to cover the variety of real-world use cases: I report the 5-pass terser size as it's the most established minifier that you <em>should</em> use if bundle size is a concern, but esduild minifier is not far behind at 405 bytes.</p>
<h2>Design for smolness</h2>
<p>If you start the development by randomly slapping the keyboard until you let out all the functionality you could think of, and <em>then</em> try to also make it small, you're probably up for an unpleasant failure. Instead, small size should be at the core of your design decisions from the outset:</p>
<ol>
<li>Design for tree-shaking. Why is <a href="https://zod.dev/">zod</a> around 11KB no matter how few of its functions you use, while <a href="https://github.com/ianstormtaylor/superstruct">superstruct</a> achieves a very respectable 1.8KB size for a real-world use case, with the same feature set? Because zod heavily relies on methods: <code>z.number().gt(5)</code>, while superstruct uses functional composition: <code>min(number(), 9000)</code>. The minifier can take a look at the bundle, see that function <code>max</code> is not referenced, and remove it. But finding all the places where zod's number validator is used, analyze its methods and drop the unused ones is nearly impossible. Besides, function names are much easier and safer to mangle. So, we should rely on functions as much as possible.</li>
<li>Focused feature set. A major feature of decent validation libraries is returning detailed error messaged describing what, exactly, did not pass the validation: <code>{ message: 'Expected string, got number', path: ['item', 'title'] }</code>. I'm sure this is a useful feature, but I also know it's not needed in <em>every</em> use case, so I decided to remove this functionality. Besides, fewer features mean quicker development.</li>
<li>More extensibility. To cut functionality <em>without</em> putting the users and ourselves into an unpleasant position when some critical stuff is missing, we need to let the users extend the library. I added two methods (yes, methods, more on that in a minute) to every validator:</li>
</ol>
<ul>
<li><code>type1.map(res => ...)</code> — perform extra validation aka type refinement: <code>string().map(str => str.length ? str : fail())</code> or transform the data: <code>string().map(str => [str])</code></li>
<li><code>type1.or(val => ...)</code> — If the left validation fails, try the right one. It's obviously useful for union types: <code>string().or(optional()))</code>, but also works for default values: <code>string().or(() => 'default')</code></li>
</ul>
<ol start="4">
<li>While we're at it, it's really great if we can make <em>one</em> thing perform several roles. <code>map</code> can both refine and transform the data, <code>or</code> works as a union or a default value.</li>
</ol>
<p>I think the resulting API is quite beautiful — and compatible with the established libraries to facilitate migration:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> userSchema <span class="token operator">=</span> <span class="token function">object</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> title<span class="token operator">:</span> <span class="token function">string</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token comment">// refinement</span><br /> price<span class="token operator">:</span> <span class="token function">number</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>num <span class="token operator">=></span> num <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">?</span> num <span class="token operator">:</span> <span class="token function">fail</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> tags<span class="token operator">:</span> <span class="token function">set</span><span class="token punctuation">(</span><span class="token function">enums</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">'sale'</span><span class="token punctuation">,</span> <span class="token string">'liked'</span><span class="token punctuation">,</span> <span class="token string">'sold'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// string OR a string array</span><br /><span class="token keyword">const</span> strings <span class="token operator">=</span> <span class="token function">string</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">or</span><span class="token punctuation">(</span><span class="token function">array</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// data conversion</span><br /><span class="token keyword">const</span> arraySum <span class="token operator">=</span> <span class="token function">array</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>arr <span class="token operator">=></span> arr<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>acc<span class="token punctuation">,</span> e<span class="token punctuation">)</span> <span class="token operator">=></span> acc <span class="token operator">+</span> e<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h2>Optimizations</h2>
<p>Smolness as a design principle already gives us a very neat 520-byte size. But I did not just want a small library, I wanted the smallest library possible. So, it's time to try like hell and push for sub-400 without sacrificing too much DX.</p>
<ol>
<li>Compile to modern JS. Replacing <code>function array(raw) ...</code> with <code>const array = (raw) => ...</code>, and using raw spreads, surprisingly, saved 23 bytes. gzip is pretty good at removing repetition (the uncompressed bundle was reduced by 430 bytes), but there still is something to be gained. ES2017 is reasonably well supported by browsers, and you can transpile the library further down yourself.</li>
<li>Remove duplicate APIs. Why have a <code>literal(42)</code> type if you can model it like a single-value union, <code>enums([42])</code>? It's not that single literal is a common type to validate, anyways.</li>
<li>Repetition is power. Gzip is great at removing repetition. If you have arrow functions, turn <em>all</em> functions into arrow functions, so that there is more repetition. If you already use a word <code>Object</code>, you can use it again, almost for free. If <em>every</em> function accepts a single argument, it makes for a nice <code>=(e)=>{</code> repeating chunk (terser makes all argument names the same). Surprisingly, having a function copy-pasted with a minor change is smaller, under gzip, than reusing a "proper" parametrized base function.</li>
<li><code>typeof x === 'number'</code> can be replaced with instance-based check: <code>like = sample => raw => typeof raw == typeof sample</code>, and then you define <code>number</code> type as <code>like(0)</code>. 20 bytes!</li>
<li><code>throw new TypeError('Invalid Banditype')</code> is quite a big chunk of code. We can just call a string: <code>'bad banditype'()</code>, and the error will be thrown anyways. 20 bytes!</li>
</ol>
<p>Naturally, I tried out a ton of ideas that didn't work. Replacing <code>throw</code> with <code>return null</code> did slim down the size a bit, <em>but</em> forced the code to rely on optional chaining, which is not well-supported, and validating container types became much harder, because the errors don't magically bubble up anymore, not to mention the problems with validating real <code>null</code> values. I also expected to gain something by replacing <code>for..in</code> with <code>Object.keys</code>, because it's an expression, and expressions minify better, but it didn't work at all.</p>
<p>Finally, I left <code>map / or</code> as methods, even though I could strip 17 bytes by modeling them as pure functions. They are <em>not</em> likely to be tree-shaken completely (this would trigger a 50-byte reduction), because built-in <code>object</code> and <code>array</code> validations use <code>map</code> internally, and <code>or</code> is used for optional and nullable types. Besides, I much prefer the readability of the chained version: <code>string().map(s => [s]).or(array(string()))</code> reads like a normal sentence, while <code>or(map(string(), s => [s]), array(string))</code> is some weird mashup of words.</p>
<hr />
<p>Today we took a first look at a new validation library, <a href="https://github.com/thoughtspile/banditypes">banditypes.</a> I managed to fit some useful, non-trivial functionality into a measly 400 bytes by treating small size as a core design principle:</p>
<ul>
<li>Prefer functions over methods, because they minify better.</li>
<li>Cut features aggressively. Extra validations and detailed error messages are not useful in <em>every</em> scenario, so they had to go.</li>
<li>Make the library extensible to compensate for the limited feature set.</li>
<li>Be resourceful, and provide multi-purpose tools.</li>
</ul>
<p>And applying some more optimizations on top of that:</p>
<ul>
<li>Modern JS = less code.</li>
<li>Remove duplicate APIs. One way to do something is enough.</li>
<li>Repetitive code is good code, as far as gzip is concerned.</li>
<li><code>typeof raw === typeof sample</code> trick</li>
<li><code>'bad banditype'()</code> hack instead of an explicit throw</li>
</ul>
<p>I think banditypes really shines in some use cases — try it out in your apps! I'd also really appreciate you <a href="https://github.com/thoughtspile/banditypes">starring the repo</a> on github — it means a lot!</p>
Ditch google analytics now: 7 open-source alternatives2023-02-12T00:00:00Zhttps://thoughtspile.github.io/2023/02/12/open-source-analytics/<p>I love writing, and I also love <em>data.</em> When starting my blog, I integrated Google Analytics — it's free, easy to set up (just drop a few tags on the page), and that's what I knew back then. I did not <em>enjoy</em> it being run by a big corporation, but I was too lazy to research the alternatives, and then pay for them. But when Google announced <a href="https://support.google.com/analytics/answer/11583528">sunsetting the current generation of analytics,</a> and I had to take some action at any rate, I decided to pull the trigger and make a jump to an open-source alternative.</p>
<p>My analytics needs are quite simple, really:</p>
<ul>
<li>See how many people read my articles, along with basic stats like session depth and duraiton, because this makes me feel good.</li>
<li>Filter by source to see which places work better for sharing my content.</li>
<li>Custom events, so that I see how design changes help me grow an audience (<em>and</em> this works as a cheap JS error tracker).</li>
<li>Data export would be nice — sometimes I play around with it in pandas.</li>
</ul>
<p>In this article, I give compelling reasons to switch to self-hosted analytics. I share my research on seven open-source analytics tools so that you don't have to do it yourself. Then, I provide a quick tutorial to get up and running with <a href="https://plausible.io/">plausible.</a> Whether you're still on google analytics (or a comparable corporate tool like <a href="https://www.facebook.com/business/tools/meta-pixel">Meta pixel</a> or <a href="https://metrica.yandex.com/about?">Yandex metrica</a>), or just considering adding analytics to your website, this will get you up and running in no time.</p>
<h2>Why self-host your web analytics</h2>
<p>I know that paying (either by subscribing to a paid hosted version or maintaining your own infrastructure) for something you can get for free needs justification. And in case on web analytics, you do get decent benefits for your buck:</p>
<ul>
<li>Privacy — we all know google eats user data for breakfast, lunch and dinner. You "pay" for google analytics with your users' data. The tools we're discussing do <em>not</em> come from companies that run the internet, and don't have the same data collection motivation, so you do protect your user privacy by switching. You <em>might</em> argue "I like programming" is not the most private information, so let's move on to the less <em>moral</em> and more <em>practical</em> arguments.</li>
<li>Better data. Google analytics are blocked by most adblock tools, leaving you with incomplete data — <a href="https://towardsdatascience.com/how-much-data-is-missing-from-your-google-analytics-dashboard-20506b26e6d">some research</a> suggesting figures around 43%. Switching to a less intrusive alternative gives you back the missing data, and more data means better insights with less effort.</li>
<li>Fuck big tech. My blog was basically excluded from google search in october 2022 for no reason, and I want revenge. Depriving google of my users' data is like a microbe bite, but it does feel good.</li>
</ul>
<p>While not directly related to self-hosting or open source, most of the tools we discuss have extra desirable treats:</p>
<ul>
<li>No cookies, so I don't have to worry about GDPR banners or whatever (even though <a href="https://github.com/plausible/analytics/discussions/1963">some disagree</a>). To be fair, I'm not 100% sure you absolutely need a banner whenever you have any cookie, but I'm not a lawyer and no cookies sounds like no problems to me.</li>
<li>Simpler interface. This <em>does</em> sound like a made-up justification for limited functionality, but I honestly see this as a win for a casual analytics user like myself.</li>
<li>Smaller JS footprint = better website performance. Google Analytics is famously around 50KB / 21KB gzip of JS, while most tools we discuss have scripts under 2KB. It's not a <em>huge</em> deal, as a tracking script does not block anything and is cached, but a pleasant bonus. In theory, this lets you catch a few events that occurr soon after the first page load.</li>
</ul>
<p>Now, is self-hosting the anallytics worth the trouble of setting up and maintaing the infrastructure? I think the answer is yes, and here's why:</p>
<ul>
<li>Yet more privacy. When sending your user data to <em>any</em> third party, you're counting on their good will. If your data goes to your own server, it just stays there. If you're extra paranoid, you can ban outgoing traffic from the analytics container, or implement any other security measures.</li>
<li>Yet better data collection. Some privacy protection tools do restrict connections to well-known analytics hosts. Sending data to your own domain makes blocking your tracker much harder, because a domain blacklist won't cut it any more.</li>
<li>Price. As we'll see, you can self-host analytics for around 6$ a month, which is cheaper than the paid analytics services I've seen, and this price will <em>not</em> auto-escalate if you suddenly have a traffic spike.</li>
<li>Data ownership. Your historic data lives on your server, and you're free to export it regardless of any decisions the maintainers of the tools make. Not <em>every</em> tool on our list has easy export, but nothing stops you from a DB dump in the worst case.</li>
<li>No vendor lock-in. With your data available, you can (in theory) migrate to any other analytics service if you change your mind. Again, not all the tools can <em>import</em> data, but some can.</li>
<li>Future-proof. The company developing your tool of choice goes out of business, switches to closed-source, or makes a big update that requires you to reconfigure everyting from scratch? No worries, you can just stay on your current version as long as you please.</li>
</ul>
<p>This should be enough to convince even the most skeptical readers that self-hosted analytics instance is worth it. Now, what tools are available for the job, and which one should you pick?</p>
<h2>Open-source web analytics tools, an overview</h2>
<p>When it comes to open-source analytics tools, we have three top-tier choices: matomo, plausible and umami.</p>
<p><a href="https://matomo.org/">Matomo</a> is the most established tool in the game (been doing it since 2011). I have personally seen it used by huge compaines, and people were pleased with it (it was called piwik back then). It's the closest alternative to google analytics in terms of features — you get customizable dashboards, integrations (ecommerce, website builders, multiple search providers), alerts, plugins, exports and what not. It even has iOS / android SDKs. But the truth is, I don't need all these extra features, I don't want to pay for it (in terms of a larger tracking script and more complex dashboards) and I'd be perfectly happy with something simpler, which brings us to...</p>
<p><a href="https://plausible.io/">Plausible</a>, in contrast, offers a stripped-down analytics experience with only the essential metrics — page views, bounce. duration, source, device, location by default. It's cookie-free and features a tiny script (claims to be "< 1KB", but it's 1.6KB on my page). Any custom events can be sent via JS API. The docs are top notch, the docker set-up is straightforward, and you can import and export CSV data as you please. I'm loving the feature set, and this is what I ultimately went with. One valid criticism of plausible is that it's slightly convoluted, featuring two databases and an elixir server, which makes it pretty hard to set up without docker, but I don't mind it.</p>
<p><a href="http://umami.is/">Umami</a> is another "simple analytics" tool. I find it <em>extremely</em> similar to plausible — same minimal dashboard, same cookie-free tracking, same tiny script (1.6KB gzip). It launched in 2020 with great hype, but plausible quickly caught up. As of 2023, umami still lacks <a href="https://github.com/umami-software/umami/discussions/1085">data import from GA,</a> and <a href="https://github.com/umami-software/umami/issues/310">only supports export via DB dump</a> — plausible can do both easily. I also prefer plausible docs. Umami has only one DB, and <a href="https://www.reddit.com/r/selfhosted/comments/o8r293/comment/h374uqf/?utm_source=share&utm_medium=web2x&context=3">can run as a raw node service.</a> Overall, it's a viable choice, and competition is always good.</p>
<p>For historical reference, <a href="https://usefathom.com/">fathom</a> was among the first privacy-focued trackers. It has since re-launched as a paid product, freezing development on the <a href="https://github.com/usefathom/fathom">open-source version</a>. The fact that you can still install and use it demonstrates my point on "suture-proofness" in action. I'd prefer something that's actively devloped, and I see no reason to pick it over the tools mentioned above.</p>
<p>Among the lesser-known tools I found these three to stand out from the competition:</p>
<ul>
<li><a href="https://www.offen.dev/">Offen</a> (dope landing page design!) is built around "fairness". Your visitors must give <em>explicit consent</em> to data collection, and can view or delete their data at any time. On the practical side, it comes with auto-renewing SSL out of the box, which is nice. I'm not against <em>consent,</em> but I have personally developed <em>popup blindness,</em> and I'm afraid the users will just scroll with the banner hanging around. Sadly, it can't track custom events (clicks, forms, etc) as of 2023, and has no import / export (presumably by design).</li>
<li><a href="https://www.goatcounter.com/">Goatcounter</a> can run without JS via a tracking pixel (but comes with full JS version, too). Extras: CSV export, API access, good docs, signle-binary deploy, and the name is quite funny. You might say the UI is dated, but I'd call it <em>hacker-themed.</em></li>
<li><a href="https://ackee.electerious.com/">Ackee</a> comes with <a href="https://docs.ackee.electerious.com/#/docs/Anonymization">extra anonymization</a> and features a <a href="https://docs.ackee.electerious.com/#/docs/API">GraphQL API</a> that allows you to import / export data as needed and build custom integrations or dashboards.</li>
</ul>
<p>Then, of course, you can just take a generic log analyzer (grafana, graylog, goaccess) and throw any data you wish there. You get more flexibility, but there are no precofigured views for common web stats, and you must write the actual tracking logic (both JS and the endpoint) yourself. We track product metrics via a log aggregator at work as it lets us trace problems to back-end issues, but I wouldn't take this path for a simple website.</p>
<p>Ultimately, I picked plausible because of its minimal approach, complete feature set (mainly data import / export) and great docs. However, all the tools on my list have their strenghts, and you can pick whichver you prefer, or even try a few before commiting. Here's a flowchart with <em>some</em> things that might influence your decision:</p>
<p><img src="https://thoughtspile.github.io/images/self-hosted-analytics/alternatives.png?invert" alt="" /></p>
<h2>Up and running</h2>
<p>Before we get started, you'll most certainly need two pieces of infrastructure:</p>
<ol>
<li>A <em>server</em> — fair enough, you need a hosting to self-host anything. I use an entry-level <a href="https://www.linode.com/pricing/#compute-shared">linode</a> for $5 a month, which is a typical price. With your average blog traffic this leaves room for other services — <a href="https://hub.docker.com/r/kylemanna/openvpn">OpenVPN server,</a> <a href="https://docs.gitea.io/en-us/">Gitea</a> or whatever you wish.</li>
<li>A custom <em>domain name.</em> GitHub can't proxy <em>some</em> traffic from <code>username.github.io</code> to your analytics server, and HTTPS won't work on a raw server IP without a domain name. You can get one starting at $5 a year (more for a decent TLD, make it $20). I heard <a href="https://porkbun.com/">porkbun</a> suggested. You are not obliged to use this domain for your website (even though <a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site">GH pages support that</a>), so something shady like <code>fk12sdj1244.cyou</code> works if you wish.</li>
</ol>
<p>With that in place, we can set up our plausible instance. I'll make it quick, since there already is a <a href="https://esc.sh/blog/plausible-analytics-selfhosting/">tutorial by Mansoor,</a> and the docs do a decent job:</p>
<ol>
<li><a href="https://docs.docker.com/engine/install/ubuntu/">Install docker.</a></li>
<li>Start plausible: clone the repo, edit config, and <code>docker-compose up -d</code> the service (see <a href="https://plausible.io/docs/self-hosting">docs</a>). If this worked, you can access the UI at <code>http://your.ip.1.1:8000</code></li>
<li>You can't send analytics from your HTTPS website to an HTTP endpoint, so there's more work.</li>
<li>Go to your DNS settings, come up with an analytics subdoiain (like <code>an.you.io</code>) and add server IP to the relevant A/AAAA record. Plausible <em>can not</em> live on a <code>/path</code>, it needs its own subdomain. Success = the UI is live on <code>http://an.you.io:8000</code></li>
<li>Now we need a reverse proxy for SSL termination. I went for a full-dockerized setup with <a href="https://github.com/nginx-proxy/nginx-proxy">nginx-proxy</a> container. Set <code>VIRTUAL_HOST</code> in <code>plausible-conf</code> to enable discovery, append <code>nginx-proxy</code> to <code>docker-compose.yml</code>, restart, and you should be able to access analytics at <code>http://an.you.io</code>. Un-exposing the port 8000 at this point won't hurt.</li>
<li>Now, the actual SSL certificates. nginx-proxy has an <a href="https://github.com/nginx-proxy/acme-companion">acme-companion</a> container that manages letsencrypt certificates for you. Add it to your <code>docker-compose.yml</code> along with all the extra volume declaraions, and you're all set — <code>https://an.you.io</code></li>
<li>It's time to add <code>https://an.you.io/js/script.js</code> script to your website and watch the data flow.</li>
<li>The most painful step is importing historical data from google analytics (thanks google, it <em>is</em> possible). Here are <a href="https://plausible.io/docs/google-analytics-import">the docs</a> to help you. Took me an hour of Google Cloud and SSO suffering, but it all worked out fine.</li>
</ol>
<p>Et voila, you're self-hosting your analytics. No need to involve google nay more. The same process applies to any dockerized setup with minor adjustments, not just plausible.</p>
<hr />
<p>Self-hosting your website analytics comes at a cost — to be precise, around $7 a month for hosting and the domain, but you can use the same infrastructure for other tasks <em>and</em> you get quite a few benefits in doing so:</p>
<ol>
<li>User privacy — no data sent to any third parties.</li>
<li>Less missing data — your own domain is unlikely to be adblocked.</li>
<li>Data ownership — you can always export historic data and move to another tool (worst case — via SQL dump).</li>
<li>Future-proofness — even if the developer goes out of business, your instance stays with you.</li>
<li>Price (when compared to hosted alternatives) — you can easily run analytics for under $7/mo, including a custom domain, while hosted solutions start at $9.</li>
<li>Most importantly, it's one small step that we, as a content creators, can take to make a dent in corporate monopoly.</li>
</ol>
<p>Most privacy-focused analytics tools also feature no cookies (aka fewer legal concerns, but not like in legal advice etc.), simpler interface, and smaller tracking JS (both at the expense of less data).</p>
<p>We examined seven open-source analytics tools that you can self-host. Here they are, in order of my preference:</p>
<ol>
<li><a href="https://plausible.io/">Plausible</a> is a minimal analytics solution with <em>enough</em> features for me: custom events, API, GA import, CSV export. Great docs!</li>
<li><a href="http://umami.is/">Umami</a> is very similar to plausible. I prefer import / export and docs on plausible, but umami has slightly better mobile UX. Close call.</li>
<li><a href="https://www.goatcounter.com/">Goatcounter</a> is a <em>hacker-style</em> tracker. Runs as a single binary, supports no-JS tracking via pixels or log imports. UI choices are divisive.</li>
<li><a href="https://matomo.org/">Matomo</a> is a well-established platform with lost and lots of features you probably don't need.</li>
<li><a href="https://ackee.electerious.com/">Ackee</a> comes with a GraphQL API and nice UI.</li>
<li><a href="https://www.offen.dev/">Offen</a> is <em>conceptual:</em> it requires explicit consent, and users can view their data. How well this works in practice remains to be seen. Some features are missing — most importantly, custom event tracking.</li>
<li><a href="https://usefathom.com/">Fathom</a> is not bad, but I can't recommend it since the development of free version has been shut down.</li>
</ol>
<p>Finally, I showed you how to install and integrate your own plausible instance, along with HTTPS.</p>
<p>Now it's your turn to self-host your analytics and say good-bye to google.</p>
The complete guide to safe type narrowing in TypeScript2023-01-31T00:00:00Zhttps://thoughtspile.github.io/2023/01/31/typescript-safe-narrow/<p>Say I'm building a TODO app with two tabs: done and pending tasks. To make the app routable, I put the active tab into the <code>?tab</code> query parameter, so that <code>mytodo.io?tab=done</code> takes me directly to the done tasks. I implement routing like this (pardon my hand-coded querystring parser):</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> tasks <span class="token operator">=</span> <span class="token punctuation">{</span><br /> done<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> pending<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> tab <span class="token operator">=</span> location<span class="token punctuation">.</span>search<br /> <span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'&'</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>f <span class="token operator">=></span> f<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'='</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>p <span class="token operator">=></span> p<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token string">'tab'</span><span class="token punctuation">)</span><span class="token operator">?.</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> items <span class="token operator">=</span> tasks<span class="token punctuation">[</span>tab<span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre>
<p>And stupid TS complains: <code>type X can't be used to index type { done: ...; pending: ...; }</code>. I put the active tab into the URL myself, what do you want from me? Time to meet my little friend, <code>tasks[tab as keyof typeof tasks]</code>.</p>
<p>But wait, TS has a point. No matter what you <em>expect</em> the URL to be, the user might manually put anything into the <code>?tab</code> parameter, or remove it altogether, purposefully or by accident. The TS-detected type <code>string | undefined</code> is 100% correct, and bypassing TS checks with <code>as</code> opens my app to a variety of bugs caused by missing <code>items</code>.</p>
<p>Since the bug happens in the "real world" of JS, as opposed to the "type-only world" of TS, we need some real-life checks to make the code safe:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> items <span class="token operator">=</span> <span class="token punctuation">(</span>tab <span class="token operator">===</span> <span class="token string">'done'</span> <span class="token operator">||</span> tab <span class="token operator">===</span> <span class="token string">'pending'</span><span class="token punctuation">)</span> <br /> <span class="token operator">?</span> tasks<span class="token punctuation">[</span>tab<span class="token punctuation">]</span> <br /> <span class="token operator">:</span> tasks<span class="token punctuation">.</span>pending<span class="token punctuation">;</span></code></pre>
<p>From TS point of view our condition is a "type narrowing" expression: in general, <code>tab</code> is of type <code>string | undefined</code>, but the condition only evaluates to true for <code>tab</code> of type <code>'done' | 'pending'</code>, and <em>that</em> can be safely used to index <code>tasks</code> object.</p>
<p>Type narrowing is useful when your program gets values from the outer world: reading <code>localStorage</code>, (or, in general, JSON-parsing strings), parsing URLs, or reading raw user input. You can also accept wider types as an API design choice to let users call <code>mount('#mount')</code> in addition to <code>mount(document.querySelector('#mount'))</code>.</p>
<p>In this article, we cover several techniques to safely narrow variable types in TS:</p>
<ul>
<li>Using native JS operators, such as <code>typeof</code>, <code>===</code> and more.</li>
<li>Detecting types inside custom functions, known as type guards. These are useful, but, surprisingly, <em>not typesafe.</em></li>
<li>Writing "downcast functions" that accept a wide-typed argument, but only return a narrow type, as in <code>(x: unknown) => number</code>. We discuss four ways to make that happen.</li>
</ul>
<p>Let's get started!</p>
<h2>Type guards and control-flow analysis</h2>
<p>JS has many tools to check the runtime type of a variable. TS is smart enough to see that some areas of code are only reachable for certain values of the variable, and narrow types there accordingly — this is known as control-flow analysis, or CFA. Here's a non-exhaustive list of things that natively narrow types in a JS / TS program:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">fun</span><span class="token punctuation">(</span>x<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">|</span> <span class="token builtin">number</span> <span class="token operator">|</span> Date<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// typeof is an old favorite</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> x <span class="token operator">===</span> <span class="token string">'number'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> res<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token operator">=</span> x<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token comment">// equality operator is useful for casting to union / literal types</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">===</span> <span class="token string">'de'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> country<span class="token operator">:</span> <span class="token string">'de'</span> <span class="token operator">=</span> x<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token comment">// Date object can't be falsy</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>x<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> res<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">|</span> <span class="token builtin">number</span> <span class="token operator">=</span> x<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token comment">// instanceof, typeof's object-oriented brother</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token keyword">instanceof</span> <span class="token class-name">Object</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> day <span class="token operator">=</span> x<span class="token punctuation">.</span><span class="token function">getDay</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token comment">// finally, 'in' can be used as a duck-type detector:</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> x <span class="token operator">===</span> <span class="token string">'object'</span> <span class="token operator">&&</span> <span class="token punctuation">(</span><span class="token string">'getDate'</span> <span class="token keyword">in</span> x<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> day <span class="token operator">=</span> x<span class="token punctuation">.</span><span class="token function">getDay</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Some narrowing operators (<code>typeof</code>, <code>instanceof</code>) actually access variable types, while the <code>in</code> operator implements a <a href="https://en.wikipedia.org/wiki/Duck_typing">duck-typing</a> check — we assume the type of a variable by looking at its property (<em>if it has a peak, it's a duck</em>). Similarly, we can narrow types of tagged unions based on the <em>tag.</em> For example, <code>Transaction</code> type contains amount in either <code>debit</code> or <code>credit</code> field, but we can alway tell wich one it is by lookig at <code>direction</code>:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">Transaction</span> <span class="token operator">=</span> <br /> <span class="token punctuation">{</span> direction<span class="token operator">:</span> <span class="token string">'incoming'</span><span class="token punctuation">,</span> debit<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span> <span class="token operator">|</span><br /> <span class="token punctuation">{</span> direction<span class="token operator">:</span> <span class="token string">'outgoing'</span><span class="token punctuation">,</span> credit<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">function</span> <span class="token function">amount</span><span class="token punctuation">(</span>t<span class="token operator">:</span> Transaction<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> t<span class="token punctuation">.</span>direction <span class="token operator">===</span> <span class="token string">'incoming'</span> <span class="token operator">?</span> t<span class="token punctuation">.</span>debit <span class="token operator">:</span> t<span class="token punctuation">.</span>credit<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>This function can also be implemented using an <code>in</code> operator, because the mere <em>presence</em> of debit / credit field tells us which kind of transaction we're looking at: <code>'debit' in t ? t.debit : t.credit</code>;</p>
<p>CFA is not limited to <code>if</code> blocks — it also works for early return / throw, ternaries, <code>&&</code> chains, and so on:</p>
<pre class="language-ts"><code class="language-ts"><span class="token comment">// these all work</span><br /><span class="token keyword">function</span> <span class="token function">getString</span><span class="token punctuation">(</span>arg<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">typeof</span> arg <span class="token operator">===</span> <span class="token string">'string'</span> <span class="token operator">?</span> arg <span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">return</span> <span class="token keyword">typeof</span> arg <span class="token operator">===</span> <span class="token string">'string'</span> <span class="token operator">&&</span> arg <span class="token operator">||</span> <span class="token string">''</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> arg <span class="token operator">!==</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token string">''</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> arg<span class="token punctuation">;</span><br /><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> arg <span class="token operator">!==</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">TypeError</span><span class="token punctuation">(</span><span class="token string">'arg is not string'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> arg<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>TS tries its best to narrow types in more complex situations than just a direct condition. For example, since TS 4.4 you can <a href="https://www.typescriptlang.org/play?ts=4.4.2&ssl=4&ssc=1&pln=5&pc=1#code/PTAEGEHsDsBcCdIBtQDEmQO6gILQIZICeAzgJYmgWiwAWApqAQLaMBmk8NDoJps9ZqEy0yAY1oBYAFAgm+eIkyU6jAA6QBcMoRpE19SvmhUAJvW1sy9LmOOgARowf4S9UzLkxujMZHO8kKwwjLSuwogCADSgRJAArqB2JgDm9LCg9pAAbjbZ1pieYGQm9AAe+MxqSPQAXLD69AC0mGTm0CUpTcamTQSKWJ0yRaAASgkptMQxUHCIKOhYuATE5JQ1sJRxiZjwZAJJ-oxIZADW9COqoBxIGK3QKbXD0uZiSAq+MCQZ8W7wAJLQNTxWC1XgITqgAA+THizCc8AA3DIyGxQAAKBoGSBo342QHAjIAXhJoAARN89g8yQBKUAAbxkoFAeIBQJBiNAckpQ2kAF9QPQkG4GUyWX8CRyuWBoHCETI+c85IDuNQ7G4Ymo9pwaJBQAAVRoAZTEezUGQALAA6C0xPwBNh3TIrUgUEaYBJIUygGDERyMfBqarWb2wPVXe2MErkAJXVHg-ACVhwK0jfWiSisYwZOiJzKgXLwIi8MhVGqgeD0Nj4MRhrgnc4+a7IO6dJ7SGR+aDfKgkI0Qh6gIl6bG4iXs4mkikDlJk5Ed2RgADqnu9YVyoAA+tBNJvQGFvV2EMhrk7jIRXUYgyd3O2UWj0RR+1SUnTGdJmazJbBOdyZ9DYXhGwqBMa0AGYFUFYVGHfT9x0JX8wB5QcYVlICuBKUBwIVJUwBVQ0DBNM1LRtUAmibQtyG8bYAHJKySeJFAsWA-RgGIIxgY8UEdLARnPVY1XsMJoFMcs6GoEhOAyHFDnMK0DQzYROFOSgRAsA1jVNMhzRGZJQGYfBG0rVwYHwBxy1cEg4XNMgvm4PMrmyBQdHM0JXGgGiMgkYw0m9choDEC5FyoDJMHCU16ETdx5NQHVykqap6BifBQAAAw2VKlPiL0mE0f1Mlc3V8rxDxgsw-jLzvaQNl7Z9OgAJiHEd6Bkr8JyHKdkNned40fPsZ3qt8xTahDpXBF8ALQhEQKwm1IKFEVYPFfEJ0Q8bIVQuVgMw60LRwhc5Firg2GylBzFgfAyGFXh6DqEZaFgWA1BIWoQBSfZaHiBwrT8ZhgGYcREEkthYGAAj6CI7TQeBW5gAtC0AHYwIABiAA">put the narrowing condition into a variable:</a></p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> x<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">|</span> <span class="token builtin">number</span> <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> isNumber <span class="token operator">=</span> <span class="token keyword">typeof</span> x <span class="token operator">===</span> <span class="token string">'number'</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> y<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token operator">=</span> isNumber <span class="token operator">?</span> x <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span></code></pre>
<p>However, a bunch of operations that should, in theory, narrow the types, have no effect on TS:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> mixed <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token string">'a'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> nums<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> mixed<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>x <span class="token operator">=></span> <span class="token keyword">typeof</span> x <span class="token operator">===</span> <span class="token string">'number'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> countries <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'de'</span><span class="token punctuation">,</span> <span class="token string">'us'</span><span class="token punctuation">]</span> <span class="token keyword">as</span> <span class="token keyword">const</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> str<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token string">'de'</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> country<span class="token operator">:</span> <span class="token string">'de'</span> <span class="token operator">|</span> <span class="token string">'us'</span> <span class="token operator">=</span> countries<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span> <span class="token operator">?</span> str <span class="token operator">:</span> <span class="token string">'de'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> nums2<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> mixed<span class="token punctuation">.</span><span class="token function">every</span><span class="token punctuation">(</span>x <span class="token operator">=></span> <span class="token keyword">typeof</span> x <span class="token operator">===</span> <span class="token string">'number'</span><span class="token punctuation">)</span> <span class="token operator">?</span> mixed <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre>
<p>Which brings us to user-defined type guards.</p>
<h3>Type guard functions</h3>
<p>TS lets you mark a function as a type narrowing condition by making it a <em>user-defined type guard</em> — just put <code>arg is SomeType</code> into the return type:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">isDate</span><span class="token punctuation">(</span>arg<span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token operator">:</span> arg <span class="token keyword">is</span> Date <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> arg <span class="token keyword">instanceof</span> <span class="token class-name">Date</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>However, as of TS 4.9, a function is never <em>inferred</em> to be a type guard based on its contents — you just get a normal <code>(arg: Something) => boolean</code> type. This lack of automatic guard inference is exactly why <code>filter</code> and <code>every</code> failed to narrow the array types. Adding an explicit type guard signature fixes it:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> mixed <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token string">'a'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> isNumber <span class="token operator">=</span> <span class="token punctuation">(</span>x<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> x <span class="token keyword">is</span> <span class="token builtin">number</span> <span class="token operator">=></span> <span class="token keyword">typeof</span> x <span class="token operator">===</span> <span class="token string">'number'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> nums<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> mixed<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>isNumber<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> nums2<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> mixed<span class="token punctuation">.</span><span class="token function">every</span><span class="token punctuation">(</span>isNumber<span class="token punctuation">)</span> <span class="token operator">?</span> mixed <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre>
<p>Another weak point of TS guard functions is that they are <em>not typesafe.</em> The guard function might contain most absurd and outrageous checks, and TS will be OK with that:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">isNumber</span><span class="token punctuation">(</span>x<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> x <span class="token keyword">is</span> <span class="token builtin">number</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token keyword">function</span> <span class="token function">isString</span><span class="token punctuation">(</span>x<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> x <span class="token keyword">is</span> <span class="token builtin">string</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">typeof</span> x <span class="token operator">===</span> <span class="token string">'boolean'</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token keyword">function</span> <span class="token function">isObject</span><span class="token punctuation">(</span>x<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> x <span class="token keyword">is</span> object <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">0.5</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Switching over variable type</h3>
<p>Regardless of the flavor, type guards let us safely do different things based on a runtime variable type — ideally, covering all the possible cases. For example, let's extract a DOM node from a parameter that can be either a selector or a DOM node:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">mount</span><span class="token punctuation">(</span>where<span class="token operator">:</span> Element <span class="token operator">|</span> <span class="token builtin">string</span> <span class="token operator">|</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> Element<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> where <span class="token operator">===</span> <span class="token string">'function'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token function">where</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>where <span class="token keyword">instanceof</span> <span class="token class-name">Element</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">render</span><span class="token punctuation">(</span>where<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br /> <span class="token comment">// by exclusion, "where" is a string selector here</span><br /> document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span>where<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>render<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>This is very handy!</p>
<h2>Downcast functions</h2>
<p>Wouldn't it be nice if, instead of writing typechecks over and over, you just had a function that takes a wide-typed variable and <em>magically</em> returns a narrow type? Let's call these nice things "downcast functions" (<a href="https://twitter.com/lexi_lambda">Alexis King</a> calls them "parsers" in the article I got the idea from — <a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/">"Parse, don't validate"</a>, but <em>parsing</em> feels too strongly associated with de-serializing in JS, hence the name change). An example would be:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">toNumber</span><span class="token punctuation">(</span>arg<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">{</span><span class="token operator">??</span><span class="token operator">?</span><span class="token punctuation">}</span></code></pre>
<p>Unlike user-defined type guards, this function is fully type-checked by TS. Using it is a pleasure:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> threads<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token operator">=</span> <span class="token function">number</span><span class="token punctuation">(</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">THREADS</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> age<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token operator">=</span> <span class="token function">number</span><span class="token punctuation">(</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The only remaining question is how to actually implement downcast functions. When the input type matches the output type, this is trivial:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">toNumber</span><span class="token punctuation">(</span>arg<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> arg <span class="token operator">===</span> <span class="token string">'number'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> arg<span class="token punctuation">;</span><br /> <span class="token comment">// ???</span><br /><span class="token punctuation">}</span></code></pre>
<p>However, when the input doesn't cleanly fit the output type, we must do something else. There are four viable options (and an extra funny one).</p>
<h3>Convert</h3>
<p>Certain input values can be converted to the output type in a logical way. For example, let's convert numeric strings to <code>number</code>:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">toNumber</span><span class="token punctuation">(</span>arg<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">,</span> fallback<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> arg <span class="token operator">===</span> <span class="token string">'number'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> arg<span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> arg <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">Number</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>If you manage to convert all the possible input values to output type — good for you! In most cases, though, some values don't have a sensible conversion — what's the <code>number</code> for an arbitrary object? So, we're back to the initial question of what to do with the remaining values.</p>
<h3>Fallback</h3>
<p>The simple way out is returning some "fallback" value. For <code>number</code>, a natural choice is <code>NaN</code>:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">toNumber</span><span class="token punctuation">(</span>arg<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> arg <span class="token operator">===</span> <span class="token string">'number'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> arg<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token number">NaN</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>In some cases, a more useful default exists. When reading the "number of times the user viewed a banner" from <code>localStorage</code>, you might default to <code>0</code> for missing or invalid values. You can even accept a default in the argument:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">toNumber</span><span class="token punctuation">(</span>arg<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">,</span> fallback<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> arg <span class="token operator">===</span> <span class="token string">'number'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> arg<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> fallback<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Alternatively, you can fall back to <code>null</code>, letting the caller pass the default in <code>??</code> operator:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> offerViewed <span class="token operator">=</span> <span class="token function">toNumber</span><span class="token punctuation">(</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>json<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token number">0</span><span class="token punctuation">;</span></code></pre>
<h3>throw</h3>
<p>Sometimes, there's no sane value to fall back to. True story: we have a REST to GraphQL proxy. GraphQL requests might return <code>null</code> or <code>undefined</code> for missing values, but, since our REST endpoint is obliged to send some value in a 200 response, we used to manually return <code>404 / 5xx</code> responses for nullish values:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token constant">GET</span> <span class="token operator">=</span> <span class="token function">apiHandler</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> user <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">executeQuery</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> query UserId {<br /> user {<br /> id<br /> }<br /> }<br /> </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>user<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Response</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token number">404</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span> id<span class="token operator">:</span> user<span class="token punctuation">.</span>id <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>It was quite inconvenient, since <em>every</em> call site has to worry about the case of missing values. Trust me, this gets out of hand real quick. It's much better to <code>throw</code> on unexpected values, let the caller ignore the case of invalid values altogether, and handle all the errors in one location (here, <code>apiHandler</code>). We greatly simplified our code with this simple non-null downcast:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token generic-function"><span class="token function">exists</span><span class="token generic class-name"><span class="token operator"><</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>value<span class="token operator">:</span> <span class="token constant">T</span> <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token constant">T</span> <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'MISSING'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token constant">T</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">const</span> <span class="token punctuation">{</span> user <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">executeQuery</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> query UserId {<br /> user {<br /> id<br /> }<br /> }<br /></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token punctuation">{</span> id<span class="token operator">:</span> <span class="token function">exists</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">.</span>id <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>So, if <em>unexpected</em> values are an unrecoverable problem, and you have a single place to safely handle the errors, throwing is a perfectly good idea. Again, check out <a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/">"Parse, don't validate"</a> for a more in-depth explanation.</p>
<h3>exit</h3>
<p>As an alternative to throwing you can just stop the program with <code>process.exit</code> in node, or terminate browser tab with <code>location.assign</code>. Sounds pretty destructive, but sometimes it's a good way to proceed.</p>
<p>For CLI programs, it's convenient to <code>exit</code> when a required environment variable is missing. <code>process.exit()</code> returns <code>never</code>, which makes writing this helper a breeze:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">extractEnv</span><span class="token punctuation">(</span>name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> value <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">[</span>name<span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>value<span class="token punctuation">)</span> <span class="token keyword">return</span> value<span class="token punctuation">;</span><br /> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">process.env.</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> is required</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> process<span class="token punctuation">.</span><span class="token function">exit</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h3>loop forever =)</h3>
<p>As the ultimate way to avoid responsibility, you can spin off a <code>while (true)</code> loop on invalid value and call it a day. After this, the function will never return, so the question "what to return" makes no sense. You probably don't want this in real life, but theoretically this produces a correct program.</p>
<hr />
<p>Wrapping up, there's a variety of cases where you need to do different things with a TS value depending on its type. Two main real-life scenarios where this happens:</p>
<ul>
<li>Reading data from untrusted external source: user input, localStorage, JSON strings, <code>process.env</code></li>
<li>Accepting various input types for API convenience: <code>mount('#app')</code> or <code>mount(mountNode)</code></li>
</ul>
<p><code>as</code> operator can push values into stricter types, but this causes bugs when your assumptions are broken. Instead, you need runtime checks on the variable to see if it fits your expectations.</p>
<p>TypeScript uses control-flow analysis to give extra guarantees about a variable (<em>narrow its type</em>) in code areas only accessible behind a runtime check:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">logDay</span><span class="token punctuation">(</span>x<span class="token operator">:</span> Date <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token operator">!</span>x<span class="token punctuation">)</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>x<span class="token punctuation">.</span><span class="token function">getDay</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>You can wrap the code to check if the variable is of type <code>T</code> into a <em>user-defined type guard:</em></p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">isNumber</span><span class="token punctuation">(</span>x<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> x <span class="token keyword">is</span> <span class="token builtin">number</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">typeof</span> x <span class="token operator">===</span> <span class="token string">'number'</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Unfortunately, TS will never infer a function as a type guard, and the code that checks the type is not validated.</p>
<p>A very neat (and type-safe!) alternative to type guards is a <em>downcast function</em> that takes a wide-typed argument and returns a narrow type, e.g. <code>(x: unknown) => number</code>. When the input doesn't match the output, you have four options:</p>
<ul>
<li>Convert the type, e.g. <code>if (typeof x === 'string') return Number(x)</code>. Not all types can be realistically converted to other types.</li>
<li>Use a fallback value — e.g. a <code>0</code>. In particular, <code>null</code> fallback leaves it up to the caller to decide how to proceed.</li>
<li><code>throw</code>, letting the caller choose between handling the error itself <em>or</em> ignoring it and leaving the handling to some higher-level wrapper.</li>
<li>stop the program with <code>process.exit()</code> or <code>location.assign</code></li>
</ul>
<p>Here are all the options we discussed today, in a cute diagram:</p>
<p><img src="https://thoughtspile.github.io/images/ts-narrowing.png?invert" alt="" /></p>
Making sense of TypeScript using set theory2023-01-23T00:00:00Zhttps://thoughtspile.github.io/2023/01/23/typescript-sets/<p>I've been working with TypeScript for a long long time. I think I'm not too bad at it. However, to my despair, some low-level behaviors still confuse me:</p>
<ul>
<li>Why does <code>0 | 1 extends 0 ? true : false</code> evaluate to <code>false</code>?</li>
<li>I'm very ashamed, but I sometimes confuse "subtype" and "supertype". Which is which?</li>
<li>While we're at it, what are type "narrowing" and "widening", and how do they relate to sub/supertypes?</li>
<li>If you want an object that satisfies both <code>{ name: string }</code> and <code>{ age: number }</code>, do you <code>&</code> or <code>|</code>? Both make some sense, since I want a <em>union</em> of the functionality in both interfaces, but I also want the object to satisfy left & (and) right interfaces.</li>
<li>How is <code>any</code> different from <code>unknown</code>? All I get is imprecise mnemonics like "Avoid Any, Use Unknown". Why?</li>
<li>What, exactly, is <code>never</code>? "A value that <em>never</em> happens" is very dramatic, but not too precise.</li>
<li>Why <code>whatever | never === whatever</code> and <code>whatever & never === never</code>?</li>
<li>Why on earth is <code>const x: {} = true;</code> valid TS code? <code>true</code> is clearly not an empty object.</li>
</ul>
<p>I was doing some research on <code>never</code>, and stumbled upon Zhenghao He's <a href="https://www.zhenghao.io/posts/ts-never">Complete Guide To TypeScript’s Never Type</a> (check out his blog, it's super cool!). It mentions that a type is just a set of values, and — boom — it clicked. I went back to the basics, re-formulating everything I know about TS into set-theoretic terms. Follow me as I:</p>
<ul>
<li>Refresh my knowledge of set theory,</li>
<li>Map TS concepts to their set counterparts,</li>
<li>Start simple with booelan, null and undefined types,</li>
<li>Extend to strings and numbers, finding some types that TS can not express,</li>
<li>Jump into objects, proving my assumptions about them wrong,</li>
<li>Finally gain confidence writing <code>extends</code> caluses,</li>
<li>And put <code>unknown</code> and <code>any</code> where they belong.</li>
</ul>
<p>In the end, I solve most of my questions, grow much cozier with TS, and come up with this brilliant map of TS types:</p>
<p><img src="https://thoughtspile.github.io/images/ts-sets/everything.png?invert" alt="" /></p>
<h2>Set theory</h2>
<p>First up, a refresher on set theory. Feel free to skip if you're a pro, but my algebra skills are a bit rusty, so I could use a reminder of how it works.</p>
<p>Sets are unordered collections of objects. In kindergarten terms: say we have two apples aka <em>objects</em> (let's call them ivan and bob, shall we?), and some bags aka <em>sets</em> where we can put the apples. We can make, in total, 4 apple sets:</p>
<ol>
<li>A bag with apple ivan, <code>{ ivan }</code> — sets are written as curly brackets with the set items inside.</li>
<li>Similarly, you can have a bag with apple bob, <code>{ bob }</code>.</li>
<li>A bag with both apples, <code>{ ivan, bob }</code>. Hold onto your hats, this is called a <em>universe</em> because at the moment there's nothing in our world except these two apples.</li>
<li>An empty bag aka empty set, <code>{}</code>. This one gets a special symbol, ∅</li>
</ol>
<p>Sets are often drawn as "venn diagrams", with each set represented as a circle:</p>
<p><img src="https://thoughtspile.github.io/images/ts-sets/apples.png?invert" alt="" /></p>
<p>Apart from listing all the items, we can also build sets by <em>condition.</em> I can say "R is a set of red apples" to mean <code>{ ivan }</code>, considernig ivan is red and bob is green. So far, so good.</p>
<p>Set A is a <em>subset</em> of set B if every element from A is also in B. In our apple world, <code>{ ivan }</code> is a subset of <code>{ ivan, bob }</code>, but <code>{ bob }</code> is not a subset of <code>{ ivan }</code>. Obviously, any set is a subset of itself, and <code>{}</code> is a subset of any other set S, because not a single item from <code>{}</code> is missing from S.</p>
<p>There are a few useful operators defined on sets:</p>
<ul>
<li>Union <em>C = A ∪ B</em> contains all the elements that are in A or in B. Note that <em>A ∪ ∅ = A</em></li>
<li>Intersection <em>C = A ∩ B</em> contains all the elements that are in A <em>and</em> B. Note that <em>A ∩ ∅ = ∅</em></li>
<li>Difference <em>C = A \ B</em> contains all the elements that are in A, but not in B. Note that <em>A \ ∅ = A</em></li>
</ul>
<p>This should be enough! Let's see how it all maps to types.</p>
<h2>What does it have to do with types</h2>
<p>So, the big reveal: you can think of "types" as sets of JavaScript values. Then:</p>
<ol>
<li>Our <em>universe</em> is all the values a JS program can produce.</li>
<li>A type (not even a typescript type, just a type in general) is some set of JS values.</li>
<li>Some types can be represented in TS, while other can not — for example, "non-zero numbers".</li>
<li><code>A extends B</code> as seen in <a href="https://www.typescriptlang.org/docs/handbook/2/conditional-types.html">conditional types</a> and <a href="https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-constraints">generic constraints</a> can be read as "A is subset of B".</li>
<li>Type union, <code>|</code>, and intersection, <code>&</code>, operators are just the union and intersection of two sets.</li>
<li><code>Exclude<A, B></code> is as close as TS gets to a difference operator, except it <em>only</em> works when both <code>A</code> and <code>B</code> are union types.</li>
<li><code>never</code> is an empty set. Proof: <code>A & never = never</code> and <code>A | never = A</code> for any type <code>A</code>, and <code>Exclude<0, 0> = never</code>.</li>
</ol>
<p>This change of view already yields some useful insights:</p>
<ul>
<li>Subtype of type A is a <em>subset</em> of type A. Supertype is a superset. Easy.</li>
<li><em>Widening</em> makes a type-set wider by allowing some extra values. <em>Narrowing</em> removes certain values. Makes geometrical sense.</li>
</ul>
<p>I know this all sounds like a lot, so let's proceed by example, starting with a simple case of boolean values.</p>
<h2>Boolean types</h2>
<p>For now, pretend JS only has boolean values. There are exaclty <em>two</em> — <code>true</code> and <code>false</code>. Recalling the apples, we can make a total of 4 types:</p>
<ul>
<li>Literal types <code>true</code> and <code>false</code>, each made up of a single value;</li>
<li><code>boolean</code>, which is <em>any</em> boolean value;</li>
<li>The empty set, <code>never</code>.</li>
</ul>
<p>The diagram of the "boolean types" is basically the one that we had for apples, just the names swapped:</p>
<p><img src="https://thoughtspile.github.io/images/ts-sets/bool.png?invert" alt="" /></p>
<p>Let's try moving between type world and set world:</p>
<ul>
<li><code>boolean</code> can be written as <code>true | false</code> (in fact, that's exactly how TS impements it).</li>
<li><code>true</code> is a subset (aka sub-type) of <code>boolean</code></li>
<li><code>never</code> is an empty set, so <code>never</code> is a sub-set/type of <code>true</code>, <code>false</code>, and <code>boolean</code></li>
<li><code>&</code> is an <em>intersection,</em> so <code>false & true = never</code>, and <code>boolean & true = (true | false) & true = true</code> (the universe, <code>boolean</code>, doesn't affect intersections), and <code>true & never = never</code>, etc.</li>
<li><code>|</code> is a <em>union,</em> so <code>true | never = true</code>, and <code>boolean | true = boolean</code> (the universe, <code>boolean</code>, "swallows" other intersection items because they're all subsets of universe).</li>
<li><code>Exclude</code> correctly computes set difference: <code>Exclude<boolean, true> -> false</code></li>
</ul>
<p>Now, a little self-assessment of the tricky <code>extends</code> cases:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name"><span class="token constant">A</span></span> <span class="token operator">=</span> <span class="token builtin">boolean</span> <span class="token keyword">extends</span> <span class="token class-name"><span class="token builtin">never</span></span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span><br /><span class="token keyword">type</span> <span class="token class-name"><span class="token constant">B</span></span> <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token keyword">extends</span> <span class="token class-name"><span class="token builtin">boolean</span></span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span><br /><span class="token keyword">type</span> <span class="token class-name"><span class="token constant">C</span></span> <span class="token operator">=</span> <span class="token builtin">never</span> <span class="token keyword">extends</span> <span class="token class-name"><span class="token boolean">false</span></span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span><br /><span class="token keyword">type</span> <span class="token class-name"><span class="token constant">D</span></span> <span class="token operator">=</span> <span class="token builtin">never</span> <span class="token keyword">extends</span> <span class="token class-name"><span class="token builtin">never</span></span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span></code></pre>
<p>If you recall that "extends" can be read as "is subset of", the answer should be clear — A0,B1,C1,C1. We're making progress!</p>
<p><code>null</code> and <code>undefined</code> are just like <code>boolean</code>, except they only contain <em>one</em> value each. <code>never extends null</code> still holds, <code>null & boolean</code> is <code>never</code> since no JS value can simultaneously be of 2 different JS types, and so on. Let's add these to our "trivial types map":</p>
<p><img src="https://thoughtspile.github.io/images/ts-sets/finites.png?invert" alt="" /></p>
<h2>Strings and other primitives</h2>
<p>With the simple ones out of the way, let's move on to string types. At first, it seems that nothing's changed — <code>string</code> is a type for "all JS strings", and every string has a corresponding literal type: <code>const str: 'hi' = 'hi';</code> However, there's one key difference — there are <em>infinitely many</em> possible string values.</p>
<blockquote>
<p>It might be a lie, because you can only represent so many strings in finite computer memory, but a) it's enough strings to make enumerating them all unpractical, and b) type systems <em>can</em> operate on pure abstrations without worrying about dirty real-life limitations.</p>
</blockquote>
<p>Just like sets, string types can be constructed in a few different ways:</p>
<ul>
<li><code>|</code> union lets you constuct any <em>finite</em> string set — e.g. <code>type Country = 'de' | 'us';</code>. This won't work for <em>infinite</em> sets — say, all strings with length > 2 — since you can't write an infinite list of value.</li>
<li>Funky <a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html">template literal types</a> let you construct <em>some</em> infinite sets — e.g. <code>type V = `v${string}`;</code> is a set of all strings that start with <code>v</code>.</li>
</ul>
<p>We can go a bit further by making unions and intersections of literal and template types. Fun time: when combining a <em>union</em> with a <em>template,</em> TS is smart enough to just filter the literals againts the template, so that <code>'a' | 'b' & `a${string}` = 'a'</code>. Yet, TS is not <em>smart enough</em> to merge templates, so you get really fancy ways of saying <code>never</code>, such as <code>`a${string}` & `b${string}`</code> (obviously, a string can't start with "a" and "b" at the same time).</p>
<p>However, some string types are <em>not</em> representable in TS at all. Try "every string except 'a'". You could <code>Exclude<string, 'a'></code>, but since TS doesn't <em>actually</em> model <code>string</code> as <em>union of all possible string literals,</em> this in fact evaluates back to <code>string</code>. The template grammar can not express this negative condition either. Bad luck!</p>
<p>The types for numbers, symbols and bigints work the same way, except they don't even get a "template" type, and are limited to finite sets. It's a pity, as I could really use some number subtypes — <em>integer, number between 0 and 1,</em> or <em>positive number.</em> Anyways, all together:</p>
<p><img src="https://thoughtspile.github.io/images/ts-sets/primitives.png?invert" alt="" /></p>
<p>Phew, we've covered all primitive, <em>non-intersecting</em> JS / TS types. We've gotten comfortable moving between sets and types, and discovered that some types can't be defined in TS. Here comes the tricky part.</p>
<h2>Interfaces & object types</h2>
<p>If you think <code>const x: {} = 9;</code> makes no sense, this section is for you. As it appears, our mental model of what TS object types / records / interfaces was built on the wrong assumptions.</p>
<p>First, you'd probably expect types like <code>type Sum9 = { sum: 9 }</code> to act like "literal types" for objects — matching a single object value <code>{ sum: 9 }</code>, adjusted for referential equality. This is absolutely <em>not</em> how it works. Instead, <code>Sum9</code> is a "<em>thing</em> on which you can access propery <code>sum</code> to get <code>9</code>" — more like a condition / constraint. This lets us call <code>(data: Sum9) => number</code> with an object <code>obj = { sum: 9, date: '2022-09-13' }</code> without TS complaining about unknown <code>date</code> property. See, handy!</p>
<p>Then, <code>{}</code> type is not an "empty object" type corresponding to a <code>{}</code> JS literal, but a "thing where I can access properties, but I don't care about any particular properties". Aha, now we can see what's going on in our initial mind-bender: if <code>x = 9</code>, you can safely <code>x['whatever']</code>, so it satisfies the unconstrained <code>{}</code> interface. In fact, we can even make bolder claims like <code>const x: { toString(): string } = 9;</code>, since we can <code>x.toString()</code> and actuallty get a string. More yet, <code>keyof number</code> gives us <code>"toString" | "toFixed" | "toExponential" | "toPrecision" | "valueOf" | "toLocaleString"</code>, meaning that TS secretly sees our primitive type as an object, which it is (thanks to <a href="https://javascript.plainenglish.io/javascript-boxing-wrappers-5b5ff9e5f6ab">autoboxing</a>). <code>null</code> and <code>undefined</code> do not satisfy <code>{}</code>, because they throw if you try to read a property. Not super intuitive, but makes sense now.</p>
<p>Coming back to my little "<code>|</code> or <code>&</code>" problem, <code>&</code> and <code>|</code> operate on "value sets", not on "object shapes", so you need <code>{ name: string } & { age: number }</code> to get objects with <em>both</em> <code>name</code> and (extra hint: and = <code>&</code>) <code>age</code>.</p>
<p>Oh, and what about that odd <code>object</code> type? Since every property on an interface just adds a constraint to the "thing" we're typing, there's no way to declare an interface that filters out primitive values. It's why TS has a built-in <code>object</code> type that means specifically "JS object, not a primitive". Yes, you can intersect with <code>object</code> to get only <em>non-primitive</em> values satisfying an interface: <code>const x: object & { toString(): string } = 9</code> fails.</p>
<p>Let's add all of these to our type map:</p>
<p><img src="https://thoughtspile.github.io/images/ts-sets/everything.png?invert" alt="" /></p>
<h2>extends</h2>
<p><code>extends</code> keyword in TS can be confusing. It comes from the object-oriented world where you <em>extend</em> a class in the sense of <em>adding functionality</em> to it, <em>but,</em> since TS uses <a href="https://www.typescriptlang.org/docs/handbook/type-compatibility.html">structural typing</a>, <code>extends</code> as used in <code>type Extends<A, B> = A extends B ? true : false</code> is <em>not</em> the same <code>extends</code> from <code>class X extends Y {}</code>.</p>
<p>Instead, <code>A extends B</code> can be read as <em>A is a sub-type of B</em> or, in set terms, <em>A is a subset of B.</em> If B is a union, every member of A must also be in B. If B is a "constrained" interface, A must not violate any of B's constraints. Good news: a usual OOP <code>class A extends B {}</code> fits <code>A extends B ? 1 : 0</code>. So does <code>'a' extends string</code>, meaning that (excuse the pun) TS <code>extends</code> extends <code>extends</code>.</p>
<p>This "subset" view is the best way to never mix up the order of <code>extends</code> operands:</p>
<ul>
<li><code>0 | 1 extends 0</code> is false, since a 2-element set <code>{0, 1}</code> is not a subset of the 1-element <code>{0}</code> (even though <code>{0,1}</code> does extend <code>{1}</code> in a geometrical sense).</li>
<li><code>never extends T</code> is always true, because <code>never</code>, the empty set, is a subset of any set.</li>
<li><code>T extends never</code> is only true if T is <code>never</code>, because an empty set has no subsets except itself.</li>
<li><code>T extends string</code> allows T to be a string, a literal, or a literal union, or a template, because all of these are subsets of <code>string</code>.</li>
<li><code>T extends string ? string extends T</code> makes sure that T is <em>exactly</em> <code>string</code>, because that's the only way it can be both a subset <em>and</em> a superset of string.</li>
</ul>
<h2>unknown and any</h2>
<p>Typescript has two types that can represent an arbitrary JS value — <code>unknown</code> and <code>any</code>. The normal one is <code>unknown</code> — the universe of JS values:</p>
<pre class="language-ts"><code class="language-ts"><span class="token comment">// It's a 1</span><br /><span class="token keyword">type</span> <span class="token class-name"><span class="token constant">Y</span></span> <span class="token operator">=</span> <span class="token builtin">string</span> <span class="token operator">|</span> <span class="token builtin">number</span> <span class="token operator">|</span> <span class="token builtin">boolean</span> <span class="token operator">|</span> object <span class="token operator">|</span> bigint <span class="token operator">|</span> <span class="token builtin">symbol</span> <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">|</span> <span class="token keyword">undefined</span> <span class="token keyword">extends</span> <span class="token class-name"><span class="token builtin">unknown</span></span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span><br /><span class="token comment">// a shorter one, given the {} oddity</span><br /><span class="token keyword">type</span> <span class="token class-name"><span class="token constant">Y2</span></span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">|</span> <span class="token keyword">undefined</span> <span class="token keyword">extends</span> <span class="token class-name"><span class="token builtin">unknown</span></span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span><br /><span class="token comment">// For other types, this is 0:</span><br /><span class="token keyword">type</span> <span class="token class-name"><span class="token constant">N</span></span> <span class="token operator">=</span> <span class="token builtin">unknown</span> <span class="token keyword">extends</span> <span class="token class-name"><span class="token builtin">string</span></span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span></code></pre>
<p>On a puzzling side, though:</p>
<ol>
<li><code>unknown</code> is <em>not</em> a union of all other base types, so you can't <code>Exclude<unknown, string></code></li>
<li><code>unknown extends string | number | boolean | object | bigint | symbol | null | undefined</code> is false, meaning that some TS types are not listed. I suspect <code>enum</code>s.</li>
</ol>
<p>All in all, it's safe to think of <code>unknown</code> as "the set of all possible JS values".</p>
<p><code>any</code> is the weird one:</p>
<ul>
<li><code>any extends string ? 1 : 0</code> evaluates to <code>0 | 1</code> which is basically a "dunno".</li>
<li>Even <code>any extends never ? 1 : 0</code> evaluates to <code>0 | 1</code>, meaning that <code>any</code> <em>might</em> be empty.</li>
</ul>
<p>We should conclude that <code>any</code> is "some set, but we're not sure which one" — like a type <code>NaN</code>. However, upon further inspection, <code>string extends any</code>, <code>unknown extends any</code> and even <code>any extends any</code> are all true, none of which holds for "some set". So, <code>any</code> is a <em>paradox</em> — every set is a subset of <code>any</code>, but <code>any</code> <em>might</em> be empty. The only good news I have is that <code>any extends unknown</code>, so <code>unknown</code> is still the universe, and <code>any</code> does not allow "alien" values.</p>
<p>So, to finish mapping our types, we wrap our entire diagram into <code>unknown</code> bubble:</p>
<p><img src="https://thoughtspile.github.io/images/ts-sets/all.png?invert" alt="" /></p>
<hr />
<p>Today, we've learnt to that TS <em>types</em> are basically <em>sets</em> of JS values. Here's a little dictionary to go from type-world to set-world, and back:</p>
<ul>
<li>Our universe = all JS values = the type <code>unknown</code></li>
<li><code>never</code> is an empty set.</li>
<li>Subtype = narrowed type = subset, supertype = widened type = superset.</li>
<li><code>A extends B</code> can be read as "A is subset of B".</li>
<li>Union and intersection types are, really, just set union and intersection.</li>
<li><code>Exclude</code> is an <em>approximation</em> of set difference that only works on union types.</li>
</ul>
<p>Going back my our initial questions:</p>
<ul>
<li><code>0 | 1 extends 0</code> is false because <em>{0,1}</em> is <em>not</em> a subset of <em>{0}</em></li>
<li><code>&</code> and <code>|</code> work on sets, not on object shapes. <code>A & B</code> is a set of things that satisfy both <code>A</code> and <code>B</code>.</li>
<li><code>unknown</code> is the set of all JS values. <code>any</code> is a paradoxical set that includes everything, but <em>might</em> also be empty.</li>
<li>Intersecting with <code>never</code> gives you <code>never</code> because it's an empty set. <code>never</code> has no effect in a union.</li>
<li><code>const x: {} = true;</code> works because TS interfaces work by <em>constraining</em> the property values, and we haven't constrained anything here, so <code>true</code> fits.</li>
</ul>
<p>We still have a lot of TS mysteries to solve, so stay tuned!</p>
Seven habits of bad interviewers2022-03-28T00:00:00Zhttps://thoughtspile.github.io/2022/03/28/interview-bad-habits/<p>I’ve been to plenty of bad interviews. Sometimes, only some <a href="https://blog.thoughtspile.tech/2022/03/21/bad-tech-interview/">questions are bad,</a> but usually it goes further than that. Bizarre questions like “what’s the difference between a number and an array” are just a symptom of deeper issues.</p>
<p>Let’s take a step back — <em>why</em> are we interviewing? To hire someone — in our case, a software developer — good at building working software (or at least, ahem, with a good outlook), in <em>our</em> team. A good interview process is precise (not hire people who can’t build software, and not reject people who can), and fast (to save our team’s time, and hire the candidate before anyone else does). That’s it, really. Hire good candidates with a reasonable precision / time ratio.</p>
<p>If this sounds trivial, then why do some interviewers…</p>
<ul>
<li>Enjoy making the candidate look stupid?</li>
<li>Ask questions that every sane person googles in real-life?</li>
<li>Not help a struggling candidate as they would a struggling teammate?</li>
<li>Ask random questions off the top of their head instead of planning ahead?</li>
<li>Or, conversely, stick to a hard plan even as it stops making any sense?</li>
<li>Spend time asking things that are on your CV?</li>
<li>Have four interviews where one would suffice?</li>
<li>Get into large groups, interrupting each other?</li>
</ul>
<p>Because they’re bad at interviewing, that’s why. Or, being more positive — because they’ve lost sight of the <em>goal</em> to be achieved behind the <em>ritual.</em> In this article, I go over the major sources of my frustration with interviews, and share some advice for improving your interviews.</p>
<p><img src="https://blog.thoughtspile.tech/images/bad-interview-habits.png?invert" alt="" /></p>
<h2>Me smart — you stupid</h2>
<p>You can’t do much worse when interviewing than adopt “me smart — you stupid” mentality. Let’s face it, feeling smart is nice. The interviewer has all the answers, while the candidate doesn’t. It’s tempting to act like a genius from another planet who knows everything and be like “oh come on think again” and “even my grandmother can do it”. Bad.</p>
<p>This approach obviously misses the whole point of interviewing — we’re trying to <em>hire</em> someone, not <em>humiliate</em> as many people as possible. As such, it’s more evident in larger companies, where the interviewer is often <em>not</em> the person looking to strengthen his team, but some random fellow whose primary objective is to have lunch ASAP.</p>
<p>I also call these interviews exam-like. To <em>pass,</em> you need to give a <em>correct answer</em> to each question on a list. Not a good model for an exam either, but it’s what many of us grew up with. Anyways, an <em>interview</em> is certainly not an exam. The <em>examiner</em> is much more knowledgeable than the <em>student,</em> which is often not true for interviews (especially not middle / senior-level ones). Most exams cover specific known topics, while strict developer curricula don’t exist. Finally, while both exams and interviews can be a selection mechanism, exams have an extra goal of giving the <em>student</em> an objective overview of his abilities. You see, different things.</p>
<p>So, <em>never</em> assume you’re smarter or somehow better than the candidate. But this is just the first step away from exam-like interviews — two other, less evident treats are focusing on the answer and not giving any hints.</p>
<h2>Focus on the answer</h2>
<p>A common feature of exam-like interviews is the checklist approach. The interviewer asks the question, the candidate answers, we up the score if the answer fits, and move on. The questions therefore tend to be very closed, to facilitate checking <em>correctness,</em> and the difficulty level is tuned by choosing more esoteric topics: a junior JS developer tells about <em>let vs const,</em> the senior — about <em>the event loop.</em></p>
<p>Real software development is rarely about quickly answering very specific questions. In fact, “how to check browser support for server-sent events” is the most minute detail usually fixed by googling at the final stage of problem-solving. What is it about, then? Many things:</p>
<ul>
<li>Brainstorming possible approaches</li>
<li>Decomposing a task into bite-sized subtasks</li>
<li>Iterating on the idea to find weak points</li>
<li>Collaborating with teammates</li>
</ul>
<p>And I haven’t even touched the actual coding yet. Not saying code interviews are worthless, but you get more bang for your buck and test several skills at once by focusing on the problem-solving rather than the <em>right answer.</em></p>
<p>Problem-solving naturally favors more open-ended questions like “design a slider gallery”, over “what touch events exist”, because they have more process, and thus avoid most of the pitfalls I described in my earlier <a href="https://thoughtspile.github.io/2022/03/21/bad-tech-interview/">article on bad interview questions</a>.</p>
<p><strong>Caveat:</strong> answer-centric questions work well for screenings. A non-technical recruiter can ask a few sane questions like “What are some React hooks?” and I’m like “useRef useMemo useEffect”, and we know I’m legit. The lack of trust is <em>slightly</em> annoying, but I’ve seen many candidates who can’t tell an iterator from a cucumber, so I feel you.</p>
<h2>Not giving any hints</h2>
<p>The final treat of “exam-like” interviews that can persist in <em>realistic</em> coding tasks is leaving the candidate alone with the problem, not helping where it’s due. The thinking is that a <em>senior-level developer</em> must know this, and if I don’t — I’m obviously not one. Again, that’s not how development works.</p>
<p>Do you immediately fire or report your teammate who misses a corner case, has room for improvement, or is completely lost? Hopefully not. Collaboration is a key development skill, and you help your friend out, right? Why, then, should the interview be the other way around? Sure,it’s best to plan for edge cases in advance, still cool to identify them yourself and iterate, but it’s not too bad if you can admit and fix a mistake pinpointed to you.</p>
<p>Worst case — don’t even show you’re not satisfied with the answer at all. “So, this is your final answer? Let’s move on then” (to yourself: “Stupid stupid Vladimir, I’ll never hire you”). Happened to me — I solved a <a href="https://www.geeksforgeeks.org/detect-loop-in-a-linked-list/">particular algorithm problem</a> in linear memory three times in three separate interviews until one interviewer told me that an constant-memory solution actually exists. Until then, I believed the first two rejected me just because they disliked my attitude.</p>
<p>There’s no shame in helping a struggling candidate!</p>
<h2>Poor planning</h2>
<p>Many interviewers drop in from their daily job with a “fuck, I’m interviewing in 3 minutes!”, don’t have a question list ready because “I’m a senior engineer, I’ll know my senior buddy from a mile”, and proceed with some trivia from a random “best question list” found online (during the interview, of course) or the last tricky thing they’ve done in their job — both poor choices. I know this because I’ve done it myself, and I’m sorry. Planning is king.</p>
<p>Let’s handle the questions first. Ideally, you want to cover several topics related to the role. In a front-end interview these are probably JS/TS, CSS, and some React (or whatever you use) / algo / deployment / performance. Nice way to go through a topic: a few closed question -> open question -> full-on <em>software design</em> task. There are many interdependencies and constraints, so trying to come up with questions on the fly is guaranteed to fail unless you interview daily.</p>
<p>Timing is not as important as you think. Finishing early is fine — whether the candidate aces all the questions in 10 minutes, or fails even the most basic ones, once you got what you came for, filler talk is not the best use for leftover time. Running badly over-time is worse, because something might be scheduled right after the interview, but easily fixed by announcing the duration with a 30-minute extra, e.g. plan for 1.5 hours for a 1-hour interview.</p>
<p>While we’re at it, locking yourself into a very strict script is no better than having no plan at all. If the question sparks some interesting discussion, don’t kill it just to ask more low-level stuff. If you were looking for a senior developer, but happen to have a good junior before you, you’d better adapt — a good junior is still useful and hard to come by. If the candidate has never worked on performance optimization, there’s no use asking in-depth about TTI measurement. Open-ended tasks give you more flexibility in all these cases than “senior-only” questions.</p>
<p><img src="https://blog.thoughtspile.tech/images/bad-interview-habits-plan.png?invert" alt="" /></p>
<p>So, do prepare the list of questions that reasonably cover the topics you care about, but don’t obsess over that plan too much — at the end of the day, you’re after a candidate to reinforce your team, not a walking encyclopedia of development.</p>
<h2>Ignoring the CV</h2>
<p>Interviews are precious face time with the candidate best used to get to know each other, judge social and problem-solving skills. Why waste this opportunity on reiterating things that are evident from the CV?</p>
<p>I worked on large-scale products in big tech companies, got a degree from a top university, and even have a blog and some open-source work — it’s all on my CV, and learning it takes 15 mins before the interview. Not bragging, but it shows I’m probably not too bad. Now, you have a right to be suspicious — maybe I’m a con artist who just made up my CV and forked some repos. A few low-level questions like “sum numbers in an array” are fine. But why spend an hour on “now, write a FUNCTION” and “have you ever worked in a team”? You haven’t taken a single look at the data you had on me, have you, lazy bastard?</p>
<h2>Overstretching</h2>
<p>Big tech companies are famous for lengthy interview processes: Google has about 8 interviews, and other tech giants are not far behind. So, as a 3-person startup that wants to be the next Google, you need to have many interviews, right? Not so fast. Large companies have many factors that let them get away with (and even require) lengthy interview processes:</p>
<ol>
<li>The candidates are motivated to work in this particular company and are less likely to accept faster offers while interviewing with you.</li>
<li>A steady stream of candidates justifies high rejection rates <em>and</em> requires ranking precision only achieved by collecting more data.</li>
<li>Many employees available for interviewing. It’s fine to spend 80 hours on 10 candidates when it’s 0.02% of your team’s time, not so much when it’s 67%.</li>
<li>Many teams are hiring at any time, and they reasonably want to have some personal time with the candidates.</li>
</ol>
<p>I’m fine with 2 interviews and a phone screening, as long as I genuinely like your place, and you can schedule them within a week or two. I’m a lot less enthusiastic about spending 4 hours with a very talkative team lead in your average outsource agency when I can get a comparable offer in a day. Requiring a larger time commitment shows how amazing <em>you</em> think you are. You’d better really excite the candidate more than the companies with shorter hiring processes do.</p>
<p>Having <em>too many</em> interviews is not the only way to overstretch the process. You can only have two, scheduled too far from each other, or be generally slow to organize them and gather feedback. I’m no expert on office administration, but remember that your goal is to hire people <em>before</em> they accept another offer, so please do your best here.</p>
<p><img src="https://blog.thoughtspile.tech/images/bad-interview-habits-overstretching.png?invert" alt="" /></p>
<p>So, <em>only</em> add more interviews if you really need them, and your offering justifies the extra time required. Also remember that extra interviews are not free for your team.</p>
<h2>Poor group dynamics</h2>
<p>Luckily, 1-on-1 interviews are the standard. However, there are good reasons to have more interviewers: you get a second opinion (as we say in Russia, “one head is good, but two are better”), and several teams that are hiring get to see the candidate in action. However, I often see problems with group dynamics.</p>
<p>Several interviewers, competing and interrupting each other, is just a mess. This might work if you’re making a <em>normal</em> (as possible under the circumstances) conversation without the whole question-answer thing, but these <em>informal</em> interviews have their drawbacks — prioritizing social skills and making it hard to compare many candidates. In a traditional QnA format, it’s better to have <em>one</em> interviewer in charge at any time, with others <em>observing.</em> You can switch roles by section or by answer if you like. But still, there are dangerous spots.</p>
<p>One pitfall is follow-up questions. Every interviewer wants to ask one, then it gets out of control and the follow-ups add no value beyond showing the second interviewer’s here. Once I’ve interviewed with 8 (yes, eight) people at once, and once they were done with their follow-ups to the first question, the time was up and nobody seemed really happy. You often get some back-end dude hiding in the dark to assault you with “OK you can paint your buttons, but can you make me a fault-tolerant DB persistence layer” — wut? Make sure every follow-up helps you assess the candidate better, and isn’t just something that sprung to your mind (see section on not planning ahead).</p>
<p>Another problem that arises with a “passive” interviewer is boredom. Listening to the same answers for the tenth time is not always exciting. However, it’s not very reassuring to see a visibly bored interviewer, especially one who starts playing with the phone because business. The worst of I’ve seen a bored interviewer do is reach into his pants, take something out and chew on it. Hope it was a candy. I needed time to accept this experience.</p>
<p><img src="https://blog.thoughtspile.tech/images/bad-interview-habits-group.png?invert" alt="" /></p>
<p>If you bring buddies to the interview, make sure to agree on who asks what in advance, avoid useless follow-ups that assert your smartness, and make sure not to fall asleep when it’s not your talking time.</p>
<hr />
<p>Many things can make interviews a horrible experience. Here are some tips to be a better interviewer:</p>
<ul>
<li>Focus on finding out what the candidate is good at, not on showing off how smart you are. Yes, even if you’re not hiring for yourself.</li>
<li>Seeing how the candidate approaches problems, is better than getting some particular answer you expect. Open questions suit this style better.</li>
<li>If the candidate struggles, help out! People <em>will</em> get stuck in real life, and acting on feedback is a useful skill.</li>
<li>Prepare a question plan that reasonably covers the topics you care about — it’s hard to come up with questions as you go.</li>
<li>But also make sure the plan is flexible enough to allow for unexpected turns. Is hiring a senior engineer experienced in performance optimization <em>really</em> the only good outcome? Don’t you think people are capable of, uhm, learning?</li>
<li>Get as much data on the candidate as possible beforehand — CV, open source work, shared acquaintances and previous interview results are your friends. Asking this again and again is a waste of precious face time.</li>
<li>Don’t make the interview process longer than it needs to be with more interviews or poor scheduling. If you add an interview, you’d better be sure the candidate will want to invest the time in it.</li>
<li>If you have several interviewers, agree on who asks what, make sure the follow-up questions add value beyond showing you’re here, and try not to fall asleep when not talking. Alternatively, try a “conversation-based” interview if it works for you.</li>
</ul>
<p>Above all, keep you end goal in mind — to hire someone who’ll help you build your product. Happy interviewing!</p>
5 coding interview questions I hate2022-03-21T00:00:00Zhttps://thoughtspile.github.io/2022/03/21/bad-tech-interview/<p>I’ve taken part in well over a hundred tech interviews now, on both sides. Some were fun, and some were pure cringe. I’ve been asked if I have kids (supposedly, people with children won’t have time to job hop), and if “I bet my ass I cost that much”. Fun times.</p>
<p>But today I’d like to talk about cringe tech interview question posing as valid ones. They’re on topic, but they do nothing but annoy and frustrate the candidate. Some are beyond saving, while some are fine, but often mishandled by the interviewer. Here are the main offenders:</p>
<ul>
<li>What happens if you build a circular prototype chain? And other intervew trivia.</li>
<li>How to migrate from webpack 3 to webpack 5? And other specifics.</li>
<li>What’s the difference between a number and an array? And other questions obfuscated with fuzzy wording.</li>
<li>What’s the fastest way to convert a string to number? And other unspecified behavior.</li>
<li>How to make this code sample <em>better</em>? And other questions with missing context.</li>
</ul>
<p>I’ll share tips both for the interviewers to spend their time better, and for the candidates who hit some of these. Let’s go!</p>
<h2>Trivia</h2>
<p>This first category comprises questions that are popular in interviews, but rarely arise in practice. What happens when we run the following code?</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> x <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> y <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br />x<span class="token punctuation">.</span> __proto__ <span class="token operator">=</span> y<span class="token punctuation">;</span><br />y<span class="token punctuation">.</span> __proto__ <span class="token operator">=</span> x<span class="token punctuation">;</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>x<span class="token punctuation">.</span>field<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>We modify the prototype chain in runtime to make it circular. You’ve probably guessed the answer is “nothing good”. You’d never write code like this in production. What a maniac would use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto">the ad-hoc standardized and since deprecated <code>__proto__</code> property?</a>) Of course, the “correct” answer is that <code>y. __proto__ = x</code> throws <code>TypeError: Cyclic __proto__ value</code>, thanks, that’s good to know.</p>
<p>These questions only show how many interviews the candidate has been in, and it’s in case you’ve picked a popular question. If you’ve just come up with a “tricky question that’s not on the web” yourself, it’s just a dice roll of spec memorization.</p>
<p>Old-style JS questions are a notable sub-category of trivia. Yes, those of us who started programming back before 2015 know how <code>var</code> hoisting works, and how to declare (and even extend) a class with <code>.prototype</code> Here, you’re effectively testing when the developer started learning JS (or if he’s worked on a legacy codebase), which is already on the CV. <em>Maybe</em> worth it if you have a legacy project yourself.</p>
<p>As an interviewer, avoid questions that have nothing to do with the real-life development. The biggest fans of these questions are non-technical people (CEOs, founders, PMs) trying to conduct coding interviews by going over a question list found online. Bad news — it doesn’t work like that. Either hire / borrow a real developer just for the interviews, or stick to soft-skill topics and experience anecdotes.</p>
<p>The only way to answer these is, sadly, to participate in many interviews — or, as a cheap substitute, to browse lists of “TOP JS questions ever, guaranteed”. No one can realistically know every corner case of the language and the libraries.</p>
<h2>Specifics</h2>
<p>What issues does migrating to webpack 6 have? How do you check browser support for server-sent events? What <code>aria-role</code> should a modal page have? I don’t know. I usually google for this kind of things, and I’m pretty good at it.</p>
<p>This is what happens when you take my advice on using <em>real-life programming</em> questions a bit too seriously, and drop random problems you recently solved into an interview. Obviously, you’ve made your research, figured out the answer, and are pleased with yourself. But I’m not you, we have different experience to complement each other — and isn’t that even better? Don’t label me as an <em>idiot</em> just because I’ve never touched a certain area of web development.</p>
<p>Instead, ask the candidate about a challenging problem <em>she</em> recently solved — it’s going to be far more entertaining and valuable for both of you.</p>
<p>If you’re asked this question, and have never faced the problem yourself, you’re in trouble. Try to access all the background knowledge around the topic you might have, and <em>maybe</em> you can even figure this out based on common sense.</p>
<h2>Obfuscated questions</h2>
<p>“What’s the difference between a number and an object” is the most surprising question ever — double the surprise every time I hear it yet again. Come on, what’s the difference between your head and a sweet potato fresh out of oven? Excuse me, am I interviewing for a comedian?</p>
<p>The problem here is that a very concrete answer is expected (I figured out it’s “number is immutable”), but asking it directly is “too simple”, so you dress it up by presenting it as open-ended, and add some fuzziness, but in the end it just becomes impossible to decipher what you were trying to ask in the first place.</p>
<p>The worst part is that you can’t even give a hint, because the hint is the answer, so you’re bound to circle around confusing everyone further. “What can you do with numbers that you can’t with objects?” (Add them? Coerce to <code>false</code>?). “Think again, do numbers have properties?” (in <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number">a way,</a> they do). What a waste of time!</p>
<p>Again, interview experience is key here. Once you wrap your head around the <a href="https://dev.to/macmacky/70-javascript-interview-questions-5gfi">common JS interview questions</a> and topics, you’ll get better at recognizing them whatever costume they’re dressed in.</p>
<p>As an interviewer, prefer direct questions (“Which JS types are immutable?”) or at least make sure your wording points in the intended direction, not all over the place (“What’s the difference between primitive types and objects?”).</p>
<h2>Unspecified behavior</h2>
<p>Will <code>console.log(Object.keys({ x: 0, y: 0 }).join())</code> log <code>x,y</code> or <code>y,x</code>? Is <code>x[key]</code> faster than <code>x.find(e => e.key === key)</code>? Under normal circumstances I know the answers are <code>x,y</code> (addition order) and <em>yes,</em> but it’s not on the spec, so I wouldn’t over-rely on it.</p>
<p>Performance-related questions are the most popular of this bunch. JS runtimes change fast, and even if you’ve seen some code perform better a few years ago, it might not be the case any more. The fact that built-in data structures in JS do not specify operation complexity does not help either. We reasonably <em>assume</em> object access <code>obj[key]</code> to take <em>O(1),</em> but in reality this depends on many factors — object size, insert / remove frequency, whether the keys are string literals (see <a href="https://esbench.com/bench/62385aeb6c89f600a57016a7">benchmark</a>). And even when the difference does exist, it’s usually abysmal and irrelevant to actual front-end development.</p>
<p>Overall, these questions are good for assessing both the practical knowledge of the common-case performance, the difference between runtimes, <em>and</em> how it applies to everyday development. As an interviewer, you only need two things to handle this well: be aware of the implementation-dependence yourself and have an open mind when it comes to unexpected answers. These questions are certainly <em>not</em> suitable for a non-interactive interview, such as a single-choice online survey (Pick the fastest method: a) <code>a.pop()</code> b) <code>a.shift()</code> c) <code>a.length -= 1</code>), because there’s no one right answer here.</p>
<h2>Missing-context questions</h2>
<p>Open-ended questions are among the best you can ask in an interview — they’re challenging, and reveal both the candidate’s problem-solving skills and real-world experience. However, these questions are unforgiving to lazy interviewers — both sides should put in some effort to make them work. The two prime ways to mess these up are being uncollaborative and locking in to one possible solution.</p>
<p>For example, what’s wrong with this function?</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">arr<span class="token punctuation">,</span> fn</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <br /> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> arr<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <br /> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">fn</span><span class="token punctuation">(</span>arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <br /> <span class="token punctuation">}</span><br /> <span class="token keyword">return</span> arr<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Why, looks perfectly fine to me. Or maybe not. Depends on what you’re trying to achieve. The code is rarely “right” or “wrong” by itself. Yes, mutating <code>arr</code> is non-idiomatic lately, but maybe it works in-place as an optimization. Yes, the use of <code>var</code> is frowned upon, but maybe it’s a library that’s supposed to work in IE6. Yes, we could replace the <code>for (;;)</code> with a <code>.map</code> or a <code>for..of</code>, but then we’d lose support for all array-likes. See, it all depends on the context. I can endlessly produce variations of this code, but I’d rather not if it’s not causing any trouble.</p>
<p>This question is often presented as “refactor this code to make it BETTER” — sure to excite any junior developer. Good thing grumpy old Vladimir is here to tell you that “refactoring” with no clear goal is the greatest waste of time out there.</p>
<p>To illustrate the solution lock-in problem, suppose you give me a slider gallery implemented with a horrendous mess of jQuery and ask to <em>improve</em> it. Problem is, you believe the best way to spend an hour on this is to write unit tests and convert to React. I think I should change jQuery to vanilla DOM and improve UX with better overscroll handling. Neither of us is right or wrong. In real life, the project goals and technical direction sets the context — how many jQuery dependencies we have across the codebase, if we intend on migrating to React, manual QA availability.</p>
<p>When asking an open-ended question, either let go of your expected answer and focus on the problem-solving process, or introduce the missing requirements to guide towards your desired solution.</p>
<hr />
<p>So, here are the five types of interview questions that I hate, most hopeless to nice-but-dangerous:</p>
<ul>
<li>Trivia: questions that often arise in interview, but never in real life, e.g. “what happens if you create a circular prototype chain”. These test for interview experience, not development skills. Avoid.</li>
<li>Specifics: solutions to unique real-life problems. Example: “how to migrate webpack 3 to webpack 5”. <em>Might</em> work if the answer can be from the expected background knowledge or if the area is super critical to your product. Instead, ask about a challenging problem the candidate recently solved.</li>
<li>Obfuscated questions: simple questions made <em>harder</em> by fuzzy wording, such as “what’s the difference between a number and an object?” instead of “are JS numbers mutable?”. Avoid.</li>
<li>Implementation questions: the answer depends on a specific JS runtime, e.g. “what’s the fastest way to loop over a list”. Avoid strong wordings and don’t bikeshed if the answer is not what you expected.</li>
<li>Missing context: answer requires additional details, e.g. “how to improve this code”. Amazing question as long as you’re ready to problem-solve together, provide the missing constraints and give credit for noting the distinct possibilities. Worst question if you expect a specific question while many objectively good alternatives exist.</li>
</ul>
<p>Here’s my advice on improving interview questions, as a nice flowchart:</p>
<p><img src="https://blog.thoughtspile.tech/images/interview-guide.png?invert" alt="" /></p>
<p>For candidates, I’m afraid, I have fewer actionable tips. Sure, practice makes perfect, and skimming the common interview questions won’t hurt even if you’re a hot shot lead senior, but other than that it’s hard to escape a bad question with an bad interviewer. You can’t convince the interviewer to change the question or his approach on the fly (if you can, apply for a top-tier sales representative position instead). Tough luck, but be ready to admit you don’t know some things, and try to work from the knowledge you have around the topic.</p>
<p>Good luck with your interviews, and see you later!</p>
Don't trust JS library size, min+gzip2022-02-15T00:00:00Zhttps://thoughtspile.github.io/2022/02/15/bundle-size-lies/<p>Many modern front-end libraries and apps obsess over their bundle size. It’s a noble pursuit — an app that uses smaller libraries has less bloat, loads faster, and the users are happier. We can agree to that.</p>
<p>Measuring the impact of a library on the app’s bundle size sounds easy, but it’s absolutely not. Understanding the nuances of library size measurement is important both for maintainers <em>and</em> users. In this article, I share some reasons not to trust the reported <em>min + gzip</em> size, moving from mere curiosities to the more serious critiques:</p>
<ul>
<li>gzip size of a library together with your app is less than the sum of individual gzip sizes.</li>
<li>App build pipeline can affect library size in unexpected ways.</li>
<li><em>Tiny library</em> size is a random number.</li>
<li><em>Full size</em> is a poor measure of a library impact under tree-shaking.</li>
<li>Smaller libraries do not always mean smaller apps.</li>
<li>Should the reported size include dependencies?</li>
<li>Bundle size is a poor proxy for performance.</li>
</ul>
<p>The quick solution? Install the library and check the size change for your particular app. And I have some advice for library authors, too!</p>
<h2>Why gzip lies</h2>
<p>Library sizes are usually reported after gzip compression. This makes sense, since you’re probably going to serve your app assets with compression. But, lucky for us, gzipped library code is almost always <em>larger</em> than the increase of app’s bundle size. If the library is 10Kb min + gzip, your users are likely to end up with a 9.8Kb increase.</p>
<p>Why? Gzip compresses data by looking for repeating words. Library code is likely to repeat much of the app code, so they gzip better together. For example, if I take <a href="https://unpkg.com/mobx@6.3.13/dist/mobx.umd.production.min.js">mobx</a> (15656b) and <a href="https://unpkg.com/react@17.0.2/umd/react.production.min.js">react</a> (4568b) and gzip them together, I get 19992b, not 20224.</p>
<p>Also, brotli is likely to compress <a href="https://css-tricks.com/real-world-effectiveness-of-brotli/">a few percent better</a> than gzip (don’t take my word on it, try yourself). Some tricky library authors report their library size under brotli, which is smart, because it <a href="https://en.wikipedia.org/wiki/Brotli#About">has a pre-defined dictionary</a> and requires less learning data. Beware: gzip size of one library is not comparable with brotli size of another.</p>
<p>Fun, but we’re talking about a difference of a few percent. That’s good to know, but does not totally subvert our expectations.</p>
<h2>Effects of the app build pipeline</h2>
<p>The code that the library author measures and the code that goes into your app can be very different. There’s no deception involved, just the different build setups:</p>
<ul>
<li>Depending on browser compat targets, babel can add extra polyfills or do more transpiling.</li>
<li>A bundled project that contains nothing but the library can end up larger than promised because of bundler runtime.</li>
<li>Minification is very sensitive to small changes, like the number of chunks a function is imported into.</li>
</ul>
<p>This can easily translate into 10–20% difference. But wait, there’s more!</p>
<h2>Very small libraries</h2>
<p>In sub-kilobyte libraries the two effects create so much interference that the exact number stops making any sense. There’s just not enough data to properly “teach” gzip, so compressed size reflects the frequency distribution of characters more than anything remotely practical. Besides, since HTTP data moves in <a href="https://en.wikipedia.org/wiki/IPv6_packet">packets</a>, the actual transmission duration only changes when you cross a packet boundary, which is over 1Kb.</p>
<p>So, the exact size — be it 100, 157, or 200 bytes — does not matter, they’re all just <em>very small libraries.</em></p>
<h2>Full vs core size</h2>
<p>We’ve started with the nitty-gritty details, but library size masurement presents bigger challenges than that. It’s not even clear <em>what</em> should we measure! Suppose our library consists of 50 React components, but a normal app only uses 20 of them. What, exactly, is the <em>library size</em> here?</p>
<p>The number you usually see is <em>full size</em> — in our example, importing all 50 components. This makes sense, since it’s the worst-case metric, and nobody wants to over-promise and be called a liar for it. Moreover, full size is very easy to measure and does not require library knowledge — just <code>import * from lib</code>, measure the asset size, and you’re done. But it has many more disadvantages. As a user, you probably don’t really care about the size of 30 exotic components you’ll never use. Besides, full size favors libraries with <em>less</em> functionality — if you have 20 components, and I have 50, your lib looks 2x smaller. Don’t get me wrong — it’s still useful to track full size, because it prevents you from adding a fat dependency anywhere. But don’t obsess about this number too much.</p>
<p>An direct opposite is <em>critical size</em> — the size of the smallest usable subset of your library. For a UI kit, this probably means the necessary providers. As a library author, you should optimize the hell out of critical size, because every user of your library pays <em>this</em> price.</p>
<p>The most sensible metric (that nobody uses) is <em>average size.</em> Most libraries contain some popular functionality sufficient for 90% uses, and a vast array of special-purpose stuff. Did you know lodash has <code>flatMapDepth</code>? Me neither. As a maintainer, try to focus your optimization effort on this subset. I know you it’s hard to know what, exactly, an average app uses, but give it your best shot.</p>
<p>At any rate, any reported number in a library with several independent parts is probably <em>not</em> the size you care about. You really really have to install it and measure yourself. Oh, and if you maintain such a library and don’t support tree-shaking, you should stop reading right now and go fix that.</p>
<h2>Smaller lib != smaller app</h2>
<p>So, comparing on bundle size favors libraries with less functionality. But using a smaller, less functional library can easily end up bloating your bundle more than a larger one with sufficient functionality. Why? Well, you’ll have to build the missing functionality yourself, and you may have to write more verbose code every time you use it. I don’t think I need to convince you that, all else being equal, you should prefer a library over writing more custom code.</p>
<p>Size should not be the deciding factor when picking a library, especially when it’s not an order-of-magnitude difference. Choose one that has the functionality you need, is well-maintained and widely used, and <em>then</em> factor in the bundle size effect.</p>
<h2>Tracking dependencies</h2>
<p>Another trick question for library size measurement: if your library uses library B under the hood, should you include library B into your size?</p>
<p>For peer dependencies, the answer is “no”. For example, a React component should not include React in its bundle size measurement. Why? Because anyone considering a using a React component is already using React, so it does not count towards increasing the final bundle.</p>
<p>All else is grey zone, but the same logic applies. If the app is likely to already use the dependency, using it has no extra bundle size cost. If the dependency is <em>exotic,</em> it will probably be added to the bundle alongside your library, making it effectively a part of your library, so eligible for inclusion. This might not affect your <em>reported</em> size, but is something to keep in mind for sure.</p>
<p>To further complicate the matter, yarn and npm <a href="https://github.com/yarnpkg/yarn/issues/3778">have different opinions on dependency version resolution,</a> so the app might end up with multiple versions of the same package. Pro tip: <a href="https://github.com/webpack-contrib/webpack-bundle-analyzer">webpack-bundle-analyzer</a> is your friend to look for duplicate dependencies.</p>
<p>What to make of it? Well, prefer libraries that use established dependencies under the hood. Sticking with the zero-dependency philosophy and writing all the needed functionality yourself seems attractive, but overall it leads us to a worse place where every trivial function is included into the app many many times, with no chance of deduplication.</p>
<h2>Does this even matter?</h2>
<p>While min + gzip size is a convenient metric, it’s just a proxy for what we really want to measure — the time users spend waiting for the app to load. And it’s a poor proxy.</p>
<p>Most importantly, the assets are only <em>downloaded</em> during the first visit. On subsequent visits, the script should load from cache (unless you really fuck up). Different use cases have different priorities for first-time vs recurrent visitors, but in most apps I’ve worked on the majority of visits were recurrent.</p>
<p>Then, bundle size is not always reflective of the start-up performance. Would you prefer a 1Kb library that takes 30ms to initialize, or a 10Kb library that initializes in 3ms? I’ll take the one that initializes quicker every day, because this price is paid by every user, not just the first-time visitors.</p>
<hr />
<p>So, by all means — pick smaller libraries, avoid bloat, and have fun doing more with less. However, picking a <em>lighter</em> library in 2022 means much more than looking whose readme claims to be closer to <em>0 bytes, min+gzip.</em> Here are top picks for users:</p>
<ul>
<li>Don’t rely on the reported min+gz size. Measure the bundle size change in your particular use case, with your build setup. There is no one-size-fits-all metric.</li>
<li>Pick the right tool for the job. What good is a smaller library, if you have to write the missing functionality yourself?</li>
<li>Prefer libraries with shared transitive dependencies if you don’t want your app bundle to contain the same logic over and over.</li>
<li>Beware of tiny libraries. See exactly what tradeoffs they made to stay that small — are you OK with missing critical functionality or more glue code on your side?</li>
<li>Prefer real-user metrics over synthetic bundle size.</li>
</ul>
<p>And a few more tips for library authors:</p>
<ul>
<li>Please, design for tree-shaking if you still don’t.</li>
<li>Consider both the critical, average and full sizes of your library.</li>
<li>Relying on a large, but widely used dependency can end up better on average than staying zero-dependency.</li>
<li>Maybe it’s time to drop IE11 support. It can be added with a pass of babel, but the built-in bloat can’t be removed.</li>
<li>Being faster beats being smaller.</li>
</ul>
<p>Finally, I made a cheat sheet with all the conflicting effects that can blur the reported size:</p>
<p><img src="https://blog.thoughtspile.tech/images/lib-bundle-size.png?invert" alt="" /></p>
<p>Hope this helps you build snappier apps without the unnecessary anxiety. See you later.</p>
Why I prefer JS for front-end build automation2022-02-14T00:00:00Zhttps://thoughtspile.github.io/2022/02/14/js-automation/<p>Every front-end project involves some automation to build it, test it, lint it, run dev servers, measure bundle size, and what not. npm scripts are fine for one-liners, but as the workflows grow more complex — run these things in parallel, then do something else, but only if building for production — you need a more coherent orchestration solution. In many projects, this means <code>bash</code> — it can handle anything, from the trivial <code>&&</code> to <code>if .. fi</code> mostrosities in separate shell scripts.</p>
<p>I must confess, I’ve never been comfortable with <code>bash</code>, and for years I’ve seen this as a weakness. But at some point I realized most front-end devs feel the same way. I took a closer look at JS, and it turned out to be a very nice tool for managing automation workflows! In this article, I’ll tell you what made me change my mind:</p>
<ul>
<li>Your team is probably most comfortable with JS</li>
<li>Node is likely installed on your dev and CI machines</li>
<li>Direct access to other JS tools</li>
<li>Node is a cross-platform runtime</li>
<li>Inter-process communication is async and fairly convenient</li>
</ul>
<p>Let’s go — see if this convinces you to stop worrying and embrace JS for your automation. Many of these points also apply when comparing to <code>make</code> or <code>python</code> (yes, I’ve seen a JS project with python automation once). Here’s a quick comparison table, if you’re in a hurry:</p>
<p><img src="https://blog.thoughtspile.tech/images/node-vs-bash.png?invert" alt="" /></p>
<h2>It’s your team’s primary language</h2>
<p>Most front-end teams know JS better than bash or any other language. Sure, node has special APIs, but overall it’s the same familiar landscape of first-class functions, loops and promises. <code>bash</code>? I’ve spent years around it, and I’m still not sure how it works — the syntax is similar but different in unexpected ways, most variables are strings, do modules even exist? Pls don’t correct me if I’m wrong, I’m not 100% certain on this and I don’t care any more. I just google all the time.</p>
<p>Analogy time: Chinese language is beautiful and useful, but you probably don’t insist on speaking it at dailies unless your team is Chinese. Why would you go that way about programming languages? The argument that every half-decent programmer <em>must</em> learn bash is ill-conceived — sure, it’s helpful in some cases, but why make it a requirement?</p>
<p>Your colleagues with other profiles (back-end friends or admins) who need to make an urgent change in your project are likely to know some JS, too. Many have done a random JS project or two, and the C-style syntax lets anybody get at least some idea of what’s going on. Granted, this is also the case with bash, but JS is no worse in this regard.</p>
<p>So, using JS for automation in a JS-first team is the most logical choice.</p>
<h2>The runtime is likely already installed</h2>
<p>Your trouble doesn’t end once you get your <code>bash</code> script to work, because it will often fail on another machines (looking at you, alpine docker containers). <a href="https://en.wikipedia.org/wiki/Shell_script">Various shells</a> (sh, ash, bash, zsh) are slightly different, and the available commands differ across linux distros. Fine, you can manually pick the necessary packages (more on that in a minute), or painfully recreate your logic manually, but it’s all a waste of time.</p>
<p>With node, the problem of missing runtimes is very rare — the CI machines probably run <code>npm</code> / <code>yarn</code> anyways, and these come bundled with <code>node</code>. Also, once your node program runs, it usually runs on every machine.</p>
<h2>Cross-platform out of the box</h2>
<p>Which brings us to the next point — node is a cross-platform runtime that works fine on linux, mac, and windows. OK, MacOS is POSIX-compliant, but many commands still have minor differences in options and output format. Now, do you need Windows support? Most front-end devs I’ve seen use macs, and bash ports for Win exist. Still, supporting Win out of the box for free is always nice:</p>
<ul>
<li>It lowers barrier to contribution for open-source projects.</li>
<li>Once I had to hastily launch a dev server on a Windows server, which was not pleasant.</li>
<li>A manager wants to play around with your project, but he runs Win.</li>
</ul>
<p>Node team has spent a lot of time abstracting the OS differences away. Ignoring that and sticking with bash is counter-productive.</p>
<h2>Direct access to other JS tools</h2>
<p>Most tools in your front-end workflows (webpack / parcel / babel / postcss) expose node APIs. Even non-JS-based tools like <a href="https://esbuild.github.io/getting-started/#deno">esbuild</a> and <a href="https://github.com/swc-project/swc/tree/main/node-swc">swc</a> provide node bindings. If your orchestration runs on node, accessing these APIs is trivial: just import the package, and call the function.</p>
<p>With bash, you have two lousy options to integrate with node-based tools:</p>
<ul>
<li>Jump through hoops of calling the tools’ CLI with weird option formatting.</li>
<li>Write a minimal JS wrapper to call the node API, and call it from bash, wondering where to draw the boundary.</li>
</ul>
<p>As an added benefit, since many tools’ CLI lives in a separate package (like <a href="https://www.npmjs.com/package/@babel/cli"><code>@babel/cli</code></a>), you can skip installing it if you use the node API directly, shaving off a bit of <code>npm i</code> time.</p>
<h2>Decent inter-process communication</h2>
<p>One positive technical aspect of node as an automation runtime is its IPC capabilities. Sometimes you prefer to use another tool via CLI over the node API. Cool — in node, this can be done with <a href="https://nodejs.org/api/child_process.html">child_process</a> — asynchronously, and in a cross-platform way! You can even pipe output between different processes, as with shell pipe <code>|</code>. Yes, the built-in <code>Stream</code> and <code>child_process</code> APIs are not too ergonomic, but you can always use a wrapper for your taste — I like <a href="https://stackoverflow.com/questions/3004811/how-do-you-run-multiple-programs-in-parallel-from-a-bash-script">execa.</a></p>
<p><code>bash</code> is good at process management, too, but there are just too many possibilities for my taste — <a href="https://stackoverflow.com/questions/3004811/how-do-you-run-multiple-programs-in-parallel-from-a-bash-script">this SO questions has five distinct ways of running commands in parallel,</a> and this makes it easy to shoot yourself in the foot if you don’t know what you’re doing (see point on familiarity).</p>
<h2>Vast ecosystem</h2>
<p><code>npm</code> has great packages for all sorts of problems. My favorites are <a href="https://github.com/sindresorhus/execa">execa</a> for managing child processes, <a href="https://github.com/yargs/yargs">yargs</a> for handling CLI options and <a href="https://github.com/chalk/chalk">chalk</a> for output styling.</p>
<p>Yes, many command-line tools exist as well, but you must install them using an OS-specific package manager (apt? brew? apk?). Nobody really wants to deal with this, so you settle on lowest denominator of universal functionality. Besides, any CLI package that you happen to install can be used from node via spawn / exec just as well.</p>
<hr />
<p>So, here are my top reasons to pick JS / node for managing complex automation workflows:</p>
<ol>
<li>JS is your team’s primary language!</li>
<li><code>node</code> runtime is usually installed both locally and in CI, since you’re dealing with <code>npm / yarn</code>.</li>
<li><code>node</code> runs cross-platform, unlike bash and make.</li>
<li><code>node</code> can directly access other JS tools.</li>
<li><code>node</code> IPC (for orchestrating CLI tools) is very decent, especially with <a href="https://github.com/sindresorhus/execa">execa.</a></li>
<li>Many good packages exist for writing CLI tools in node.</li>
</ol>
<p>There are reasons to <em>avoid</em> node (like the lack of tutorials on automation use cases and the complexity of async for people unfamiliar with it), but I still believe it’s the most solid choice for build automation in JS projects.</p>
Using global memoization in React2022-02-09T00:00:00Zhttps://thoughtspile.github.io/2022/02/09/react-global-memo/<p>When our React apps get slow, we usually turn to <code>useMemo</code> to avoid useless job on re-render. It’s a <em>hammer</em> that often works well, and makes it hard to shoot yourself in the foot. But <code>useMemo</code> is not a silver bullet — sometimes it just introduces more useless work instead of making your app faster.</p>
<p>In this article, I explore the less conventional caching techniques in React that can do wonders to optimize your apps:</p>
<ol>
<li>First, we must understand exactly how <code>useMemo</code> works — and why.</li>
<li>What are some use cases where <code>useMemo</code> does not help much?</li>
<li>Then, we examine four global caching methods, where cache is shared between components. As usual, they come with different tradeoffs, and some are even dangerous if used carelessly.</li>
</ol>
<p>There’s a neat cheat sheet awaiting you at the end. Let’s dive in!</p>
<h2>Inside useMemo</h2>
<p>To see if <code>useMemo</code> fits our particular use case, we must know how, precisely, it works. To quote <a href="https://reactjs.org/docs/hooks-reference.html#usememo">the docs,</a> <em>useMemo will only recompute the memoized value when one of the dependencies has changed.</em> This is rather ambiguous, so let’s check against <a href="https://github.com/facebook/react/blob/9d4e8e84f7fb782385d81ffcdcda73822acf4ad1/packages/react-reconciler/src/ReactFiberHooks.new.js#L1906">the implementation:</a></p>
<ol>
<li>The cache is initialized when mounting a component instance, and destroyed when unmounting.</li>
<li>The cache is never shared between different component instances.</li>
<li>The cache stores just a single value — the last one.</li>
</ol>
<p>This is a sensible default. Storing <em>one</em> value never leaks memory, even if you use an unstable dependency. Say our memo (and <code>useCallback</code> is just a wrapper over <code>useMemo</code>) depends on an unstable arrow, <code>onClick</code>:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">id</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> id<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> handleClick <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">onClick</span><span class="token punctuation">(</span>props<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>onClick<span class="token punctuation">,</span> props<span class="token punctuation">.</span>id<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Now we create a new <code>handleClick</code> on every render. If <code>useMemo</code> stored all the previous values, every <code>handleClick</code> would occupy memory forever — bad. Also, storing N values requires N dependency comparisons when reading, which is N times slower than checking once. Sure, <code>useMemo</code> is worthless here, but at least it does not explode.</p>
<p>Localizing cache to a single component guards against missing deps. Suppose you’re sure a scope variable <em>never</em> changes during the component lifetime, so you just omit it from the dependency array:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span>clicks<span class="token punctuation">,</span> setClicks<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> handleClick <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> <span class="token function">setClicks</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> c <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><em>If</em> the cache was shared among multiple components, distinct <code>handleClick</code>s would call the same <code>setClicks</code>, so only one counter would increment — unexpected!</p>
<p>Good job, React team — thanks for saving us the trouble of debugging this! But this safe implementation has its limitations.</p>
<h2>useMemo pitfalls</h2>
<p>While a great default, the locality and single-value limit of <code>useMemo</code> make it useless in some scenarios. For example, consider this attempt at memoizing a large city list:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">RouteItem</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> <span class="token keyword">const</span> cities <span class="token operator">=</span> <span class="token function">useMemo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span><span class="token punctuation">{</span> <br /> <span class="token literal-property property">label</span><span class="token operator">:</span> <span class="token string">'Moscow'</span><span class="token punctuation">,</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token string">'MOW'</span> <br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <br /> <span class="token literal-property property">label</span><span class="token operator">:</span> <span class="token string">'Saint Petersburg'</span><span class="token punctuation">,</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token string">'LED'</span> <br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// 1000 more cities], []); </span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span><span class="token punctuation">></span></span><span class="token plain-text"> <br /> </span><span class="token punctuation">{</span>cities<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> <br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>option</span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>c<span class="token punctuation">.</span>value<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>c<span class="token punctuation">.</span>label<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>option</span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text"> <br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>If we render a 1000 <code>RouteItem</code>s, each one gets its own array, which is wasteful. In this case, we’d prefer sharing the cache between different instances.</p>
<p>Another problem point is alternating dependency values. Let’s say we want to generate color scheme based on checkbox value:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">SchemePicker</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>isDark<span class="token punctuation">,</span> setDark<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <br /> <span class="token keyword">const</span> colors <span class="token operator">=</span> <span class="token function">useMemo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> <br /> <span class="token literal-property property">background</span><span class="token operator">:</span> isDark <span class="token operator">?</span> <span class="token string">'black'</span> <span class="token operator">:</span> <span class="token string">'white'</span><span class="token punctuation">,</span> <br /> <span class="token literal-property property">color</span><span class="token operator">:</span> isDark <span class="token operator">?</span> <span class="token string">'white'</span> <span class="token operator">:</span> <span class="token string">'black'</span><span class="token punctuation">,</span> <br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>isDark<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>colors<span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"> <br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setDark</span><span class="token punctuation">(</span><span class="token operator">!</span>isDark<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"> <br /> toggle theme <br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><span class="token plain-text"> <br /> </span><span class="token punctuation">{</span>props<span class="token punctuation">.</span>children<span class="token punctuation">}</span><span class="token plain-text"> <br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Here, we only have two possible dependency values, <code>true</code> and <code>false</code>, so there is no risk of a memory leak. Yet, on every checkbox change, we compute a fresh color scheme. The old one would be just fine, thank you.</p>
<p>So, in some cases we’d like to:</p>
<ol>
<li>Share cache between different component instances.</li>
<li>Remember several values, not just the last one.</li>
</ol>
<p>No problem, with the power of JS at our disposal we can make it happen.</p>
<h2>Global memo</h2>
<p>If we want to reuse a value between component instances, no hook can save us, because both <code>useState</code> and <code>useRef</code> are local to component instance. But we can extract the cache into module scope, and work from there:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// this is shared between all components</span><br /><span class="token keyword">const</span> cache <span class="token operator">=</span> <span class="token comment">/* some cache */</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">Component</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> <span class="token comment">// cache is always the same object </span><br /> <span class="token keyword">const</span> value <span class="token operator">=</span> cache<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>deps<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Precomputed global constant</h3>
<p>The simplest kind of “cache” is one with no dependencies — a constant that’s usable in every component. And the simplest solution is to just to declare this constant right away:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> cities <span class="token operator">=</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">{</span> <span class="token literal-property property">label</span><span class="token operator">:</span> <span class="token string">'Moscow'</span><span class="token punctuation">,</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token string">'MOW'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <br /> <span class="token punctuation">{</span> <span class="token literal-property property">label</span><span class="token operator">:</span> <span class="token string">'Saint Petersburg'</span><span class="token punctuation">,</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token string">'LED'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <br /> <span class="token comment">// 1000 more cities</span><br /><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token comment">// yay, every RouteItem refers to the same cities</span><br /><span class="token keyword">const</span> <span class="token function-variable function">RouteItem</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span><span class="token punctuation">></span></span><span class="token plain-text"> <br /> </span><span class="token punctuation">{</span>cities<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> <br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>option</span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>c<span class="token punctuation">.</span>value<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>c<span class="token punctuation">.</span>label<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>option</span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text"> <br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Having just <em>one</em> value for all components seems limiting. But, if we know all the possible dependency values in advance, we can just precompute the value for each dependency:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> schemes <span class="token operator">=</span> <span class="token punctuation">{</span> <br /> <span class="token literal-property property">dark</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">background</span><span class="token operator">:</span> <span class="token string">'black'</span><span class="token punctuation">,</span> <span class="token literal-property property">color</span><span class="token operator">:</span> <span class="token string">'white'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <br /> <span class="token literal-property property">light</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">background</span><span class="token operator">:</span> <span class="token string">'white'</span><span class="token punctuation">,</span> <span class="token literal-property property">color</span><span class="token operator">:</span> <span class="token string">'black'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">SchemePicker</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>isDark<span class="token punctuation">,</span> setDark<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <br /> <span class="token comment">// we only have 2 values, each one is stable </span><br /> <span class="token keyword">const</span> colors <span class="token operator">=</span> schemes<span class="token punctuation">[</span>isDark <span class="token operator">?</span> <span class="token string">'dark'</span> <span class="token operator">:</span> <span class="token string">'light'</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>colors<span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"> <br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setDark</span><span class="token punctuation">(</span><span class="token operator">!</span>isDark<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"> <br /> toggle theme <br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><span class="token plain-text"> <br /> </span><span class="token punctuation">{</span>props<span class="token punctuation">.</span>children<span class="token punctuation">}</span><span class="token plain-text"> <br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>However, this technique comes with some drawbacks. Building the object in the initial execution phase delays the first paint, even if you don’t need the value right away. All the data needed to construct the value must be available when the script is initially executed. If any of this is a concern, let’s move on to the next technique!</p>
<h3>Lazy global constant</h3>
<p>So, we want to share a single value between all components, but we want to compute it only when we need it. Fine, it’s a well-known pattern:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> citiesCache<span class="token punctuation">;</span><br /><span class="token comment">// getCities intercepts accessing cities</span><br /><span class="token keyword">const</span> <span class="token function-variable function">getCities</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> <span class="token comment">// use cached value if it exists </span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>citiesCache<span class="token punctuation">)</span> <span class="token punctuation">{</span> <br /> <span class="token keyword">return</span> citiesCache<span class="token punctuation">;</span> <br /> <span class="token punctuation">}</span> <br /> <span class="token comment">// otherwise put the array into the cache </span><br /> citiesCache <span class="token operator">=</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">{</span> <span class="token literal-property property">label</span><span class="token operator">:</span> <span class="token string">'Moscow'</span><span class="token punctuation">,</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token string">'MOW'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <br /> <span class="token punctuation">{</span> <span class="token literal-property property">label</span><span class="token operator">:</span> <span class="token string">'Saint Petersburg'</span><span class="token punctuation">,</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token string">'LED'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <br /> <span class="token comment">// 1000 more cities</span><br /> <span class="token punctuation">]</span><span class="token punctuation">;</span> <br /> <span class="token keyword">return</span> citiesCache<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">RouteItem</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span><span class="token punctuation">></span></span><span class="token plain-text"> <br /> </span><span class="token punctuation">{</span><span class="token function">getCities</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> <br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>option</span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>c<span class="token punctuation">.</span>value<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>c<span class="token punctuation">.</span>label<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>option</span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Here, we delay building the value until we actually need it. Great! And we could even pass some data from an API to the builder, as long as it never changes. Fun fact: storing data in a state manager or an API cache is actually an example of this technique.</p>
<p>But what if we try to generalize this method for multiple values, just like we did with a precomputed map? Oh, that’s a whole different story!</p>
<h3>True memo</h3>
<p>Let’s up our game by letting every component get a special version of city list, with one city excluded. We’d still like to share the cache between several instances, just in case. It’s not that hard:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> cities <span class="token operator">=</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">{</span> <span class="token literal-property property">label</span><span class="token operator">:</span> <span class="token string">'Moscow'</span><span class="token punctuation">,</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token string">'MOW'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <br /> <span class="token punctuation">{</span> <span class="token literal-property property">label</span><span class="token operator">:</span> <span class="token string">'Saint Petersburg'</span><span class="token punctuation">,</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token string">'LED'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <br /> <span class="token comment">// 1000 more cities</span><br /><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> filterCache <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">getCitiesExcept</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">exclude</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> <span class="token comment">// use cached value if it exists </span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>filterCache<span class="token punctuation">[</span>exclude<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <br /> <span class="token keyword">return</span> filterCache<span class="token punctuation">[</span>exclude<span class="token punctuation">]</span><span class="token punctuation">;</span> <br /> <span class="token punctuation">}</span> <br /> <span class="token comment">// otherwise put the filtered array into the cache</span><br /> filterCache<span class="token punctuation">[</span>exclude<span class="token punctuation">]</span> <span class="token operator">=</span> cities<br /> <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> c<span class="token punctuation">.</span>value <span class="token operator">!==</span> exclude<span class="token punctuation">)</span><span class="token punctuation">;</span> <br /> <span class="token keyword">return</span> filterCache<span class="token punctuation">[</span>exclude<span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">RouteItem</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> value <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span><span class="token punctuation">></span></span><span class="token plain-text"> <br /> </span><span class="token punctuation">{</span><span class="token function">getCitiesExcept</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> <br /> <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>option</span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>c<span class="token punctuation">.</span>value<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>c<span class="token punctuation">.</span>label<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>option</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This works, but global caches are vulnerable to infinite growth problem. In a long-lived app, you might eventually get to the point where every possible city was excluded, leaving you with a 1000 copies of your 1000-item array in the cache, most of them useless. To protect against this, we need some way to limit the cache size.</p>
<h3>LRU cache</h3>
<p>To restrict cache size, we need some way to choose exactly which elements to “forget”. This is called <a href="https://en.wikipedia.org/wiki/Cache_replacement_policies"><em>cache replacement policy,</em></a> and there are surprisingly many approaches.</p>
<p>We’ll stick to the simplest method — least-recently used, or LRU cache. We only remember N last values. For example, after passing numbers 1, 2, 3, 1 to an LRU cache of size 2, we only store the values for 3 and 1, while the value for 2 was thrown away. The implementation is not interesting, hope you believe this is doable (see <a href="https://github.com/lukeed/flru/blob/master/src/index.js">flru</a> for details). It’s worth noting that the original <code>useMemo</code> is actually an LRU cache of size 1, because it only stores one last value.</p>
<p>While it sounds good on paper, global bounded cache does not actually work that well for our use cases. To see why, let’s consider a cache of size 1. If we have several component instances alive at once, they <em>likely</em> have different dependency values. If they render in alternating order, every instance encounters the value from the previously rendered one, which is a cache miss, and has to recompute. So, we end up recomputing on every render, and doing some useless comparisons.</p>
<p>More generally, a cache of size N is likely to have misses once N+1 components with different values are alive, and become useless at 2N components. This is not a good quality — a cache shouldn’t care how many consumers exist. We could experiment with other replacement policies — say, frequency-based caches — but they’re way harder to implement, and I feel like React apps don’t have cache usage patterns that could benefit from them.</p>
<p>There is, however, one case where it works: if you have N possible dependency values, and N is <em>small</em> — say, <code>true</code> / <code>false</code>, or a number 1..10, a cache of size N has you fully covered with 100% cache hits, and only computes values when needed. But if that’s the case, a simple global cache works just the same, without the overhead of tracking usage order.</p>
<hr />
<p>Recap time! We’ve started out by looking at <code>useMemo</code> in detail. <code>useMemo</code> cache is never shared between component instances, lives as long as the instance lives, and only stores one last value. There are good reasons for these decisions.</p>
<p>However, this makes <code>useMemo</code> not usable in some cases:</p>
<ol>
<li>When you <em>want</em> to reuse a value between components (e.g. always the same large object)</li>
<li>When your dependency quickly alternates between several values (e.g. true / false / true etc.)</li>
</ol>
<p>Then, we examined 4 (4-and-a-half? 5?) caching techniques with a globally shared cache that overcome these issues:</p>
<ol>
<li>Just use a module constant. Simple, reliable, but builds the object during initial script execution — suboptimal if the object is heavy and not needed during initial render.</li>
<li>Precomputed map — a simple extension of <em>module constant</em> that stores several values. Same drawbacks.</li>
<li>Lazy constant — delay building the object until it’s needed, then cache forever. Removes module constant init delay during script init time.</li>
<li>Full memo — saves <em>all</em> the results of function calls with <em>all</em> arguments. Leaks memory when there are many possible dependency values / combinations. Good when there are few possible inputs. Use with care.</li>
<li>Bounded cache (e.g. LRU). Fixes the memory leak problem, but useless when the number of components alive with different deps is larger than cache size. Not recommended.</li>
</ol>
<p>Here’s a cheat sheet to help you remember these techniques:</p>
<p><img src="https://blog.thoughtspile.tech/images/global-memo-cheatsheet.png?invert" alt="" /></p>
<p>These techniques are useful in regular react apps, and can up your performance. But we don’t always need our cache to be shared between component instances. Luckily, all these methods also work when scoped to a component — stay tuned for the next post on alternate <code>useMemo</code> implementations.</p>
What is a react component, anyways?2022-01-25T00:00:00Zhttps://thoughtspile.github.io/2022/01/25/what-is-react-component/<p>I used to teach a class on React. There’s no better way to start a hands-on course than “Let’s write a simple component!”. But time after time I hear — “Vladimir, what’s a component, anyways?”. Bah, what a question! It’s a react thingie. React apps are made of components. Why do you care at all? When you grow up you’ll see. But at some point in life you just have to find the definitive answer to this question, and for me this time is now.</p>
<p><img src="https://blog.thoughtspile.tech/images/hat-soup.png?invert" alt="" /></p>
<p>Here’s what we know so far. <code>Card</code> here is most certainly a component:</p>
<pre><code>const Card = (props) => <div className="Card" {...props} />;const Page = () => { return <Card>hello</Card>;};
</code></pre>
<p>However, I have a strong sense that <code>renderCard</code> <em>is not:</em></p>
<pre><code>const renderCard = (props) => <div className="Card" {...props} />;const Page = () => { return renderCard({ children: 'hello' });};
</code></pre>
<p>But why is that? React <a href="https://reactjs.org/docs/components-and-props.html">docs on components</a> are not very helpful — first they say a component is a piece of UI, then that “components are like functions that return JSX”, yet neither explains why <code>renderCard</code> is not a component. If we can solve this paradox, we can also find out what a react component actually is. We’ll examine 4 versions: is React component a…</p>
<ul>
<li>piece of UI?</li>
<li>software decomposition unit?</li>
<li>thing that implements some interface?</li>
<li>unit of update?</li>
</ul>
<p>And, surprise, it’s a bit of all four, really. Let’s dive in!</p>
<blockquote>
<p>Before we begin: “React component is a web component” is so false it does not deserve a section in my article. React components <a href="https://reactjs.org/docs/web-components.html">do not implement</a> anything from the <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">Web Components spec.</a></p>
</blockquote>
<h2>Piece of UI</h2>
<p>The most intuitive answer for anyone with front-end experience (both developers <em>and</em> designers) is that React component is a reusable piece of UI. You look at figma designs, spot recurring fragments, and these are your React components:</p>
<p><img src="https://blog.thoughtspile.tech/images/cmp-decomposition.png?invert" alt="" /></p>
<p>It’s tempting to say that React components implement the concept of UI components. It’s a good first attempt at definition. Still, it leaves some questions unanswered. Our <code>renderCard</code> function is a reusable piece of UI, so it must be a component. This feels wrong. Next, React allows for components that don’t actually own any UI — most <a href="https://reactjs.org/docs/higher-order-components.html">higher-order components</a> and some <a href="https://github.com/downshift-js/downshift">libraries</a> work that way:</p>
<pre><code>// Not a single UI element!const List = (props) => { const [items, setItems] = useState([]); const add = () => setItems([...items, items.length]); return ( <> {items.map(props.renderItem)} {props.renderAdd({ add })} </> );}
</code></pre>
<p>So, UI components and React components are different, even if mostly overlapping, concepts. Still, if you know your way around general UI component decomposition, you’ll mostly do fine in React. But to capture the true essence of React components, we must dig further.</p>
<h2>The architecture answer</h2>
<p>As an <a href="https://www.joelonsoftware.com/2001/04/21/dont-let-architecture-astronauts-scare-you/">architecture cosmonaut,</a> I must now bring up <a href="https://en.wikipedia.org/wiki/Component-based_software_engineering#Software_component">component-based development.</a> It’s just a formalization of my “react thingie” answer, defining a <em>component</em> as as <em>encapsulated set of related functions and data.</em></p>
<p>This is not useless — in a react app, components are similar to <em>classes</em> in object-oriented programming and <em>functions</em> in functional / procedural programming. Your codebase would benefit from applying design principles developed for other techniques, like SOLID — you needn’t throw all the previous best practices away and reinvent them as “react insights”. Component decomposition is your primary tool for managing<a href="https://en.wikipedia.org/wiki/Coupling_(computer_programming)">coupling</a> / <a href="https://en.wikipedia.org/wiki/God_object">god object</a> issues.</p>
<p>Still, as an overly abstract definition, this includes a lot of things that are <em>not</em> react components. Your average React app consists of multiple layers — React view, state manager, API wrapper — and only one of these uses React components. <em>Hooks</em> fit the definition by encapsulating data & functions, yet they’re most certainly <em>not</em> components. <code>renderCard</code> is a nuisance. Next one!</p>
<h2>API contract interface</h2>
<p>For the most boring take, let’s see what React itself considers a component, by looking at <a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/v16/index.d.ts">@types/react</a> and the <a href="https://github.com/facebook/react/blob/13036bfbc8ecbcf4451adb7bde397f438caa8607/packages/react-dom/src/server/ReactPartialRenderer.js">react-dom implementation.</a> Actually, many things work:</p>
<ol>
<li>A function component that accepts props object and returns JSX: <code>(props: P) => ReactElement | null</code></li>
<li>An object returned from <code>React.memo</code>, <code>forwardRef</code>, <code>Context</code> and some other builtins. They are called <a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/v16/index.d.ts#L351">exotic components</a> and trigger <a href="https://github.com/facebook/react/blob/13036bfbc8ecbcf4451adb7bde397f438caa8607/packages/react-dom/src/server/ReactPartialRenderer.js#L1161">special renderer behavior.</a></li>
<li>A class that <code>extends React.Component</code>. Fun fact: since JS classes are functions under the hood, <a href="https://overreacted.io/how-does-react-tell-a-class-from-a-function/">React checks</a> <code>prototype.isReactClass</code> (see <a href="https://github.com/facebook/react/blob/13036bfbc8ecbcf4451adb7bde397f438caa8607/packages/react-dom/src/server/ReactPartialRenderer.js#L286">react-dom</a>) <a href="https://github.com/facebook/react/blob/main/packages/react/src/ReactBaseClasses.js#L28">defined</a> on <code>Component</code> to distinguish classes from functions. This means that technically you can implement a class component without extending <code>Component</code>, which is probably a horrible idea.</li>
<li><em>Module pattern components</em> — functions returning component-like objects, as in <code>Card = () => ({ render: () => <div /> })</code>. Their <a href="https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md#deprecate-module-pattern-components">deprecation notice</a> was probably the first time anyone learnt these existed.</li>
<li><em>Maybe</em> a string, as in <code><div /> -> jsx('div')</code>. According to <a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/v16/index.d.ts#L69">react types</a> it’s an <code>ElementType</code>, not a <code>ComponentType</code>, but I’ve also heard them referred to as “host components”. I frankly don’t care if we call these components, because there’s not much choice about using them anyways.</li>
</ol>
<p>This peek inside the implementation answers at least one of our questions: <em>higher-order component</em> is a misnomer. If a higher-order function is a function that operates on other functions, HOC should be a <em>component</em> that accepts other components as props or returns a component. But the HOC itself is <em>not</em> a component, because it’s a function that does not return vDOM. <em>Component factory</em> would be a more fitting term. What’s done is done, though.</p>
<p>But what about <code>Card</code> vs <code>renderCard</code>? Both are functions that accept an object argument and return a react element, so both should be components. We could duct-tape our definition to force every component to be used properly — as a JSX <code><tag></code>, or directly passed to a JSX runtime (<code>jsx(s) / createElement</code>). But this means that nothing can <em>intrinsically</em> be a component. Try a thought experiment: if I serve soup in a hat, the hat does not stop being a hat, it’s just me acting weird — why then would misusing a react component prevent it from being a component?</p>
<p>If the “software component” answer was too abstract, this one is too concrete. We have a list of requirements any React component must satisfy, but, as seen in <code>renderCard</code>, checking these boxes does not automatically make you a component. Moreover, this definition is very unstable — a change to react core (say, supporting Vue-like <a href="https://v3.vuejs.org/guide/single-file-component.html">single-file components</a>) can easily invalidate it. Let’s try to find some deeper meaning in react components.</p>
<h2>Unit of update</h2>
<p>This brings us to the answer I find most insightful: react component is a unit of update in a react app. <em>Only</em> components can schedule an update by calling <code>useState / useReducer</code> update handle, doing <code>this.setState</code>, or subscribing to context changes. And updating state always re-renders (calls the render function and generates virtual DOM for) whole components. Whether a component uses this ability is irrelevant, only the fact that it <em>can</em> matters.</p>
<p>For example, this component…</p>
<pre><code>const Toggler = () => { const [state, setState] = useState(false); console.log('render'); return <button onClick={() => setState(!state)}>{String(state)}</button>;};
</code></pre>
<p>… can schedule an update when clicked, and this update runs every single line of <code>Toggler</code> — calls <code>useState</code>, logs to console, and creates vDOM for <code>button</code>.</p>
<p>We can connect this definition with our “piece of UI” take. Not all UI is elements — sometimes, the crucial part is <em>behavior.</em> Behavior can be viewed as mapping actions to updates, and that’s exactly what a component with no markup does.</p>
<p>This definition relies on the deeper architecture of React and I don’t think it’s bound to change anytime soon. It has survived the introduction of hooks and concurrent features, and it can incorporate many more changes. It also sets React apart from other frameworks that apply updates on sub-component level, like <a href="https://blog.lacolaco.net/2021/02/angular-ivy-library-compilation-design-in-depth-en/">Angular</a>, <a href="https://dev.to/zev/how-does-svelte-actually-work-part-1-j9m">svelte</a> or <a href="https://www.solidjs.com/guides/comparison#react">solid.</a></p>
<p>Components can also opt out of an update using <code>React.memo</code>, <code>PureComponent</code> or <code>shouldComponentUpdate</code>. You could argue that caching vDOM objects with e.g. <code>useMemo</code> <a href="https://reactjs.org/docs/hooks-faq.html#how-to-memoize-calculations">opts out of an update, too,</a> but that’s not entirely the same, because you’re just providing a hint to the reconciler — all of the render function and the effects still run. More on this some other time.</p>
<p>This explains why hooks <a href="https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions">can only be called as a part of a function component.</a> In functional react, hooks are <em>the</em> way to schedule an update. You can’t use hooks outside a component, because there’s nothing to update. <em>Note:</em> hooks inside other hooks are allowed because, as long as the outermost hook is inside a component, inner ones are inside that component as well.</p>
<p>Hooks themselves are <em>not</em> components, because updating <code>useState</code> in a hook does not just update this particular hook, but the whole component that contains it. Also, a hook can not prevent a running update from happening.</p>
<h3>Component vs render-function</h3>
<p>Now let’s finally tackle <code>Card</code> vs <code>renderCard</code> confusion using our newfound definition. So far, neither <code>Card</code> nor <code>renderCard</code> is trying to update itself.</p>
<pre><code>const Card = (props) => <div className="Card" {...props} />;const renderCard = (props) => <div className="Card" {...props} />;const Page = () => { return <> <Card>I am cool</Card> {renderCard({ children: 'I am a function' })} </>;};
</code></pre>
<p>Let’s change that by adding state into each implementation to paint it red on click:</p>
<pre><code>const [active, setActive] = useState(false);return <div className="Card" style={{ color: active ? 'red' : null }} onClick={() => setActive(!active)} {...props}/>;
</code></pre>
<p>I know, <code>useState</code> inside a <em>regular function</em> like <code>renderCard</code> <a href="https://reactjs.org/docs/hooks-rules.html">is not allowed</a> but remember that we’re yet unsure whether <code>renderCard</code> is a component. So, let’s run our code and <a href="https://codesandbox.io/s/fragrant-platform-7596t?file=/src/App.js:600-617">test updates in both implementations.</a> Aha, we see the difference! When clicked, <code>Card</code> just updates itself, while calling <code>setActive</code> in <code>renderCard</code> leaks up into the <em>real</em> component, <code>Page</code>, and there’s nothing we can do about it inside <code>renderCard</code>. <code>Card</code> is capable of independent updates, but updating <code>renderCard</code> only works because it happens inside another component. That’s why <code>Card</code> is a component, and <code>renderCard</code> is not. Phew!</p>
<hr />
<p>We’ve tried 4 explanations of what a react component actually is. Complete or not, each one gives us new insights into creating better components:</p>
<ol>
<li>React components are pieces of UI. Sensible UI decomposition is a great start for React component structure. But not all React components own some UI elements.</li>
<li>React components are decomposition units of React apps. Applying SOLID or other API design principles to react components is a good practice. But not every single unit of a React app is a component.</li>
<li>React components implement some interface, and React runtime abstracts the different implementations (function / class component / <code>memo</code> & co) away. But why then is <code>{renderCard()}</code> not a component?</li>
<li>React components are a unit of update — they a) can update themselves b) always run render function completely and c) can opt out of a pending update.</li>
</ol>
<p>I’m pretty satisfied with this last answer — it’s React-specific, but describes the <em>intended behavior</em> instead of <em>implementation.</em> It also gives a good reason to prefer smaller components: they’ll update faster, and less frequently. See my post on <a href="https://blog.thoughtspile.tech/2021/10/04/react-context-dangers/?twitter">optimizing context</a> for a concrete example.</p>
Good advice on JSX conditionals2022-01-17T00:00:00Zhttps://thoughtspile.github.io/2022/01/17/jsx-conditionals/<p>Conditional rendering is a cornerstone of any templating language. React / JSX bravely chose not to have a dedicated conditional syntax, like <code>ng-if="condition"</code>, <a href="https://reactjs.org/docs/conditional-rendering.html">relying</a> on JS <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND">boolean operators</a> instead:</p>
<ul>
<li><code>condition && <JSX /></code> renders <code><JSX /></code> iff <code>condition</code> is truthy,</li>
<li><code>condition ? <JsxTrue /> : <JsxFalse /></code> renders <code><JsxTrue /></code> or <code><JsxFalse /></code> depending on the truthiness of <code>condition</code>.</li>
</ul>
<p>Courageous, but not always as intuitive as you’d expect. Time after time I shoot myself in the foot with JSX conditionals. In this article, I look at the trickier corners of JSX conditionals, and share some tips for staying safe:</p>
<ol>
<li>Number <code>0</code> likes to leak into your markup.</li>
<li>Compound conditions with <code>||</code> can surprise you because precedence</li>
<li>Ternaries don’t scale.</li>
<li><code>props.children</code> is not something to use as a condition</li>
<li>How to manage update vs remount in conditionals.</li>
</ol>
<p>If you’re in a hurry, I’ve made a cheat sheet:</p>
<p><img src="https://blog.thoughtspile.tech/images/jsx-conditional-cheatsheet.png?invert" alt="" /></p>
<h2>Beware of zero</h2>
<p>Rendering on <em>numerical</em> condition is a common use case. It’s helpful for rendering a collection only if it’s loaded and non-empty:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token punctuation">{</span>gallery<span class="token punctuation">.</span>length <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Gallery</span></span> <span class="token attr-name">slides</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>gallery<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">}</span></code></pre>
<p>However, if the gallery <em>is</em> empty, we get an annoying <code>0</code> in out DOM instead of nothing. That’s because of the way <code>&&</code> works: a falsy left-hand side (like 0) is returned immediately. In JS, boolean operators do not cast their result to boolean — and for the better, since you don’t want the right-hand JSX to turn into <code>true</code>. React then proceeds to put that 0 into the DOM — unlike <code>false</code>, it’s a valid react node (again, for good — in <code>you have {count} tickets</code> rendering 0 is perfectly expected).</p>
<p>The fix? I have two. Cast the condition to boolean explicitly in any way you like. Now the expression value is <code>false</code>, not <code>0</code>, and <code>false</code> is not rendered:</p>
<pre class="language-jsx"><code class="language-jsx">gallery<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&&</span> jsx<br /><span class="token comment">// or</span><br /><span class="token operator">!</span><span class="token operator">!</span>gallery<span class="token punctuation">.</span>length <span class="token operator">&&</span> jsx<br /><span class="token comment">// or</span><br /><span class="token function">Boolean</span><span class="token punctuation">(</span>gallery<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator">&&</span> jsx</code></pre>
<p>Alternatively, replace <code>&&</code> with a ternary to explicitly provide the falsy value — <code>null</code> works like a charm:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token punctuation">{</span>gallery<span class="token punctuation">.</span>length <span class="token operator">?</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Gallery</span></span> <span class="token attr-name">slides</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>gallery<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">}</span></code></pre>
<h2>Mind the precedence</h2>
<p><em>And</em> (<code>&&</code>) has a higher precedence than <em>or</em> (<code>||</code>) — that’s how <a href="https://en.wikipedia.org/wiki/Boolean_algebra">boolean algebra</a> works. However, this also means that you must be very careful with JSX conditions that contain <code>||</code>. Watch as I try to render an access error for anonymous <em>or</em> restricted users…</p>
<pre class="language-jsx"><code class="language-jsx">user<span class="token punctuation">.</span>anonymous <span class="token operator">||</span> user<span class="token punctuation">.</span>restricted <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>error<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span></code></pre>
<p>… and I screw up! The code above is actually equivalent to:</p>
<pre class="language-jsx"><code class="language-jsx">user<span class="token punctuation">.</span>anonymous <span class="token operator">||</span> <span class="token punctuation">(</span>user<span class="token punctuation">.</span>restricted <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>error<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">)</span></code></pre>
<p>Which is <em>not</em> what I want. For anonymous users, you get <code>true || ...whatever...</code>, which is <code>true</code>, because JS <em>knows</em> the or-expression is true just by looking at the left-hand side and skips (<a href="https://stackoverflow.com/questions/12554578/does-javascript-have-short-circuit-evaluation">short-circuits</a>) the rest. React doesn’t render <code>true</code>, and even if it did, <code>true</code> is not the error message you expect.</p>
<p>As a rule of thumb, parenthesize the condition as soon as you see the OR:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token punctuation">{</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span>anonymous <span class="token operator">||</span> user<span class="token punctuation">.</span>restricted<span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>error<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span></code></pre>
<p>For a more sneaky case, consider this ternary-inside-the-condition:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token punctuation">{</span>user<span class="token punctuation">.</span>registered <span class="token operator">?</span> user<span class="token punctuation">.</span>restricted <span class="token operator">:</span> user<span class="token punctuation">.</span>rateLimited <span class="token operator">&&</span> <br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>error<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span></code></pre>
<p>Parentheses still help, but avoiding ternaries in conditions is a better option — they’re very confusing, because you can’t even read the expression out in English (if the user is registered then if the user is restricted otherwise if the user is rate-limited, please make it stop).</p>
<h2>Don’t get stuck in ternaries</h2>
<p>A ternary is a fine way to switch between <em>two</em> pieces of JSX. Once you go beyond 2 items, the lack of an <code>else if ()</code> turns your logic into a bloody mess real quick:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token punctuation">{</span>isEmoji <span class="token operator">?</span> <br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">EmojiButton</span></span> <span class="token punctuation">/></span></span> <span class="token operator">:</span> <br /> isCoupon <span class="token operator">?</span> <br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">CouponButton</span></span> <span class="token punctuation">/></span></span> <span class="token operator">:</span> <br /> isLoaded <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">ShareButton</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span></code></pre>
<p>Any extra conditions inside a ternary branch, be it a nested ternary or a simple <code>&&</code>, are a red flag. Sometimes, a series of <code>&&</code> blocks works better, at the expense of duplicating some conditions:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token punctuation">{</span>isEmoji <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">EmojiButton</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span><br /><span class="token punctuation">{</span>isCoupon <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">CouponButton</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span><br /><span class="token punctuation">{</span><span class="token operator">!</span>isEmoji <span class="token operator">&&</span> <span class="token operator">!</span>isCoupon <span class="token operator">&&</span> isLoaded <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">ShareButton</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span></code></pre>
<p>Other times, a good old <code>if / else</code> is the way to go. Sure, you can’t inline these in JSX, but you can always extract a function:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">getButton</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>isEmoji<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">EmojiButton</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span> <br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>isCoupon<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">CouponButton</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span> <br /> <span class="token keyword">return</span> isLoaded <span class="token operator">?</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">ShareButton</span></span> <span class="token punctuation">/></span></span> <span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<h2>Don’t conditional on JSX</h2>
<p>In case you’re wondering, react elements passed via props don’t work as a condition. Let me try wrapping the children in a div <em>only</em> if there are children:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Wrap</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>props<span class="token punctuation">.</span>children<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">{</span>props<span class="token punctuation">.</span>children<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>I expect <code>Wrap</code> to render <code>null</code> when there is no content wrapped, but React doesn’t work like that:</p>
<ul>
<li><code>props.children</code> can be an empty array, e.g. <code><Wrap>{[].map(e => <div />)}</Wrap></code></li>
<li><code>children.length</code> fails, too: <code>children</code> can <em>also</em> be a single element, not an array (<code><Wrap><div /></Wrap></code>).</li>
<li><code>React.Children.count(props.children)</code> <a href="https://reactjs.org/docs/react-api.html#reactchildrencount">supports</a> both single and multiple children, but thinks that <code><Wrap>{false && 'hi'}{false && 'there'}</Wrap></code> contains 2 items, while in reality there are none.</li>
<li>Next try: <code>React.Children.toArray(props.children)</code> <a href="https://reactjs.org/docs/react-api.html#reactchildrentoarray">removes invalid nodes,</a> such as <code>false</code>. Sadly, you still get true for an empty fragment: <code><Wrap><></><Wrap></code>.</li>
<li>For the final nail in this coffin, if we move the conditional rendering inside a component: <code><Wrap><Div hide /></Wrap></code> with <code>Div = (p) => p.hide ? null : <div /></code>, we can <em>never</em> know if it’s empty during <code>Wrap</code> render, because react only renders the child <code>Div</code> after the parent, and a stateful child can re-render independently from its parent.</li>
</ul>
<p>For the only sane way to change anything if the interpolated JSX is empty, see <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:empty">CSS <code>:empty</code> pseudo-class.</a></p>
<h2>Remount or update?</h2>
<p>JSX written in separate ternary branches feels like completely independent code. Consider the following:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token punctuation">{</span>hasItem <span class="token operator">?</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Item</span></span> <span class="token attr-name">id</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token operator">:</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Item</span></span> <span class="token attr-name">id</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token number">2</span><span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span></code></pre>
<p>What happens when <code>hasItem</code> changes? Don’t know about you, but my guess would be that <code><Item id={1} /></code> unmounts, then <code><Item id={2} /></code> mounts, because I wrote 2 separate JSX tags. React, however, doesn’t know or care what I wrote, all it sees is the <code>Item</code> element in the same position, so it keeps the mounted instance, updating props (see <a href="https://codesandbox.io/s/still-cherry-o123c?file=/src/App.js">sandbox</a>). The code above is equivalent to <code><Item id={hasItem ? 1 : 2} /></code>.</p>
<blockquote>
<p>When the branches contain different components, as in <code>{hasItem ? <Item1 /> : <Item2 />}</code>, React remounts, because <code>Item1</code> can’t be updated to become <code>Item2</code>.</p>
</blockquote>
<p>The case above just causes some unexpected behavior that’s fine as long as you properly manage updates, and even a bit more optimal than remounting. However, with uncontrolled inputs you’re in for a disaster:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token punctuation">{</span>mode <span class="token operator">===</span> <span class="token string">'name'</span> <br /> <span class="token operator">?</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <br /> <span class="token operator">:</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>phone<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span></code></pre>
<p>Here, if you input something into <em>name</em> input, then switch the mode, your name unexpectedly leaks into the <em>phone</em> input. (again, see <a href="https://codesandbox.io/s/still-cherry-o123c?file=/src/App.js">sandbox</a>) This can cause even more havoc with complex update mechanics relying on previous state.</p>
<p>One workaround here is using the <code>key</code> prop. Normally, we use it for <a href="https://reactjs.org/docs/lists-and-keys.html">rendering lists,</a> but it’s actually an <em>element identity</em> hint for React — elements with the same <code>key</code> are the same logical element.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// remounts on change</span><br /><span class="token punctuation">{</span>mode <span class="token operator">===</span> <span class="token string">'name'</span> <br /> <span class="token operator">?</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span> <span class="token attr-name">key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <br /> <span class="token operator">:</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>phone<span class="token punctuation">"</span></span> <span class="token attr-name">key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>phone<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span></code></pre>
<p>Another option is replacing the ternary with two separate <code>&&</code> blocks. When <code>key</code> is absent, React falls back to the index of the item in <code>children</code> array, so putting distinct elements into distinct positions works just as well as an explicit key:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token punctuation">{</span>mode <span class="token operator">===</span> <span class="token string">'name'</span> <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span><br /><span class="token punctuation">{</span>mode <span class="token operator">!==</span> <span class="token string">'name'</span> <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>phone<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span></code></pre>
<p>Conversely, if you have <em>very different</em> conditional props on the same logical element, you can split the branching into two separate JSX tags for readability with <em>no</em> penalty:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// messy</span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Button</span></span> <br /> <span class="token attr-name">aria-busy</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>loading<span class="token punctuation">}</span></span> <br /> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>loading <span class="token operator">?</span> <span class="token keyword">null</span> <span class="token operator">:</span> submit<span class="token punctuation">}</span></span><br /><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>loading <span class="token operator">?</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Spinner</span></span> <span class="token punctuation">/></span></span> <span class="token operator">:</span> <span class="token string">'submit'</span><span class="token punctuation">}</span><span class="token plain-text"><br /></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Button</span></span><span class="token punctuation">></span></span><br /><span class="token comment">// maybe try:</span><br />loading <br /> <span class="token operator">?</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Button</span></span> <span class="token attr-name">aria-busy</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Spinner</span></span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Button</span></span><span class="token punctuation">></span></span> <br /> <span class="token operator">:</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Button</span></span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>submit<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">submit</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Button</span></span><span class="token punctuation">></span></span><br /><span class="token comment">// or even</span><br /><span class="token punctuation">{</span>loading <span class="token operator">&&</span> <br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Button</span></span> <span class="token attr-name">key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span> <span class="token attr-name">aria-busy</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Spinner</span></span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Button</span></span><span class="token punctuation">></span></span><span class="token punctuation">}</span><br /><span class="token punctuation">{</span><span class="token operator">!</span>loading <span class="token operator">&&</span> <br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Button</span></span> <span class="token attr-name">key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>submit<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">submit</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Button</span></span><span class="token punctuation">></span></span><span class="token punctuation">}</span><br /><span class="token comment">// ^^ bonus: _move_ the element around the markup, no remount</span></code></pre>
<hr />
<p>So, here are my top tips for using JSX conditionals like a boss:</p>
<ul>
<li><code>{number && <JSX />}</code> renders <code>0</code> instead of nothing. Use <code>{number > 0 && <JSX />}</code> instead.</li>
<li>Don’t forget the parentheses around or-conditions: <code>{(cond1 || cond2) && <JSX />}</code></li>
<li>Ternaries don’t scale beyond 2 branches — try an <code>&&</code> block per branch, or extract a function and use <code>if / else</code>.</li>
<li>You can’t tell if <code>props.children</code> (or any interpolated element) actually contains some content — CSS <code>:empty</code> is your best bet.</li>
<li><code>{condition ? <Tag props1 /> : <Tag props2 />}</code> will <em>not</em> remount <code>Tag</code> — use unique <code>key</code> or separate <code>&&</code> branches if you want the remount.</li>
</ul>
Make useRef lazy — 4 ways2021-11-30T00:00:00Zhttps://thoughtspile.github.io/2021/11/30/lazy-useref/<p>I love <code>useRef</code>, but it lacks the lazy initializer functionality found in other hooks (<code>useState</code> / <code>useReducer</code> / <code>useMemo</code>). <code>useRef({ x: 0, y: 0 })</code> creates an object <code>{ x: 0, y: 0 }</code> on every render, but only uses it when mounting — it subsequent renders it's thrown away. With <code>useState</code>, we can replace the initial <em>value</em> with an <em>initializer</em> that's only called on first render — <code>useState(() => ({ x: 0, y: 0 }))</code> (I've explored this and other <code>useState</code> features in my <a href="https://thoughtspile.github.io/2021/09/27/usestate-tricks/">older post</a>). Creating functions is very cheap in modern JS runtimes, so we skip allocating memory and building the object for a slight performance boost (see <a href="https://jsbench.me/p3kwj6ojfs">benchmark</a>).</p>
<p>I'm not super excited about doing useless work, and <code>useRef</code> is your primary tool for <a href="https://thoughtspile.github.io/2021/10/18/non-react-state/">avoiding useless re-renders.</a> In this post, I'll show you four ways to support lazy initializer in <code>useRef</code>:</p>
<ol>
<li>Move initialization to <code>useEffect</code></li>
<li>Sync lazy <code>useRef</code> initializer that works like <code>useState</code> initializer.</li>
<li>Lazy <code>useRef</code> on top of <code>useState</code> (almost zero code!)</li>
<li>A <code>useRef</code> that only computes the value when you read <code>.current</code></li>
</ol>
<p><img src="https://thoughtspile.github.io/images/lazy-useref-sofa.jpg" alt="" /></p>
<h2>Use cases</h2>
<p>Any ref that involves an object can benefit from lazy initialization. I use such refs a lot for <a href="https://thoughtspile.github.io/2021/10/18/non-react-state/">tracking gestures:</a></p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> touch <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">onTouchMove</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> touch<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">x</span><span class="token operator">:</span> e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientX<span class="token punctuation">,</span> <br /> <span class="token literal-property property">y</span><span class="token operator">:</span> e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientY<span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>A lazy initializer is useless for atomic values like <code>useRef(9)</code>, since those are cheap to create, too.</p>
<p>For a slightly different use case, sometimes we want a stateful object (often a Resize/IntersectionObserver) with a stable identity — <code>useMemo</code> <a href="https://reactjs.org/docs/hooks-reference.html#usememo">does not guarantee it.</a> We don't really want to reassign <code>current</code>, so a <code>RefObject</code> API is not needed:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// Would be nice</span><br /><span class="token keyword">const</span> observer <span class="token operator">=</span> <span class="token function">useStableMemo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name">IntersectionObserver</span><span class="token punctuation">(</span>cb<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// Why write observer.current if you never swap an observer?</span><br /><span class="token keyword">const</span> rootRef <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token parameter">e</span> <span class="token operator">=></span> observer<span class="token punctuation">.</span><span class="token function">observe</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span></code></pre>
<p>For each technique, we'll see how good it is at supporting both use cases.</p>
<h2>The async way</h2>
<p>The most intuitive way to lazy-initialize a ref is combining a value-less <code>useRef()</code> with a mount effect:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> ref <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> ref<span class="token punctuation">.</span>current <span class="token operator">=</span> initialValue<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Nicely, init inside an effect does not (<a href="https://thoughtspile.github.io/2021/11/15/unintentional-layout-effect/">normally</a>) block the paint, allowing you to paint a touch faster. However, this implementation is not always convenient, because the <code>.current</code> value is not accessible before the effect — in the first render phase, in DOM refs, <code>useLayoutEffect</code>, and even in some other <code>useEffect</code>s (inside child components and ones scheduled before the <em>init</em> effect) — try it yourself in a <a href="https://codepen.io/thoughtspile/pen/wvrvQjN?editors=0011">codepen</a>. If the whole <code>useRef</code> + <code>useEffect</code> construction is written inline in a component, you at least see that the initialization is delayed. Wrapping it into a custom hook increases the chances of a misuse:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> observer <span class="token operator">=</span> <span class="token function">useLazyRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name">IntersectionObserver</span><span class="token punctuation">(</span><span class="token operator">...</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// spot the bug</span><br /><span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> observer<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">observe</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The logic relying on <code>.current</code> is awkwardly pushed into effects, complicating your code:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span>width<span class="token punctuation">,</span> setWidth<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> node <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> observer <span class="token operator">=</span> <span class="token function">useLazyRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <br /> <span class="token keyword">new</span> <span class="token class-name">ResizeObserver</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">[</span>e<span class="token punctuation">]</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setWidth</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>borderBoxSize<span class="token punctuation">.</span>width<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> observer<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">observe</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>current<span class="token punctuation">)</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>node<span class="token punctuation">}</span></span> <span class="token attr-name">data-width</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>width<span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span></code></pre>
<p>Replacing <code>useEffect</code> with <code>useLayoutEffect</code> does not help much — a bunch of places that can't access the <code>current</code> still exists (first render, DOM refs, child <code>useLayoutEffect</code>s), <em>and</em> now the initialization blocks the paint. As we'll see now, better ways to initialize early exist.</p>
<p>The <code>useEffect</code> approach works OK if you only need <code>.current</code> <em>later</em> — in other effects, timeouts or event handlers (and you're 100% sure those won't fire during the first paint). It's my least favorite approach, because the other ones work better and avoid the "pre-initialization gap".</p>
<h2>The DIY way</h2>
<p>If we want the <code>.current</code> value to be available at all times, but without re-creation on every render (a lot like <code>useState</code> / <code>useMemo</code>), we can just build a custom hook over bare <code>useRef</code> ourselves (see <a href="https://codepen.io/thoughtspile/pen/MWEWzMe?editors=0011">codepen</a>):</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// none is a special value used to detect an uninitialized ref</span><br /><span class="token keyword">const</span> none <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">function</span> <span class="token function">useLazyRef</span><span class="token punctuation">(</span><span class="token parameter">init</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// not initialized yet</span><br /> <span class="token keyword">const</span> ref <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span>none<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// if it's not initialized (1st render)</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>ref<span class="token punctuation">.</span>current <span class="token operator">===</span> none<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// we initialize it</span><br /> ref<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token comment">// new we return the initialized ref</span><br /> <span class="token keyword">return</span> ref<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>This implementation is a good default for custom <code>useLazyRef</code> hooks: it works <em>anywhere</em> — inside render, in effects and layout effects, in listeners, with no chance of misuse, and is similar to the built-in <code>useState</code> and <code>useMemo</code>. To turn it into a readonly ref / stable memo, just return <code>ref.current</code> — it's already initialized before <code>useLazyRef</code> returns.</p>
<blockquote>
<p>Note that using <code>null</code> as the un-initialized value breaks if <code>init()</code> returns <code>null</code>, and setting <code>ref.current = null</code> triggers an accidental re-initialization on next render. <code>Symbol</code> works well and might be more convenient for debugging.</p>
</blockquote>
<p>This is the most convenient approach for storing <code>observers</code>, because they're safe to use from DOM refs:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span>width<span class="token punctuation">,</span> setWidth<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> observer <span class="token operator">=</span> <span class="token function">useLazyRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <br /> <span class="token keyword">new</span> <span class="token class-name">ResizeObserver</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">[</span>e<span class="token punctuation">]</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setWidth</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>borderBoxSize<span class="token punctuation">.</span>width<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /><span class="token keyword">const</span> nodeRef <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> observer<span class="token punctuation">.</span><span class="token function">observe</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>nodeRef<span class="token punctuation">}</span></span> <span class="token attr-name">data-width</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>width<span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span></code></pre>
<p>The only downside is that the initializer runs even if we never read the value. I'll show you how to avoid this, but first let's see how we can (and can't) build <em>this</em> flavor of lazy <code>useRef</code> over other hooks.</p>
<h2>The resourceful way</h2>
<p>If <code>useState</code> has the lazy initializer feature we want, why not just use it instead of writing custom code (<a href="https://codepen.io/thoughtspile/pen/eYGYbYg?editors=0011">codepen</a>)?</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> ref <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">current</span><span class="token operator">:</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre>
<p>We <code>useState</code> with a lazy initializer that mimics the shape of a RefObject, and throw away the update handle because we'll never use it — ref identity must be stable. For readonly ref / stable-memo we can skip the <code>{ current }</code> trick and just <code>useState(init)[0]</code>. Storing a mutable object in <code>useState</code> is not the most orthodox thing to do, but it works pretty well here. I imagine that at some point future react <em>might</em> choose to rebuild the current <code>useState</code> by re-initializing and re-applying all the updates (e.g. for HMR), but I haven't heard of such plans, and this will break a lot of stuff.</p>
<p>As usual, anything doable with <code>useState</code> can also be done with <code>useReducer</code>, but it's slightly more complicated:</p>
<pre class="language-js"><code class="language-js"><span class="token function">useReducer</span><span class="token punctuation">(</span><br /> <span class="token comment">// any reducer works, it never runs anyways</span><br /> <span class="token parameter">v</span> <span class="token operator">=></span> v<span class="token punctuation">,</span> <br /> <span class="token comment">// () => {} and () => 9 work just as well</span><br /> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">current</span><span class="token operator">:</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token comment">// And here's the stable memo:</span><br /><span class="token function">useReducer</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> v<span class="token punctuation">,</span> init<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre>
<p>The most obvious base hook, <code>useMemo</code>, doesn't work well. <code>useMemo(() => ({ current: init() }), [])</code> currently returns a stable object, but React docs <a href="https://reactjs.org/docs/hooks-reference.html#usememo">warn against relying</a> on this, since a future React version might re-initialize the value when it feels like it. If you're OK with that, you didn't need <code>ref</code> in the first place.</p>
<p><code>useImperativeHandle</code> is not recommended, too — it <a href="https://reactjs.org/docs/hooks-reference.html#useimperativehandle">has something to do with refs,</a> but its <a href="https://github.com/facebook/react/blob/82c8fa90be86fc0afcbff2dc39486579cff1ac9a/packages/react-reconciler/src/ReactFiberHooks.new.js#L1777">implemented</a> to set the value in a layout effect, similar to the worst one of our <code>async</code> options. Also, it</p>
<p>So, <code>useState</code> allows you to build a <em>lazy ref</em> with almost zero code, at a minor risk of breaking in a future react version. Choosing between this and a DIY lazy ref is up to you, they work the same.</p>
<h2>The really lazy way</h2>
<p>I'd argue that what we've discussed so far isn't really <em>lazy</em> — sure, you avoid useless job on re-render, but you still eagerly compute the initial value on first render. What if we only computed the value on demand, when someone reads <code>.current</code>?</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> none <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">function</span> <span class="token function">useJitRef</span><span class="token punctuation">(</span><span class="token parameter">init</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> value <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span>none<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> ref <span class="token operator">=</span> <span class="token function">useLazyRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token keyword">get</span> <span class="token function">current</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>value<span class="token punctuation">.</span>current <span class="token operator">===</span> none<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> value<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">return</span> value<span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token keyword">set</span> <span class="token function">current</span><span class="token punctuation">(</span><span class="token parameter">v</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> value<span class="token punctuation">.</span>current <span class="token operator">=</span> v<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> ref<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Tricky! See <a href="https://codepen.io/thoughtspile/pen/YzrzdyJ?editors=0011">codepen</a>, and let me break it down for you:</p>
<ul>
<li>Wrap the bare ref with a get / set interceptor</li>
<li>Reading <code>current</code> goes through the <code>get()</code>, computing the value on first read and returning the cached value later.</li>
<li>Assigning <code>current</code> updates the value instantly and removes the need to initialize.</li>
<li>The wrapper object is a <code>useLazyRef</code> itself to preserve the builtin <code>useRef</code> guarantee of stable identity and avoid extra object creation.</li>
</ul>
<p>For readonly ref / stable memo, try the simpler <em>getter function</em> approach <a href="https://reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily">suggested in react docs:</a></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> none <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">function</span> <span class="token function">useMemoGet</span><span class="token punctuation">(</span><span class="token parameter">init</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> value <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span>none<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>value<span class="token punctuation">.</span>current <span class="token operator">===</span> none<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> value<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">return</span> value<span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Is it worth the trouble? Maybe, maybe not. The code is more complicated than the eager <code>useLazyRef</code>. If the initializer is <em>really</em> heavy, and you use the value conditionally, and you often end up not needing it, sure, it's a good fit. Honestly, I have yet to see a use case that fits these conditions.</p>
<p>This is a very interesting and flexible technique that supports many variations:</p>
<ul>
<li>Pre-compute the value, e.g. in <code>requestIdleCallback(() => ref.current)</code></li>
<li>Allow for lazy updates — don't set the explicit value, but provide a new way to compute it: <code>ref.current = () => el.clientWidth</code></li>
<li>Replace <em>updating</em> with <em>invalidation</em> — say, with <code>getWidth = useMemoGet(() => el.clientWidth)</code> you can mark the cached value as stale with <code>getWidth.invalidate()</code> on content change.</li>
</ul>
<hr />
<p>We've covered 4 good base techniques (<code>useState</code> is an alternative implementation of ) for creating lazy useRef. They all have different characteristics that make them useful for different problems:</p>
<ul>
<li>Initialize in <code>useEffect</code> — not recommended because it's easy to hit un-initialized <code>.current</code>.</li>
<li>Sync custom-built <code>useRef</code> works well, but blocks first render. Good enough for most cases.</li>
<li>Putting the value into <code>useState</code>'s initializer, but hiding the update handle. Least code, but a chance of breaking in future react versions.</li>
<li>On-demand <code>useRef</code> that only computes the value when you read <code>.current</code> — complicated, but flexible and never computes values you don't use.</li>
</ul>
<p>Hope you find this useful! If you want to learn more about react, check out my <a href="https://thoughtspile.github.io/">other posts.</a></p>
Open source starter pack for JS devs2021-11-20T00:00:00Zhttps://thoughtspile.github.io/2021/11/20/open-source-starter/<p>So you've decided to open-source your project. Amazing! Bad news first: writing code is only the beginning. The information for library authors on the web is surprisingly fragmented, so I've decided to put together a list of things to keep in mind when open-sourcing a JS library:</p>
<ul>
<li>Decent docs, an OSS license, TypeScript definitions and a changelog are a must if you want anyone to use your library.</li>
<li>Build setup of a library is different from that of an app — but even simpler, if you know what to do.</li>
<li><code>peerDependenies</code> are a thing (also, npm / yarn version resolution is a nightmare).</li>
</ul>
<p>I'll give you enough info on each point to get started without going too deep.</p>
<p>I assume you're comfortable with <a href="https://babeljs.io/">babel</a> and <a href="https://webpack.js.org/">webpack</a> (or any other build toolchain), and have followed some <a href="https://www.digitalocean.com/community/tutorials/workflow-publishing-first-package-to-npm">basic tutorial</a> on publishing an npm package. I also suggest you host your code on GitHub — it has a ton of features useful for OSS, and any other choice is just <em>bizarre</em> in 2021. Let's pick up where you've written some code, pushed it to a public github repo, set up your <code>package.json</code> with <code>name</code>, <code>version</code> and <code>main</code>, and got <code>npm publish</code> to pass. There's still a bumpy ride ahead.</p>
<p><img src="https://thoughtspile.github.io/images/oss-scream.jpg" alt="" /></p>
<h2>Documentation</h2>
<p>Your library will be used by people who have no idea what it's supposed to do, and how it works. For a small project, don't block yourself into building a fancy docs website — a <code>readme.md</code> file in the repo root is enough. Writing the actual docs is a better way to spend your time. Make sure to include:</p>
<ul>
<li>What problem does your project solve? Does your library do something I need?</li>
<li>How to install it? Even if it's <code>npm i my-project</code>, don't make me guess the package name.</li>
<li>A basic usage example. Just something to copy-paste and get started with it.</li>
<li>Full API docs. What can the library do? Does it cover all my use-cases? What arguments do I pass where? I shouldn't have to read the source to answer these questions.</li>
</ul>
<p>See <a href="https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax">markdown cheatsheet</a> if you're not fluent yet.</p>
<h2>License</h2>
<p><em>(I'm not a lawyer, but this is my understanding).</em> A project without an explicit license is closed source by default — the users can look at the code, but can't legally use it. <a href="https://choosealicense.com/">Choose an open-source license,</a> copy the text into a <code>LICENSE</code> file to your repo, put the name in the <code>license</code> field of <code>package.json</code>, and probably mention it in the readme. <a href="https://mit-license.org/">MIT license</a> is a good choice if you just want everyone to use your code and don't have a strong opinion on <em>everything must be open-source.</em></p>
<h2>Package structure</h2>
<p>When building an app, you have some transpiler + bundler (probably babel + webpack, but anything goes) setup to turn your source into someting that runs in a browser. If you don't want to make your users jump around patching webpack config, you need <em>some</em> of that, too. Exactly <em>how</em> you should package your code is a complex topic, but let me scratch the surface for you. TLDR:</p>
<ul>
<li>If you use extended JS (JSX / TS / whatever), or want to support older runtimes with zero setup, do a <code>babel</code> (or friends) pass.</li>
<li>Have a ES-module build for tree-shaking, and a legacy CommonJS build.</li>
<li>Be clear about supported browsers / node versions. Too much = bloat, too little = broken apps for users who don't do extra setup (they won't).</li>
<li>Never use global polyfills, and prefer well-supported APIs when possible.</li>
<li>Don't bother bundling.</li>
</ul>
<p>Just a touch deeper:</p>
<h3>Transpiling</h3>
<p>The code you ship <em>must</em> be standard ES to "just work" for your users. Convert JSX / TypeScript / Vue SFC / other fancy syntax down to JS with <code>babel</code>, <code>tsc</code>, or whatever <code>esbuild</code> you enjoy, and point <code>main</code> to the built version instead of your raw source.</p>
<p>The exact ES target (ES2020 / 6 / 5) is up to you — pick a browser / node target and stick to it. Your users <em>can</em> transpile further down, if they're determined, but undoing an unnecessary transform is next to impossible — it becomes bloat in the final app. Also, some babel transforms (like <a href="https://babeljs.io/docs/en/babel-plugin-transform-unicode-regex">unicode regex</a>) are <em>very</em> verbose — have a look at the generated code once in a while.</p>
<p>The most important question is what to do with <code>import / export</code>. Read on.</p>
<h3>Tree-shaking</h3>
<p>If your library runs in a browser, and it does more than <em>one</em> thing, you'd better support <a href="https://webpack.js.org/guides/tree-shaking/">tree shaking</a> — otherwise, every app using your library will ship useless dead code to the end users' browsers. To get started, create a modular build:</p>
<ul>
<li>Set <code>modules: false</code> in <a href="https://babeljs.io/docs/en/babel-preset-env#modules"><code>@babel/preset-env</code> config</a> to preserve <code>import / export</code></li>
<li>Add <code>"sideEffects": false</code> or <a href="https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free">list your side-effect modules</a> explicitly in <code>package.json</code>.</li>
<li>Point the <a href="https://webpack.js.org/guides/author-libraries/#final-steps">non-standard <code>module</code></a> field of <code>package.json</code> to the resulting entrypoint.</li>
</ul>
<p>Now a module-capable bundler can pick it up and remove unused code from the final bundle. Cool.</p>
<p>However, node <= 12 does not support import / export syntax <em>without a flag</em>. I'm lost in all the <a href="https://nodejs.org/api/esm.html">new <code>"type": "module"</code> / <code>".mjs"</code></a>, but shipping a <a href="https://web.dev/publish-modern-javascript/#modern-with-legacy-fallback">fallback CommonJS build</a> works fine in all node versions and older bundlers. For a library that works both client- and server-side, keep a CommonJS (<code>exports / require</code>) version (generated with a <a href="https://babeljs.io/docs/en/babel-preset-env#modules">pass of babel</a> with <code>modules: 'commonjs'</code>), referenced in <code>main</code> field of <code>package.json</code>.</p>
<p>There's more to tree shaking — some patterns are not tree-shakable, it affects your API choices, complex topic. We'll save for it later — supporting basic tree-shaking with ES modules is always a good start.</p>
<h3>Polyfills</h3>
<p>Should your library include the polyfills for recent browser APIs you use? It's a <a href="https://github.com/w3ctag/polyfills/issues/6">surprisingly debatable issue</a> among OSS authors. Problem, short version: in most app setups, <code>babel</code> doesn't process <code>node_modules</code>, possibly breaking the final app in older browsers. <em>But</em> if you include a polyfill, removing it is from an app that only targets modern browsers is super hard. Also, the final bundle is likely to contain duplicate polyfills of one API.</p>
<p>I'll stick with <a href="https://github.com/w3ctag/polyfills/issues/6#issuecomment-272222513">Rich</a> <a href="https://github.com/w3ctag/polyfills/issues/6#issuecomment-272651240">Harris</a> on this one:</p>
<ul>
<li><em>Never</em> include global polyfills that patch <code>BuiltIn.prototype</code> or <code>window</code> — they can clash with other global polyfills, and are not tree-shakable.</li>
<li>Use helper functions (aka <a href="https://ponyfill.com/"><em>ponyfills</em></a>) like <code>export function startsWith</code> if for code reuse.</li>
<li>Prefer well-supported APIs if possible — using <code>str.indexOf(...) === 0</code> instead of <code>startsWith</code> is not <em>that</em> hard.</li>
<li>Clearly say what targets you support in the readme. Don't pretend to support IE11 if you're not very serious. Maybe provide instructions on setting up <code>babel-loader</code> to process your library.</li>
</ul>
<h3>Bundling</h3>
<p>You <em>could</em> bundle your code, but I think it creates more problems than it solves at the start. How to exclude your dependencies from the bundle? What <code>libraryTarget</code> do you need? Are we sure bundling does not accidentally create non-tree-shakable logic? I'd stick with babel CLI and ship the code as separate JS files: <code>npx babel src --out-dir dist</code>.</p>
<h2>Typings</h2>
<p>TypeScript is a major player in the JS ecosystem these days. Libraries without TS types explode in TS projects with <code>Could not find a declaration file for module '...'</code>, forcing users to either <code>@ts-ignore</code> it or slap together custom ambient declarations. Some lazier developers will probably move to the next library, and I won't blame them.</p>
<p><img src="https://thoughtspile.github.io/images/no-dts.png" alt="" /></p>
<p>Shipping TS types is actually easy: if you write in TypeScript, <code>tsc --declaration</code> (<code>--declarationOnly</code> if you build with babel, see <a href="https://www.typescriptlang.org/tsconfig#declaration">docs</a>) into your build folder. If you write pure JS, it's even easier — just write a custom <code>index.d.ts</code> file describing your library, and copy it to build folder. Now point <code>types</code> field in <code>package.json</code> to the declaration entry point, and you're all set! Don't worry about <code>@types/*</code> pacakges for now. See full <a href="https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#including-declarations-in-your-npm-package">TS docs on publishing</a> if you have any trouble.</p>
<p>I don't know much about Flow, and no one ever asked me to support it, but if you're a fan, see <a href="https://stackoverflow.com/questions/61889988/exporting-my-own-flow-type-with-npm-package">SO tips on doing that.</a></p>
<h2>Understand dependencies vs dev/peerDependencies</h2>
<p>In app development <code>dependencies</code> (<code>npm i</code>) vs <code>devDependenices</code> (<code>npm i --dev</code>) is not a real issue — sure, <code>dev</code> is for your build pipeline, <code>dependencies</code> for real runtime libraries, but it mostly works fine if you mess up. The difference is critical for libraries, though:</p>
<ul>
<li><code>devDependencies</code> are not installed after <code>npm i your-lib</code>.</li>
<li><code>dependencies</code> are automatically installed along with your package. If the user (or some other package) requests a different version of the same dependency, they may get duplicated, but at least it usually works.</li>
<li><code>peerDependencies</code> allow you to reference a package explicitly installed by your users. This ensures the dependency instance is shared between user code and your library, which is crucial for <em>plugins</em> — react components, express middlewares, etc. In effect, this forces a single dependency version per app — your users can't upgrade to react 18 until you support it. Assume the users have to install peers manually — sure, npm 7+ installs <a href="https://github.blog/2021-02-02-npm-7-is-now-generally-available/#peer-dependencies">them automatically</a>, but <a href="https://github.com/yarnpkg/yarn/issues/1503">yarn</a> and <a href="https://github.com/pnpm/pnpm/issues/827">pnpm</a> don't. People hate manually installing stuff they don't care about to get the project to build, so don't overuse peers.</li>
<li><a href="https://docs.npmjs.com/cli/v8/configuring-npm/package-json#bundleddependencies">bundledDependencies</a> and <a href="https://docs.npmjs.com/cli/v8/configuring-npm/package-json#optionaldependencies">optionalDependencies?</a> You don't need them.</li>
</ul>
<p>Basic guideline: all the runtime dependencies go into <code>dependencies</code>. <em>Plugins</em> should put the main library into <code>peerDependencies</code>.</p>
<h2>Changelog</h2>
<p>I install your library, and I'm happy with it. Some time later, it's friday evening and I can't get real job done any more. I decide to update my dependencies, and discover that your library moved from <code>1.0.3</code> to <code>2.3.0</code>.</p>
<ul>
<li>Have you fixed some important bugs, so that I need to update <strong>right now</strong>?</li>
<li>Have you added new features I might enjoy?</li>
<li>What's the breaking change in v2, and how do I update?</li>
</ul>
<p>To answer these questions, I'd love to see a changelog saying what changed in every version since <code>1.0.3</code>. Both <a href="https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases">GH releases</a> and a <code>CHANGELOG.md</code> in the root work fine. Otherwise, I'll have to read the commit / PR list, which is likely to make me very sad.</p>
<hr />
<p>So, here's my list of stuff to keep in mind when publishing a JS library:</p>
<p>Must have:</p>
<ol>
<li>A readme with a problem statement, installation command, hello world example, and full API docs.</li>
<li>A license: full text in project root, name in the readme and in <code>package.json</code></li>
<li>TS typings (unless it's a CLI tool).</li>
<li>Changelog in GH releases or a <code>changelog.md</code>.</li>
</ol>
<p>Managing dependencies:</p>
<ol>
<li>Runtime <code>dependencies</code> go into <code>package.json</code></li>
<li><em>Plugins</em> make the <em>master library</em> a <code>peerDependency</code></li>
</ol>
<p>Build setup:</p>
<ol>
<li>Transpile the code to standard JS.</li>
<li>Running in browsers? Ship es-modules entrypoint in <code>package.json</code> <code>modules</code> for tree shaking.</li>
<li>Running on node? Ship CommonJS entrypoint in <code>main</code>.</li>
<li>Avoid global polyfills.</li>
<li>Don't bundle.</li>
</ol>
useEffect sometimes fires before paint2021-11-15T00:00:00Zhttps://thoughtspile.github.io/2021/11/15/unintentional-layout-effect/<p><code>useEffect</code> should run after paint to prevent blocking the update. But did you know it's not really <em>guaranteed</em> to fire after paint? Updating state in <code>useLayoutEffect</code> makes every <code>useEffect</code> from the same render run <em>before</em> paint, effectively turning them into layout effects. Confusing? Let me explain.</p>
<p>In a normal flow, react updates go like this:</p>
<ol>
<li>React stuff: render virtual DOM, schedule effects, update real DOM</li>
<li>Call <code>useLayoutEffect</code></li>
<li>React releases control, browser paints the new DOM</li>
<li>Call <code>useEffect</code></li>
</ol>
<p><a href="https://reactjs.org/docs/hooks-reference.html#useeffect">React docs</a> don't say when, exactly, useEffect fires — it happens, quote, <strong>after layout and paint, during a deferred event.</strong> I always assumed it was a <code>setTimeout(effect, 3)</code>, but it <a href="https://stackoverflow.com/a/56727837">appears to use</a> a <code>MessageChannel</code> trick, which is neat.</p>
<p>There is, however, a more interesting passage in the docs:</p>
<blockquote>
<p>Although useEffect is deferred until after the browser has painted, it’s guaranteed to fire before any new renders. React will always flush a previous render’s effects before starting a new update.</p>
</blockquote>
<p>This is a good guarantee — you can be sure no updates are missed. But it also implies that sometimes the effect fires before paint. If <em>a)</em> effects are flushed before a new update starts, and <em>b)</em> an update can start <em>before</em> paint, e.g. when triggered from <code>useLayoutEffect</code>, <em>then</em> the effect must be flushed <em>before</em> that update, which is <em>before</em> paint. Here's a timeline:</p>
<p><img src="https://thoughtspile.github.io/images/forced-le-flush-chart.png?invert" alt="" /></p>
<ol>
<li>React update 1: render virtual DOM, schedule effects, update DOM</li>
<li>Call <code>useLayoutEffect</code></li>
<li><strong>Update state,</strong> schedule re-render</li>
<li><strong>Call <code>useEffect</code></strong></li>
<li>React update 2</li>
<li>Call <code>useLayoutEffect</code> from update 2</li>
<li>React releases control, browser <strong>paints</strong> the new DOM</li>
<li>Call <code>useEffect</code> from update 2</li>
</ol>
<p>This is not a very rare situation — you can't really update state in <code>useEffect</code>, because updating state updates the DOM, and doing so after paint leaves the user with one stale frame, <a href="https://blog.logrocket.com/useeffect-vs-uselayouteffect/">resulting in noticeable flickering.</a></p>
<p>For example, let's build a responsive input (like a fake <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries">CSS container query</a>) that only renders the clear button if the input is wider than <code>300px</code>. We need real DOM to measure the input, so we need some effect. We also don't want the icon to appear / disappear after one frame, so the initial measurement goes into <code>useLayoutEffect</code>:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">ResponsiveInput</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> onClear<span class="token punctuation">,</span> <span class="token operator">...</span>props <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> el <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>w<span class="token punctuation">,</span> setW<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">measure</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setW</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span>current<span class="token punctuation">.</span>offsetWidth<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">measure</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// don't take this too seriously, say it's a ResizeObserver</span><br /> window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"resize"</span><span class="token punctuation">,</span> measure<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> window<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">"resize"</span><span class="token punctuation">,</span> measure<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>label</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>el<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>w <span class="token operator">></span> <span class="token number">200</span> <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onClear<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">clear</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>label</span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>We've tried to delay <code>addEventListener</code> until after paint with <code>useEffect</code>, but the state update in <code>useLayoutEffect</code> forces it to happen before paint <a href="https://codesandbox.io/s/infallible-wildflower-127lv?file=/src/App.js:294-408">(see sandbox):</a></p>
<p><img src="https://thoughtspile.github.io/images/le-flush-paint.png?invert" alt="" /></p>
<p><code>useLayoutEffect</code> is not the only place where updating forces an early effect flush — host refs (<code><div ref={HERE}></code>), <code>requestAnimationFrame</code> loops, and microtasks scheduled from uLE trigger the same behavior.</p>
<p>Fine, this is not the end of the world — under some circumstances, your render flow is less optimal than it could be, who cares. Still, it's useful to know the limitations of your tool. Here are 4 practical lessons to learn:</p>
<h3>Don't rely on useEffect to fire after update</h3>
<p>Even if you know the catch, it's very hard to make sure some <code>useEffect</code> is not affected by <code>useLayoutEffect</code> state update:</p>
<ol>
<li>My component doesn't <code>useLayoutEffect</code>. But are you sure none of the custom hooks (e.g. <code>usePopper</code>) do that?</li>
<li>My component only uses built-in React hooks. But a uLE state update up the tree can leak through <code>useContext</code> or a parent re-render.</li>
<li>My component only has <code>useEffect</code>, and a <code>memo()</code>. But effects from an update <a href="https://github.com/facebook/react/blob/4ff5f5719b348d9d8db14aaa49a48532defb4ab7/packages/react-reconciler/src/ReactFiberWorkLoop.new.js#L769">are flushed globally</a>, so a pre-paint update in other components still flushes child effects.</li>
</ol>
<p>With a lot of discipline you probably can have a codebase with <em>no</em> state updates in <code>useLayoutEffect</code>, but that's superhuman. The best advice is not to rely on <code>useEffect</code> to fire after paint, just like <code>useMemo</code> does not guarantee 100% stable reference. If you <em>want</em> the user to see something painted for one frame, <code>useEffect</code> is not the way to do it — try double <code>requestAnimationFrame</code> or do the postMessage trick yourself.</p>
<p>Conversely, suppose you don't listen to the good advice from React team and update DOM in <code>useEffect</code>. You test it, and, <em>aha!</em>, no flickering. Bad news — maybe it's the result of a state update before paint. Move some code around, and it <em>will</em> flicker.</p>
<h3>Don't waste your time splitting layout effects</h3>
<p>Following <code>useEffect</code> vs <code>useLayoutEffect</code> guidelines to the letter, we could split one logical side-effect into a layout effect to update the DOM, and a "delayed" effect, like we've done in our <code>ResponsiveInput</code> example:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// DOM update = layout effect</span><br /><span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setWidth</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span>current<span class="token punctuation">.</span>offsetWidth<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// subscription = lazy logic</span><br /><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'resize'</span><span class="token punctuation">,</span> measure<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> window<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'resize'</span><span class="token punctuation">,</span> measure<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>However, as we now know, this does nothing — both effects are flushed before render. Besides, the separation is <em>sloppy</em> — if we pretend <code>useEffect</code> does fire after paint, are you 100% sure the element won't resize between the effects? I'm not. Leaving all size-tracking logic in a single <code>layoutEffect</code> here is safer, cleaner, has the same amount of pre-paint work, and gives React one less effect to manage — pure win:</p>
<pre class="language-js"><code class="language-js"><span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setWidth</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span>current<span class="token punctuation">.</span>offsetWidth<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'resize'</span><span class="token punctuation">,</span> measure<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> window<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'resize'</span><span class="token punctuation">,</span> measure<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h3>Don't update state in useLayoutEffect</h3>
<p>Good advice, but easier said than done — <code>useEffect</code> is a worse place to update state, because flickering is poor UX, and UX is more important than performance. Updating state during render looks dangerous.</p>
<p>Sometimes state can be safely replaced with useRef. Updating a ref doen't trigger an update, and the effect can run as intended. I happen to have <a href="https://blog.thoughtspile.tech/2021/10/18/non-react-state/">a post exploring some of these cases.</a></p>
<p>If you can, try to come up with a <a href="https://blog.thoughtspile.tech/2021/09/21/useeffect-derived-state/">state model that doesn't rely on effects,</a> but I don't know how to invent "good" state models on command.</p>
<h3>Bypass state update</h3>
<p>If you find particular <code>useLayoutEffect</code> causing trouble, consider bypassing state update and mutating DOM directly. That way, react doesn't schedule an update, and needn't flush effects eagerly. We could try:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> clearRef <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">measure</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// No worries react, I'll handle it:</span><br /> clearRef<span class="token punctuation">.</span>current<span class="token punctuation">.</span>display <span class="token operator">=</span> el<span class="token punctuation">.</span>current<span class="token punctuation">.</span>offsetWidth <span class="token operator">></span> <span class="token number">200</span> <span class="token operator">?</span> <span class="token keyword">null</span> <span class="token operator">:</span> none<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">measure</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"resize"</span><span class="token punctuation">,</span> measure<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> window<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">"resize"</span><span class="token punctuation">,</span> measure<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>label</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>el<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>clearRef<span class="token punctuation">}</span></span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onClear<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">clear</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>label</span><span class="token punctuation">></span></span><br /><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>I've explored this technique in my <a href="https://blog.thoughtspile.tech/2021/10/18/non-react-state/">older post on avoiding <code>useState</code></a>, and we just got one more reason to skip react updates. Still, manually managing DOM updates is complicated and error-prone, so reserve this trick for performance-critical situations — very hot components or super-heavy useEffects.</p>
<hr />
<p>Today we've discovered that <code>useEffect</code> sometimes executes before paint. A frequent cause is updating state in <code>useLayoutEffect</code> — it requests a re-render <em>before</em> paint, and the effect must run before that re-render. This also happens when updating state from RAFs or microtasks. What this means for us:</p>
<ol>
<li>Updating state in <code>useLayoutEffect</code> is not good for app performance. Try not to do that, but sometimes there is no good alternative.</li>
<li>Don't rely on <code>useEffect</code> to fire after paint.</li>
<li>Updating DOM from <code>useEffect</code> <em>will</em> cause a visible flicker — maybe you don't see it because of a layout effect updating state.</li>
<li>Extracting a part of <code>useLayoutEffect</code> into <code>useEffect</code> for <em>performance</em> makes no sense if you set state in the layout effect part.</li>
<li>One more reason to mutate the DOM from uLE manually in performance-critical cases.</li>
</ol>
SemVer: The Tricky Parts2021-11-08T00:00:00Zhttps://thoughtspile.github.io/2021/11/08/semver-challenges/<p>Semantic versioning, is the way to version packages in JS ecosystem. I always thought I understood semver, but that illusion disappeared once I started maintaining libraries myself. Semver has tricky edge cases where it's unclear what the new version number should be:</p>
<ul>
<li>Should you bump anything after a refactoring? Can you have a refactor-only release at all?</li>
<li>What's the new version after updating a dependency? (spoiler: it <em>depends</em>)</li>
<li>Is dropping IE11 support a minor or major?</li>
<li>Is fixing a bug always a patch-level change? (Spoiler: no)</li>
<li>Does rewording "support multi-line content in button" to "fix multi-line button" turn a minor into a patch?</li>
<li>What if a bug can't be fixed without a breaking change?</li>
</ul>
<p>In this post, I'll explore these problems in depth, and share my tips on handling them.</p>
<p><img src="https://thoughtspile.github.io/images/semver.jpg" alt="" /></p>
<h2>A quick intro to SemVer</h2>
<p>A Semanic Version, or semver, has a format of <code>major.minor.patch-(maybe) prerelease</code> — three numbers and some gibberish after a dash that we'll ignore for today. As <a href="https://semver.org/">the semver spec</a> explains it:</p>
<ul>
<li>MAJOR makes incompatible API changes,</li>
<li>MINOR adds functionality in a backwards compatible manner, and</li>
<li>PATCH makes backwards compatible bug fixes.</li>
</ul>
<p>The trick is, SemVer talks about the public API of your package, and the concept of <em>API</em> is a bit fuzzy, so it's not really as strict as you'd expect.</p>
<p>In product front-end development, life is simple. Your product has no public API, no other code depends on it, so you don't really care. Three-number semver format is still useful, since many node tools support it, but you can do whatever you like with the numbers. Using a single number, incrementing it on every build, is just fine: <code>0.0.123 -> 0.0.124</code>, why not. Classic git flow works well with two numbers: minor for releases, patch for hotfixes: <code>1.1.0 -> 1.2.0</code>, then <code>1.2.1</code> if you fix a bug. You can also increment the major version to congratulate yourself on a particularly big feature: <code>1.2.0 -> 2.0.0</code> = <em>well done, Vladimir.</em> Really, anything works.</p>
<p>Once your code becomes a library (and I expect this to happen more often as micro-frontends grow), you need a way to communicate the API compatibility of your new releases to consumers. You need real semver, and you have two conflicting goals. First, you must follow the <em>semantic</em> part of semver to tell the consumers if they can safely update. This also helps package managers decide if a particular version can be reused between several consumers, or must be duplicated. <em>But</em> you also want to increment the version as slowly as possible — frequent breaking changes and even large minor increments are scary for your consumers, and may lead to duplicate versions of your library in the final app.</p>
<h2>SemVer no-ops</h2>
<p>Sometimes you haven't really done anything visible from the outside, but still want to release. Refactorings, performance improvements, documentation changes fall in this category. In all these cases, I usually go with a patch update, because:</p>
<ul>
<li>Once a versioned package has been released, the contents of that version MUST NOT be modified. Any modifications MUST be released as a new version — <a href="https://semver.org/#spec-item-3">semver spec says so.</a></li>
<li>It's hard to re-release a version with the same number anyways.</li>
<li>It provides a way to identify the version if you created some new bugs.</li>
</ul>
<p>On the other hand, <a href="https://semver.org/#spec-item-7">spec p. 7</a> allows you to bump minor for <em>"substantial new functionality or improvements are introduced within the private code"</em> but come figure what <em>substantial</em> means. Anyways, see an <a href="https://github.com/semver/semver/issues/146">official discussion</a>.</p>
<h2>Changelog</h2>
<p>SemVer is useless without a changelog: have a breaking change? Amazing, but what is it and what should your users do about it? Good places to maintain the changelog are <a href="https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository">GitHub releases,</a>, <code>CHANGELOG.md</code>, confluence / dropbox paper / whatever for internal projects, a dedicated page in the docs, or even a pinned message in the support chat. Just make sure all your users know where to look for it.</p>
<h2>Releases with multiple changes</h2>
<p>This one is clear, but keep an eye out: if you release changes in batches, the new version must be the largest of versions from each change. Some examples of a release after <code>1.2.3</code>:</p>
<ul>
<li>3 bug fixes = patch, <code>1.2.4</code></li>
<li>3 bug fixes + 1 feature = minor, <code>1.3.0</code></li>
<li>3 bug fixes + 1 breaking change = major, <code>2.0.0</code></li>
<li>1 feature + 1 breaking change = major, <code>2.0.0</code></li>
</ul>
<p>If you have a patch release planned, but add a feature to it, don't forget to change it to a minor release, etc.</p>
<h2>Breaking bug fixes</h2>
<p>Say you release a buggy <code>1.2.3</code> — a dropdown component calls <code>onClose</code> on open. Strictly speaking, if you now stop calling <code>onClose</code> on open, you must release <code>2.0.0</code>, because it's a breaking change — your fix breaks apps that rely on <code>onClose</code> firing on open. On the other hand, a major release is likely to confuse everyone and scare them away from updating, so you should prefer <code>1.2.4</code>. There's no hard rule for situations like this, use your best judgement to decide if you can get away releasing the patch. Some things to consider:</p>
<ul>
<li>Can you know for sure if anyone actually relies on the broken behavior? Maybe search the codebase for internal projects, or ask around.</li>
<li>Does the broken behavior make no sense or contradict the documentation?</li>
<li>Has the bug been there for a long time? If you've been calling <code>onClose</code> on open for 2 years, since <code>0.0.1</code>, some users may well rely on it, especially if you didn't have an <code>onOpen</code>. If you just released it 5 minutes ago, just patch and deprecate the broken version ASAP.</li>
<li>Can you support <em>both</em> the broken and the fixed versions? This is often the case for typos, like <code>onColse -> onClose</code>. If you can — go with it, <a href="https://thoughtspile.github.io/2021/09/22/dev-warnings/">warn</a> on the old name and don't forget to remove it in the next major release.</li>
</ul>
<p>If you do release the breaking bufix as a patch, consider deprecating the broken version <a href="https://docs.npmjs.com/deprecating-and-undeprecating-packages-or-package-versions">via npm,</a> mentioning it in the changelog and notifying your users in the support chat / twitter.</p>
<h2>Feature-like bug fixes</h2>
<p><em>Bug fix</em> in semver terms is loosely related to normal person's idea of bug vs feature. Sometimes you can't fix a bug in the current API. In this case, <em>fixing</em> it is a <em>feature,</em> so you must release a <em>minor.</em></p>
<p>For example, your button component looks bad when you pass multi-line content. If you edit some CSS or adjust the display based on <code>offsetHeight</code>, it's a patch. If you add a special <code>multiline</code> option that users should pass for multiline content, you've just implemented a feature — <em>support multi-line content in buttons,</em> so a <em>minor.</em></p>
<h2>Feature vs Enhancement</h2>
<p>The <a href="https://stackoverflow.com/questions/27572557/scrum-terminology-what-is-the-difference-between-a-new-feature-and-an-enhanceme">feature / enhancement distinction</a> happens to be much more practical in SemVer. Say, you improve the positioning of a dropdown so that it detects scroll overflow and automatically chooses the up / down direction. Is it a bug fix, because the old behavior was <em>incorrect,</em> or a feature, because now your library does something it didn't do before?</p>
<p>I usually go for a feature (<em>minor</em> increment) in these cases, because a <em>patch</em> seems confusing, and a <em>major</em> is scary, but you can choose a different path. Some PRs to semver spec (<a href="https://github.com/semver/semver/pull/415">#415</a> or <a href="https://github.com/semver/semver/pull/588">#588</a>) allow you to make such changes in a patch, since it does not affect the API.</p>
<h2>Type updates</h2>
<p>Obviously, if your library has a TypeScript / Flow / whatever interface, any change to the interface type should be reflected in the version number. A type-only change, like exporting an interface that was internal, is a feature that deserves a minor bump.</p>
<h2>Dependency updates</h2>
<p>What should the new version of your package be if you update a package B you depend on? Summary of the <a href="https://github.com/semver/semver/issues/148">official discussion:</a></p>
<ul>
<li>If your library completely wraps the dependency and your users can't interact with package B, ignore it and version as per <em>your</em> change.</li>
<li>If your library exposes the underlying package B by letting the users access its objects or passing through user options, find out if the minor / breaking changes in B affects the <em>exact part</em> you expose. A safe & lazy option is to match your major / minor / patch update to the update in B.</li>
<li>Updating a <em>peer</em> dependency (like <code>React</code>), requires the users of your lib to also update that dependency, so it's breaking.</li>
<li>Updating a <em>dev</em> dependency is usually a no-op. Caveat: <em>if</em> you update TypeScript <em>and</em> use some new features in your public types, it's essentially a <em>peer</em> dep update, so breaking.</li>
</ul>
<p>I often see libraries update deps in a minor. I'd rather not do that, but I'm not completely against it, as long as the update path for package B is safe. Updating peer major in a minor release is pure evil, though.</p>
<h2>Compatibility changes</h2>
<p>Most libraries increase the major version when dropping runtime support. If your library runs in IE11, but then you add an unpolyfilled <code>.closest</code>, it's a breaking change because it may <em>break</em> some apps that were supposed to run in IE11. <em>Increasing</em> runtime compatibility (like adding a polyfill) is a no-op. The key here is the public compatibility guarantees you give — if you say "runs in IE11" in your docs, it's your API now, and dropping it is breaking. If you never promised IE11, you can argue that it just <em>happens</em> to work as an implementation detail and ignore it in your versioning.</p>
<hr />
<p>Here are my 10 semver lessons from 2 years of open-source work:</p>
<ol>
<li>Internal changes, like optimizations and refactorings, get either a <em>patch</em> bump or a <em>minor</em> bump if they're <em>substantial,</em> whatever that means.</li>
<li>Semver is useless without a good changelog detailing the chagnes.</li>
<li>Bump the highest component in releases with multiple changes: <em>bug fix + feature = minor.</em></li>
<li>A breaking change in a patch may be OK if it fixes a bug, and users are unlikely to depend on the broken behavior.</li>
<li><em>Features</em> don't change the API can fit into a <em>patch.</em></li>
<li>If a <em>bug fix</em> touches the API, it's a <em>feature,</em> so it gets a <em>minor</em> bump.</li>
<li>Your public types affect semver, too.</li>
<li>Updating dependencies affects your version as much as you expose their API.</li>
<li>Updating peer dependencies is <em>breaking.</em></li>
<li>Dropping browser / runtime compatibility is <em>breaking.</em></li>
</ol>
Why I always wrap Context.Provider and useContext2021-10-27T00:00:00Zhttps://thoughtspile.github.io/2021/10/27/better-react-context/<p>React context is a cool feature, and I use it a lot for injecting configuration and making container / child component APIs (think <code><RadioGroup /> + <RadioButton /></code>). Unfortunately, out of the box Context comes with a limiting and not very convenient API. In most cases, I choose to wrap both the provider and consumer with a custom component and a hook. Some of the issues I highlight are more relevant to library maintainers, but most apply to app development as well.</p>
<p>In this post, we revisit an <code>AdaptivityContext</code> that allows components to read viewport dimension data — pixel <code>width</code> and breakpoint status, <code>isMobile</code>:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">getWidth</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> window<span class="token punctuation">.</span>innerWidth<span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">isMobile</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">w</span><span class="token operator">:</span> number</span><span class="token punctuation">)</span> <span class="token operator">=></span> w <span class="token operator"><</span> <span class="token number">600</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> AdaptivityContext <span class="token operator">=</span> <span class="token function">createContext</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">w</span><span class="token operator">:</span> <span class="token function">getWidth</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">isMobile</span><span class="token operator">:</span> <span class="token function">isMobile</span><span class="token punctuation">(</span>getWidth<span class="token punctuation">)</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>If you've read my <a href="https://thoughtspile.github.io/2021/10/04/react-context-dangers/">post on Context performance issues,</a> you know it is not the best design choice — components that only care about <code>isMobile</code> will still re-render on every <code>width</code> change. Still, suppose that's what we happen to have on our project. How can custom <code>AdaptivityProvider</code> and <code>useAdaptivity</code> help us?</p>
<p><img src="https://thoughtspile.github.io/images/wrapped-context.jpg" alt="" /></p>
<h2>Wrap useContext</h2>
<p>In raw context API, the consuming components utilize <code>useContext</code> hook (or a <code>Context.Consumer</code> component, but I don't know why anyone would choose it over the hook today). There's nothing especially wrong with <code>useContext</code>, but we can do so much better with a custom <code>useAdaptivity</code>!</p>
<p>If <code>useContext</code> is used outside <code>Provider</code>, you're left with either a static default value from <code>createContext</code> or cryptic <em>can't read property width of null</em> errors. Sometimes it's enough, but <code>AdaptivityContext</code> is supposed to be dynamic, and we get a lot of "bug reports" that are fixed with a "did you forget the provider?". A custom <code>useAdaptivity</code> gives us two stronger options:</p>
<ol>
<li>Show an explicit error message, like <code>console.error('useAdaptivity must be used inside AdaptivityProvider')</code></li>
<li>Give each component an independent size observer, and make <code>AdaptivityProvider</code> optional for advanced optimizations and overrides.</li>
</ol>
<p>Next, <code>useContext</code> has a 1:1 relationship to contexts. Fixing <code>AdaptivityContext</code> performance problems involves splitting it into two separate contexts — a frequently-changing one for <code>width</code>, and a more stable one for <code>isMobile</code>. <code>useAdaptivity</code> can subscribe to both contexts — it won't have any performance benefits, but it's backwards compatible and allows users to gradually update their apps to the new API:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">useAdaptivity</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token string">'Please migrate to useMobile or useViewport for better performance'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> viewport <span class="token operator">=</span> <span class="token function">useContext</span><span class="token punctuation">(</span>ViewportContext<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> mobile <span class="token operator">=</span> <span class="token function">useContext</span><span class="token punctuation">(</span>MobileContext<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>viewport<span class="token punctuation">,</span> <span class="token operator">...</span>mobile <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Custom <code>useAdaptivity</code> hook also allows for an alternate context injection mechanism, like <a href="https://github.com/dai-shi/react-tracked">react-tracked</a>. You can even bind to a global state manager instead of context. Nothing about <code>useAdaptivity</code> implies that it has anything to do with contexts!</p>
<p>So, a custom <code>useAdaptivity</code> hook gives us a lot of freedom — we can modify the contexts as we wish, replace them with other state management mechanism, and we can handle a missing provider as we see fit. That's convincing. What about <code>Provider</code>?</p>
<h2>Wrap Context.Provider, too</h2>
<p><code>React.createContext</code> gives you a <code>Context.Provider</code> component you're supposed to use for passing a context value. It lacks some important features, but we can easily fix that by wrapping it into a custom <code>Provider</code> component.Frankly, it's less of a concern than <code>useContext</code> — you often have a single <code>Provider</code>, and it has to be located in <em>some</em> component, so you can't go too wrong. For completeness, here's what I normally do with a custom <code>Provider</code>.</p>
<p>Raw <code>Context.Provider</code> with object context is a performance hazard — if you don't stabilize <code>value</code> reference yourself, every context consumer will re-render on every <code>Provider</code> render, because React updates them every time context value changes under strict equality. I don't know why this feature is not in react core, but it's one good reason to have a custom provider (see my <a href="https://thoughtspile.github.io/2021/04/05/useref-usememo/">post on custom memo</a> for details on <code>useObjectMemo</code>):</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">AdaptivityProvider</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children<span class="token punctuation">,</span> <span class="token operator">...</span>context <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> contextValue <span class="token operator">=</span> <span class="token function">useObjectMemo</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">AdaptivityContext.Provider</span></span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>contextValue<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">AdaptivityContext.Provider</span></span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Just like <code>useContext</code>, raw <code>Providers</code> have a 1:1 relationship with contexts, making it harder to split / merge the contexts. To fix the coupling of <code>width</code> and <code>isMobile</code> updates, we must split <code>AdaptivityContext</code> into two parts. Easy with a custom provider:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">AdaptivityProvider</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children<span class="token punctuation">,</span> width<span class="token punctuation">,</span> isMobile <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> viewportValue <span class="token operator">=</span> <span class="token function">useObjectMemo</span><span class="token punctuation">(</span><span class="token punctuation">{</span> width <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> mobileValue <span class="token operator">=</span> <span class="token function">useObjectMemo</span><span class="token punctuation">(</span><span class="token punctuation">{</span> isMobile <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">ViewportSizeContext.Provider</span></span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>viewportValue<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">MobileContext.Provider</span></span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>mobileValue<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">MobileContext.Provider</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">ViewportSizeContext.Provider</span></span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Just like <code>useAdaptivity</code>, <code>AdaptivityProvider</code> also allows you to replace context with any other state management technology — just throw a <code><StoreProvider></code> in there and you're done.</p>
<p>Finally, a custom provider can handle context value in a smarter way — add default options or merge with another provider up the tree. If we had both <code>width</code> and <code>height</code>, we could allow partial overrides — user could use <code><ViewportSizeProvider width={100}></code> in a narrow sidebar, while preserving the <code>height</code> value:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> parentViewport <span class="token operator">=</span> <span class="token function">useContext</span><span class="token punctuation">(</span>ViewportSizeContext<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> contextValue <span class="token operator">=</span> <span class="token function">useObjectMemo</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token operator">...</span>parentWiewport<span class="token punctuation">,</span><br /> <span class="token operator">...</span>size<br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Of course, you could also have a custom mechanism of auto-detecting and updating context values:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">cb</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setDetectedSize</span><span class="token punctuation">(</span><span class="token function">getViewportSize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token function">cb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'resize'</span><span class="token punctuation">,</span> cb<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> window<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'resize'</span><span class="token punctuation">,</span> cb<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> contextValue <span class="token operator">=</span> <span class="token function">useObjectMemo</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token operator">...</span>detectedSize<span class="token punctuation">,</span><br /> <span class="token operator">...</span>props<br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>You could have amazing combinations of inheritance, auto-detection and overrides. Really, there are endless possibilities once you are the master of your context provider. Just don't settle for raw <code>Context.Provider</code>.</p>
<hr />
<p>Wrapping both the provider and the consumer of a context into custom hooks gives you a lot of flexibility:</p>
<ul>
<li>Merge and split context as you want.</li>
<li>Replace raw contexts with another state injection technique.</li>
<li>Stabilize context object value.</li>
<li>Introduce smart dynamic defaults for context value.</li>
<li>Inherit from other providers up the tree with partial overrides.</li>
<li>Warn or fallback on missing provider.</li>
</ul>
<p>This flexibility is crucial if you're building a library, but it also helps a lot in any non-trivial app. Hope that convinces you! See you later.</p>
Can we useRef, but without the .current? Let's try!2021-10-25T00:00:00Zhttps://thoughtspile.github.io/2021/10/25/useref-no-current/<p>Ah, <code>ref.current</code>. Everybody knows that I love <code>useRef</code> — I've <a href="https://thoughtspile.github.io/2021/04/05/useref-usememo/">built custom <code>useMemo</code> with it,</a> and I've used it <a href="https://thoughtspile.github.io/2021/10/18/non-react-state/">instead of <code>useState</code> to optimize re-renders.</a> But typing <code>ref.current</code> over and over is just annoying. Come on, Vladimir, <code>startX.current</code> is just the same as <code>this.startX</code> in a class, I told myself a million times, but it just doesn't work.</p>
<p>I think <code>ref.current</code> annoys me because it exists just to please the computer — I mean, mr. React, do you think I want a <code>.stale</code> value, or a <code>.future</code> one? Of course I'd like <code>.current</code>, could you please get it for me? Doing <em>any</em> work that can (or feels like it can) be automated is always annoying — you know what I mean if you ever had to write ES5 code without babel or struggled to sort imports for eslint without <code>--fix</code>.</p>
<p>In today's article, we embark on a journey to kill all <code>.current</code> (or at least <em>some</em>). We'll understand why it exists in the first place, see some practical cases when it can be avoided, and then, just for entertainment, see what the world wihout <code>.current</code> could have been.</p>
<h2>Why do we need ref.curernt at all?</h2>
<p>A brief recap if you're unsure why <code>useRef</code> exists. React function component is, obviously, a JS function that accepts <code>props</code> as an argument and returns some vDOM. Different props come in through an argument, so you might guess that React calls that function on every render:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Clicker</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// one call = one render</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>But if you declare a <code>let</code> variable in your component, it will be re-initialized to its initial value on every render, forgetting anything you may have assigned to it. Here, <code>clicks</code> will be back to zero if <code>Clicker</code>'s parent re-renders:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Clicker</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> clicks <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>clicks<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onClick<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token punctuation">}</span></code></pre>
<p>Moving the declaration outside the function solves the reset issue, but now all instances of our component share the same value, which is probably not what you want:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> clicks <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /><span class="token keyword">function</span> <span class="token function">Clicker</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// total number of clicks on all Clickers in our app ever</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>clicks<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onClick<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token punctuation">}</span></code></pre>
<p>Hence, react has a <code>useRef</code> hook that magically stores one value per component instance and persists it between re-renders:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Clicker</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> clicks <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>clicks<span class="token punctuation">.</span>current<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onClick<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token punctuation">}</span></code></pre>
<p>Note that the value we care about now lives in a <code>.current</code> property of some object. This solves two problems:</p>
<ul>
<li>React can't capture a new value from <code>clicks = clicks + 1</code>, since you can't observe assignments in JS.</li>
<li>The <em>wrapper object,</em> also known as a <em>box,</em> has a constant reference that lets callbacks cached in past renders read a "value from the future" — otherwise, they'd be stuck with a stale one.</li>
</ul>
<p>So, <code>useRef</code> lets us persist a mutable value between re-renders by putting it into a <code>current</code> property of a constant-reference box object. Looks like every part is necessary. But what if we don't always need to carry the whole box around?</p>
<p><img src="https://thoughtspile.github.io/images/cat-ref.jpg" alt="" /></p>
<h2>Skip .current for constants</h2>
<p>If the value wrapped in <code>useRef</code> actually never changes, we can dereference right in the declaration:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">[</span>clicks<span class="token punctuation">,</span> setClicks<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> onClick <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setClicks</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> c<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /><span class="token comment">// now we can just</span><br />onClick<span class="token operator">=</span><span class="token punctuation">{</span>onClick<span class="token punctuation">}</span><br /><span class="token comment">// instead of</span><br />onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> onClick<span class="token punctuation">.</span><span class="token function">current</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span></code></pre>
<p>This works because you never assign current, and don't need the <em>box</em> to preserve the reference because the inner reference is just as stable. Whether you should use this to cache callbacks or just <code>useCallback</code> is another question. Anyways, this works for any value you'd like to reliably cache forever:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> initialValue <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span>props<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token operator"><</span>input<br /> data<span class="token operator">-</span>changed<span class="token operator">=</span><span class="token punctuation">{</span>props<span class="token punctuation">.</span>value <span class="token operator">!==</span> initialValue<span class="token punctuation">}</span><br /> <span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span><br /><span class="token operator">/</span><span class="token operator">></span></code></pre>
<p>Don't carry the box around if the contents never change.</p>
<h2>Skip .current for mutable objects</h2>
<p>Storing constant values in a ref is not the most obscure use case, but still a fairly specialized one. But when you're storing a mutable object in a ref without reassigning it, you're still working with a <em>constant</em> — sure, the contents of your object change, but the reference is stable, so the trick above still applies. If you feel like this is against hooks, or will cause any trouble, please see <a href="https://thoughtspile.github.io/2021/10/11/usestate-object-vs-multiple/">my older post on <code>useState(object)</code> vs many <code>useStates</code></a> (spoiler: it's OK and even preferable for related values).</p>
<p>For instance, here's what I often use for gesture tracking:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Swiper</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> el <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> gesture <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">startX</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">startY</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">startT</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onStart</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// ah, it's so nice to skip gesture.current.startX</span><br /> gesture<span class="token punctuation">.</span>startX <span class="token operator">=</span> e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientX<span class="token punctuation">;</span><br /> gesture<span class="token punctuation">.</span>startY <span class="token operator">=</span> e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientY<span class="token punctuation">;</span><br /> gesture<span class="token punctuation">.</span>startT <span class="token operator">=</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onMove</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> x <span class="token operator">=</span> e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientX<span class="token punctuation">;</span><br /> <span class="token keyword">const</span> y <span class="token operator">=</span> e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientY<span class="token punctuation">;</span><br /> <span class="token comment">// no .current is amazing</span><br /> el<span class="token punctuation">.</span>current<span class="token punctuation">.</span>style<span class="token punctuation">.</span>transform <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">translate(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>x <span class="token operator">-</span> gesture<span class="token punctuation">.</span>startX<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">,</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>y <span class="token operator">-</span> gesture<span class="token punctuation">.</span>startY<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">,0)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><br /> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>el<span class="token punctuation">}</span></span><br /> <span class="token attr-name">onTouchStart</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onStart<span class="token punctuation">}</span></span><br /> <span class="token attr-name">onTouchMove</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onMove<span class="token punctuation">}</span></span><br /> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>We've grouped the three variables we track during a gesture into a single object ref. I think it's more convenient, and communicates the intent better than just having some separate refs floating around your code with no clear relationship.</p>
<p>So, if your ref contents is a <em>box</em> itself, you don't need one more box to carry the first one around. Also, if you have several related refs anyways, why not put them into one box?</p>
<h2>Fragile corner-cases</h2>
<p>That's it for the stuff I use frequently. There are two more cases that work the same with or without a <code>useRef</code>, but they're very fragile and I wouldn't rely on these. Still, they'd be interesting to cover.</p>
<h3>Constant component</h3>
<p>OK, <code>let</code> variable resets on re-render. Then, if our component <em>never</em> re-renders, maybe we're safe skip the <code>useRef</code> and just use a <code>let</code>:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> Icon <span class="token operator">=</span> <span class="token function">memo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> clicks <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> clicks<span class="token operator">++</span><span class="token punctuation">;</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>clicks<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">SomeStaticSVG</span></span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onClick<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Not <em>using</em> any props in a component and slapping a <code>memo</code> on it is not enough — we could pass a useless prop and change it, like <code><Icon gotcha={Math.random()} /></code> — React doesn't know if we care for <code>gotcha</code>. An extra hint in our memo comparator does the job. Hooks that can re-render our component are a no-go, too — <code>useState</code>, <code>useReducer</code>, <code>useContext</code>, or any custom hooks based on these.</p>
<p>Components like this one are not as useless as you might think — I've actually made an optimized icon pack with a similar pattern. Still, the lack of props is very limiting. But the major problem with this code is that React doesn't give any guarantees about <code>memo</code> — at some point it might start discarding old values to free memory, resetting your precious clicks. Dangerous!</p>
<h3>Constant callbacks</h3>
<p>A slightly more practical (yet still sloppy) scenario is using a <em>ref</em> only inside callbacks that are created in the first render and cached forever. Yes, we reset the value on every render, but who cares if all the function that use it are stuck in the scope of the first render:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Swiper</span><span class="token punctuation">(</span><span class="token parameter">p</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> clicks <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> onClick <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> clicks<span class="token operator">++</span><span class="token punctuation">;</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>clicks<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onClick<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">click me</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token punctuation">}</span></code></pre>
<p><code>useCallback(..., [])</code> won't cut it, since, again, react does not actually guarantee it will cache forever. With an explicit constant <code>useRef</code> we're safe, but the whole thing explodes if you ever need to capture a state/props in a callback, and rewrite it to <code>useCallback</code> or remove caching altogether. Not recommended.</p>
<h2>Going beyond with objects.</h2>
<p>For the sake of an argument, let's assume I find <code>.current</code> absolutely inacceptable for religious reasons. What could I do to never type it again? There's a whole bunch of solutions if I'm really determined.</p>
<p>A least adventurous option is a custom hook that's just like a default ref, but replaces <code>current</code> with a different name. <code>v</code> is fine — it's short, it stands for Value, and it's a fine-looking letter. Here we go:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// inner object is the ref-box now</span><br /><span class="token keyword">const</span> <span class="token function-variable function">useV</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">init</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">v</span><span class="token operator">:</span> init <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /><span class="token comment">// use as follows</span><br /><span class="token keyword">const</span> startX <span class="token operator">=</span> <span class="token function">useV</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><br /> <span class="token attr-name">onTouchStart</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> startX<span class="token punctuation">.</span>v <span class="token operator">=</span> e<span class="token punctuation">.</span>clientX<span class="token punctuation">}</span></span><br /> <span class="token attr-name">onTouchMove</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setOffset</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>clientX <span class="token operator">-</span> startX<span class="token punctuation">.</span>v<span class="token punctuation">)</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">transform</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">translateX(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>offset<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px)</span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><br /><span class="token punctuation">></span></span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>But that's boring. What if we always put all the refs in a component into a large object? Anything we can do with multiple refs is doable with a single one. Looks like something a person who hates hooks but is forced to use them could do:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// hope you're old enough to get this hommage</span><br /><span class="token keyword">const</span> that <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">startX</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span><br /> <span class="token comment">// WOW we can even have CLASS METHODS back!</span><br /> <span class="token function">onTouchStart</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>startX <span class="token operator">=</span> e<span class="token punctuation">.</span>clientX<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token function">onTouchMove</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// And call state update handles since they're stable</span><br /> <span class="token function">setOffset</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>clientX <span class="token operator">-</span> <span class="token keyword">this</span><span class="token punctuation">.</span>startX<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><br /> <span class="token attr-name">onTouchStart</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>that<span class="token punctuation">.</span>onTouchStart<span class="token punctuation">}</span></span><br /> <span class="token attr-name">onTouchMove</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>that<span class="token punctuation">.</span>onTouchMove<span class="token punctuation">}</span></span><br /> <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">transform</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">translateX(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>offset<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px)</span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><br /><span class="token punctuation">></span></span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>The fact that we can have <em>methods</em> on that large stateful object is very exciting. On a sadder note, we can't read current props or state, because they don't have a stable reference. We could start copying props into <code>that</code>, but the very idea of "current props" <a href="https://github.com/facebook/react/pull/18545">gets fuzzy</a> once you enter concurrent mode, and I'm not going to die on this (ha, <code>this</code>) hill, or at least not today.</p>
<p>In an unexpected twist, we could even move ref management into a HOC. Remember <a href="https://reactjs.org/docs/react-without-es6.html">createReactClass?</a> Well, it's back:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">makeComponent</span> <span class="token operator">=</span> <span class="token parameter">descriptor</span> <span class="token operator">=></span> <span class="token parameter">props</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> scope <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span>descriptor<span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> scope<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> Swiper <span class="token operator">=</span> <span class="token function">makeComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token comment">// you can't use arrows because you need "this"</span><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// any hooks in render() are OK:</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>value<span class="token punctuation">,</span> setValue<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>onClick<span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">clicks</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span><br /> <span class="token function">onClick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>clicks<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Apart from the missing props / state access, these solutions have other downsides:</p>
<ul>
<li>We create an extra object on each render and throw it away. A custom lazy-initializing <code>useRef</code> can work around that, though.</li>
<li>Like all object-based code, they minify a bit worse than "atomic refs", because property names are not mangled (see my earlier <a href="https://thoughtspile.github.io/2021/10/11/usestate-object-vs-multiple/">benchmark of atomic vs object state</a>).</li>
</ul>
<p>Anyways, <code>{ current }</code> is not the only object shape that could work as a ref. What else can we do?</p>
<h2>And even further with callbacks</h2>
<p>Objects are not the only JS thing that can be a stable container for a changing value. Let's try a function instead! (Don't get me started with <code>(() => {}) instanceof Object</code>, functions are clearly not objects). First, let's try a polymorphic handle that can both get and set the value:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">useFunRef</span><span class="token punctuation">(</span><span class="token parameter">init</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> ref <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span>init<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> handle <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>args</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// if we pass an argument, update the value</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>args<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> ref<span class="token punctuation">.</span>current <span class="token operator">=</span> args<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">return</span> ref<span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> handle<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Using it is simple: you either call the handle with no arguments to get the current value, or with a new value to update:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span>offset<span class="token punctuation">,</span> setOffset<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> nodeRef <span class="token operator">=</span> <span class="token function">useFunRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> startX <span class="token operator">=</span> <span class="token function">useFunRef</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><br /> <span class="token attr-name">onTouchStart</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">startX</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientX<span class="token punctuation">)</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">onTouchMove</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setOffset</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientX <span class="token operator">-</span> <span class="token function">startX</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>nodeRef<span class="token punctuation">}</span></span><br /> <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">transform</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">translateX(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>offset<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px)</span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><br /><span class="token punctuation">></span></span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>I like how this one integrates with DOM refs thanks to the callback-ref syntax. As an added advantage, functions should be faster to create (then throw away) than objects. And, since you're using more functions, your programming clearly becomes more functional.</p>
<p>If you don't like functions that do different things depending on the number of arguments, we can separate the getter and the setter, similarly to what <code>useState</code> does:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">useStateRef</span><span class="token punctuation">(</span><span class="token parameter">init</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> ref <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span>init<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> setter <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">v</span><span class="token punctuation">)</span> <span class="token operator">=></span> ref<span class="token punctuation">.</span>current <span class="token operator">=</span> v<span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token keyword">const</span> getter <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> ref<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">[</span>getter<span class="token punctuation">,</span> setter<span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token comment">// usage example</span><br /><span class="token keyword">const</span> <span class="token punctuation">[</span>startX<span class="token punctuation">,</span> setStartX<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useStateRef</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><br /> <span class="token attr-name">onTouchStart</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setStartX</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>clientX<span class="token punctuation">)</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">onTouchMove</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setOffset</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>clientX <span class="token operator">-</span> <span class="token function">startX</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><br /><span class="token punctuation">></span></span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>So yes, a function can be a ref-box, too. That's good to know. Is there anything else?</p>
<h2>Nothing can stop me now</h2>
<p>Until now, we've been playing with the <em>box</em> shape without straying too far from the overall concept. But maybe that's what we call "a poultice for a dead man" in Russia? (<em>English tip: a poultice is a warm bag of herbs used in traditional medicine. It surely won't help if you're dead. I learnt this word just to write this post.</em>) What if we don't need a box?</p>
<p>Component scope resets on every render. Fine, we need another scope to store our value. Module scope is too drastic — can we just get one that persists between re-renders, but is unique to every component? I'm the master of my scopes, so why not:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">makeClicker</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// this is the outer / instance scope</span><br /> <span class="token keyword">let</span> clicks <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /> <span class="token comment">// we can declare callbacks here</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>clicks<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// this is the inner / render scope</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onClick<span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><span class="token keyword">function</span> <span class="token function">Clicker</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// Now we need to manage the instance scope</span><br /> <span class="token keyword">const</span> render <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token function">makeClicker</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token comment">// and turn it into a regular component</span><br /> <span class="token keyword">return</span> <span class="token function">render</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>While we're at it, more of the same can be done with a generator — sure, we can only <code>return</code> once, but why not <code>yield</code> our JSX on every render instead?</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span><span class="token operator">*</span> <span class="token function">genClicker</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> clicks <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>clicks<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> props <span class="token operator">=</span> <span class="token keyword">yield</span> <span class="token punctuation">(</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><br /> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onClick<span class="token punctuation">}</span></span><br /> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span><br /> <span class="token punctuation">/></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><span class="token keyword">function</span> <span class="token function">Clicker</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> render <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token function">genClicker</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> render<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">.</span>value<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>In both cases, we can't use hooks in the <em>outer scope</em>. If we were to turn <code>clicks</code> into state, we couldn't do it like this:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">makeClicker</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>clicks<span class="token punctuation">,</span> setClicks<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setClicks</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> c <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onClick<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>clicks<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>It doesn't explode, since we happen to call <code>useState</code> on every render (because we call <code>makeClicker</code> on every render and throw it away), but <code>clicks</code> will be stuck at 0 — it's a <code>const</code> from the first render. We're free to use hooks both in our <em>inner scope</em> and the <code>Swiper</code> wrapper, though. This also means that we can't use our outer refs to cache state update / dispatch handles, which I liked very much.</p>
<p>These concepts are very interesting, because they're in line with the hooks mindset: minimal object use (good for memory & minification) and creative handling of JS scopes. At the same time, we don't need an object box to host our ref! Also, if we manage to build a <em>lazy ref</em> for out instance scope, we skip recreating useless variables and callbacks on every render, which is pleasant. The syntax and the limitations on hooks in the outer scope are sad, but I feel like they can be worked around (maybe something like <code>clicks = yield useGenState(0)</code>). Promising.</p>
<hr />
<p>In this article, we've seen why <code>useRef</code> has that weird <code>.current</code> property, and learnt some tricks to write <code>.current</code> less:</p>
<ul>
<li>Dereference constant values during creation: <code>const onClear = useRef(() => setValue('')).current;</code></li>
<li>Combine several <code>refs</code> into a mutable ref-object, and mutate that instead of <code>current</code>: <code>pos = useRef({ x: 0, y: 0 }).current</code>, read with <code>pos.x</code>, write with <code>pos.x = e.clientX()</code></li>
</ul>
<p>In some cases, you could drop the <code>useRef</code> and use a simple <code>let</code> variable instead, but I don't recommend it.</p>
<p>To stimulate our imagination, we've also implemented <em>seven</em> alternate APIs on top of the default <code>useRef</code> that don't use <code>.current</code>:</p>
<ul>
<li>One with an alternate property name: <code>useV(0).v</code></li>
<li><em>Stateful core</em> that's surprisingly similar to a class component.</li>
<li>A <code>makeComponent</code> factory that lets you put the render function, along with some properties and methods, into an object, yet still allows for hooks.</li>
<li>Two function-based <code>useRefs</code>: a <code>useState</code>-like one that has separate get and set handles: <code>const [getX, setX] = useStateRef(0)</code>, and one with a single handle.</li>
<li>A component with two scopes: one that persists throughout re-rendering and can host ref-like mutable variables, and one that actually renders the JSX. We've also made a similar one with generators.</li>
</ul>
<p>Maybe this wasn't very useful (I'm not eager to rewrite all my code using these patterns), but I hope it was great fun (it sure was for me). React is amazingly flexible, which is why I love it. Hope this mental exercise got you excited. See you later!</p>
How to replace useState with useRef and be a winner2021-10-18T00:00:00Zhttps://thoughtspile.github.io/2021/10/18/non-react-state/<p>React state is the bread and butter of a react app — it's what makes your app dynamic. React state lives in <code>useState</code>, <code>useReducer</code> or in <code>this.state</code> of a class component, and changing it updates your app. But then there's a vast ocean of state not managed by React. This includes <code>ref.current</code>, object properties, and, really, anything other than react state.</p>
<p>React state is a safe default — if you put a dynamic value somewhere else, the component won't re-render. But stuffing values that don't <em>need</em> to be managed by react into state is more sneaky. It rarely results in visible bugs, but makes your components more complex and slows them down.</p>
<p>In this post, we'll:</p>
<ul>
<li>Discuss the difference between react state and non-react state;</li>
<li>See when state can be safely replaced with a ref (spoiler: you only <em>need</em> state if your JSX depends on it, or to trigger <code>use*Effect</code>);</li>
<li>Learn a few optimizations for performance-critical cases.</li>
</ul>
<p><img src="https://thoughtspile.github.io/images/states.png" alt="" /></p>
<h2>What are we even talking about?</h2>
<p>Let's first spend a minute reflecting on what's so special about react state, and what types of non-react state exist, and how they're so different, but still useful.</p>
<p>Describing react state is easy: it's a value stored in <code>useState</code> hook (or <code>useReducer</code>, since <a href="https://thoughtspile.github.io/2021/09/27/usestate-tricks/">they are the same</a>) or in <code>this.state</code> of a class component. Updating react state makes your component re-render. In fact, updating react state is the <em>only</em> thing that makes react re-render. React veterans recall <code>forceUpdate</code>, but it can be <a href="https://stackoverflow.com/a/53215514">trivially emulated with a setState</a>. <code>ReactDOM.render</code> makes your app <em>render,</em> not <em>re</em>-render. So, react state is what makes react tick.</p>
<p>Now, let's see where else in our app a state can live. "Anywhere else" is correct, but too vague — let's make a list of common locations:</p>
<ol>
<li><code>useRef().current</code>.</li>
<li>Class properties of class components, fashionable or not.</li>
<li>Actually, every property of every object ever.</li>
<li>Yes, that includes state managers. Their state only turns into react state after a couple of magic tricks.</li>
<li>DOM state — input values, focus, scrolls, any DOM tree elements and attributes not managed by React. Making them <em>controlled</em> does not literally turn them into react state, it's just another trick.</li>
<li>Values of variables. You may have never thought of these as "state", but hey — that's a value lying in memory that closures can read, so it qualifies.</li>
</ol>
<p>This list could go on: other stateful browser APIs (think pending timeouts), back-end state, the photons in the transatlantic cables carrying our API data, your user's neural signals, and all his lifetime experience, and that tree in the forest that fell while no one was watching, all came together just for the user to click the button you're building now. Does free will exist? Are we mere grains of sand carried by the flow of creation? Oh no, Vladimir, you've done it again, let's get back on track, shall we? There are more pressing and practical matters we need to discuss today.</p>
<h2>When to use react state</h2>
<p>React depends on state to make your app dynamic. That is the core functionality of a front-end framework, so you'd expect an infinite variety of use cases to exist. But in fact, there are only two situations when you <em>must</em> use react state, and they are easy to spot.</p>
<p>Every dynamic value that affects your component's DOM is react state. Fair enough, the UI should stay up-to-date. Quick example, no revelations here:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Incrementer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>value<span class="token punctuation">,</span> setValue<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setValue</span><span class="token punctuation">(</span>value <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> Clicked </span><span class="token punctuation">{</span>value<span class="token punctuation">}</span><span class="token plain-text"> times<br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>But values that have no effect on the vDOM can still belong in react state. Why? To trigger an effect:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">TitleRandomizer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>title<span class="token punctuation">,</span> setTitle<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> document<span class="token punctuation">.</span>title <span class="token operator">=</span> title<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>title<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setTitle</span><span class="token punctuation">(</span><span class="token string">''</span> <span class="token operator">+</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> randomize page title<br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>This is not exclusive to hooks — <code>componentDidUpdate</code> is no different, since it's only called when a component, you know, <em>did update:</em></p>
<pre class="language-jsx"><code class="language-jsx"><span class="token function">componentDidUpdate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> document<span class="token punctuation">.</span>title <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>title<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Believe it or not, that's it: use react state for values that (a) are used in the JSX <em>or</em> (b) trigger side-effects via <code>use*Effect</code> or in lifecycle hooks. In all other cases, you can safely store them anywhere you want.</p>
<h2>When not to use React state</h2>
<p>Is there anything wrong with react state? You'd much prefer your app to update, not to stay jammed in a stale state. It's a fine feature, but <em>not</em> using react state has some hard (and some soft) advantages.</p>
<p>First, non-react state is easier to work with. Updates to non-react state are synchronous — no need to put stuff that reads an updated value into effects or that nasty <code>this.setState</code> callback. You also get to utilize mutable data containers and assign them directly without <a href="https://github.com/immerjs/immer">immer</a> or <a href="https://mobx.js.org/">mobx</a> — I know you've secretly missed it.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// We've come to accept this</span><br /><span class="token function">setChecked</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span>checked<span class="token punctuation">,</span> <span class="token punctuation">[</span>value<span class="token punctuation">]</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// But isn't this just nicer?</span><br />checked<span class="token punctuation">[</span>value<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span></code></pre>
<p>Secondly, updating a non-react state doesn't trigger a re-render. You can see it as a footgun, or you can use it to your advantage. The lack of rendering enables very powerful performance optimizations — see hard rule of performance #1/1: doing nothing is <em>not slower</em> than doing something. Also, since refs are constant-reference mutable objects, you don't have to recreate callbacks that rely on them, and can thus skip re-rendering memo-children:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> onCheck <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// re-render, including children</span><br /> <span class="token function">setChecked</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span>checked<span class="token punctuation">,</span> <span class="token punctuation">[</span>value<span class="token punctuation">]</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>checked<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> onCheckRef <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// relax, react, nothing happened</span><br /> checked<span class="token punctuation">[</span>value<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span></code></pre>
<p>Not using react state helps avoid a problem I call <em>render thrashing</em> — a react equivalent of <a href="https://www.afasterweb.com/2015/10/05/how-to-thrash-your-layout/">layout thrashing.</a> That's when a state change triggers an effect that changes more state, and react must keep re-rendering until the state stabilizes. If timed correctly, ref updates are very effective at avoiding this pitfall.</p>
<p>Finally, react state carries more semantics, and overusing it makes your app seem more complex. State is a big deal in react. Touching state has consequences — it triggers DOM changes and funny side-effects. When changing a non-state, you just change it, and maybe later someone can read it back. Not so scary!</p>
<p>One <em>caveat</em> pointed out by <a href="https://twitter.com/phry">Lenz Weber:</a> accessing <code>ref.current</code> in the render phase is not concurrent-mode-safe, and <a href="https://github.com/facebook/react/pull/18545">may cause a warning</a> in a future react version. Setting state while rendering seems to work. At any rate, firing side-effects from a render function is not healthy.</p>
<p>Now, let's move on to some concrete examples where replacing state with a ref is useful.</p>
<h3>Values you only need in callbacks</h3>
<p>You don't need react state if you only use it in callbacks — event handlers or effects. To demonstrate this, let's build a simple swipe detector. The user puts a finger on the screen and moves it left or right. Sticking to react state, we end up with:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Swiper</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> prev<span class="token punctuation">,</span> next<span class="token punctuation">,</span> children <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>startX<span class="token punctuation">,</span> setStartX<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">detectSwipe</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientX <span class="token operator">></span> startX <span class="token operator">?</span> <span class="token function">prev</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><br /> <span class="token attr-name">onTouchStart</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token parameter">e</span> <span class="token operator">=></span> <span class="token function">setStartX</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientX<span class="token punctuation">)</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">onTouchEnd</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>detectSwipe<span class="token punctuation">}</span></span><br /> <span class="token punctuation">></span></span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p><code>startX</code> does not affect the DOM or fire any effects, we only store it to read later in a <code>touchend</code>. Still, you get a useless render on <code>touchstart</code>. Let's try again with a ref:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Swiper</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> prev<span class="token punctuation">,</span> next<span class="token punctuation">,</span> children <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> startX <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">detectSwipe</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientX <span class="token operator">></span> startX<span class="token punctuation">.</span>current <span class="token operator">?</span> <span class="token function">prev</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><br /> <span class="token attr-name">onTouchStart</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token parameter">e</span> <span class="token operator">=></span> startX<span class="token punctuation">.</span>current <span class="token operator">=</span> e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientX<span class="token punctuation">}</span></span><br /> <span class="token attr-name">onTouchEnd</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>detectSwipe<span class="token punctuation">}</span></span><br /> <span class="token punctuation">></span></span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Voila, Swiper now doesn't have to re-render on <code>touchstart</code>. Additionally, <code>detectSwipe</code> now doesn't depend on the changing <code>startX</code> reference, so you can <code>useCallback(..., [])</code> on it. Awesome!</p>
<p>By the way, the tradition of storing DOM nodes in a ref is a special case of this rule — it works because you only access the node in callbacks.</p>
<h3>Buffering state updates</h3>
<p>OK, one render is <em>nothing</em> for react. Let's up the stakes by bringing in a whole rerendering barrage. Now the user can move the <code>Swiper</code> content around with the power of his finger:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Swiper</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> startX <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>offset<span class="token punctuation">,</span> setOffset<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onStart</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> startX<span class="token punctuation">.</span>current <span class="token operator">=</span> e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientX<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">trackMove</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setOffset</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientX <span class="token operator">-</span> startX<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><br /> <span class="token attr-name">onTouchStart</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onStart<span class="token punctuation">}</span></span><br /> <span class="token attr-name">onTouchMove</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>trackMove<span class="token punctuation">}</span></span><br /> <span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">transform</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">translate3d(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>offset<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px,0,0)</span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>It works, but note how <code>touchMove</code> updates state and makes the component re-render. <code>touchMove</code> event is famous for firing <em>a lot</em> — I ended up with <a href="https://codesandbox.io/s/objective-lederberg-zjft4">4–5 renders per frame.</a> The user only sees the result of the last render before paint, the other 4 are wasted. <code>requestAnimationFrame</code> is a perfect fit for this case — we remember the swipe position in a ref, but only update the state once per frame:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> pendingFlush <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">trackMove</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>startX<span class="token punctuation">.</span>current <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">cancelAnimationFrame</span><span class="token punctuation">(</span>pendingFlush<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> pendingFlush<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token function">requestAnimationFrame</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setOffset</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>clientX <span class="token operator">-</span> startX<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Here's an alternate take. Instead of canceling the pending RAF, we can let them all fire, but set state to the same value — <a href="https://thoughtspile.github.io/2021/09/27/usestate-tricks/">only one will cause a re-render:</a></p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> pendingOffset <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">trackMove</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>startX<span class="token punctuation">.</span>current <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> pendingOffset<span class="token punctuation">.</span>current <span class="token operator">=</span> e<span class="token punctuation">.</span>clientX <span class="token operator">-</span> startX<span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token function">requestAnimationFrame</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setOffset</span><span class="token punctuation">(</span>pendingOffset<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>We've just implemented a custom update batching mechanism by making state and ref work together. The mutable ref acts as a <em>staging area</em> for pending state updates. Just like the last time, <code>trackMove</code> only depends on stable refs, and can be turned into a const-reference callback.</p>
<h3>State that you want to manage yourself</h3>
<p>When the user moves his finger, we let react determine the current offset and update the <code>style</code> accordingly. React may be fast, but it doesn't know that <code>trackMove</code> just changes the transform, and has to do a lot of guessing — call your render, generate the vDOM, diff it, and then, a-ha, it seems like we just have to update a transform. But <em>you</em> know what you're up to, and can save React all that trouble by just doing it yourself:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Swiper</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> startX <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> transformEl <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onStart</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> startX<span class="token punctuation">.</span>current <span class="token operator">=</span> e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientX<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">trackMove</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> offset <span class="token operator">=</span> e<span class="token punctuation">.</span>touches<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>clientX <span class="token operator">-</span> startX<span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> transformEl<span class="token punctuation">.</span>current<span class="token punctuation">.</span>style<span class="token punctuation">.</span>transform <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">translate3d(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>offset<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px,0,0)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><br /> <span class="token attr-name">onTouchStart</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onStart<span class="token punctuation">}</span></span><br /> <span class="token attr-name">onTouchMove</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>trackMove<span class="token punctuation">}</span></span><br /> <span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>transformEl<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Voila, 0 renders! Fair warning — it's very easy to trick yourself here, especially if several things can affect the DOM. Reserve this technique for frequent low-level stuff like animations and gestures — it can make a huge difference.</p>
<h3>Derived state</h3>
<p>If a value always updates <em>together</em> with a react state item, we can piggyback on that re-render and update something else that is not react state along the way. This can be very clean — remember how I said <em>any</em> variable holds a state?</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span>value<span class="token punctuation">,</span> setValue<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> isValid <span class="token operator">=</span> value <span class="token operator">>=</span> <span class="token number">0</span> <span class="token operator">&&</span> value <span class="token operator"><</span> <span class="token number">100</span><span class="token punctuation">;</span></code></pre>
<p>This can be trickier and involve a ref, but still straightforward on the outside, as <code>useMemo</code> — yes, it <a href="https://thoughtspile.github.io/2021/04/05/useref-usememo/">does use a ref</a> deep inside:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span>search<span class="token punctuation">,</span> setSearch<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> matches <span class="token operator">=</span> <span class="token function">useMemo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> options<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">op</span> <span class="token operator">=></span> op<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span>search<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>options<span class="token punctuation">,</span> search<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>In both cases, we're using non-react state, carefully synchronizing its updates with the master state. Much better than cascading state updates:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// un-example</span><br /><span class="token keyword">const</span> <span class="token punctuation">[</span>search<span class="token punctuation">,</span> setSearch<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token punctuation">[</span>matches<span class="token punctuation">,</span> setMatches<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// now we re-render twice per search change</span><br /> <span class="token function">setMatches</span><span class="token punctuation">(</span>options<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">op</span> <span class="token operator">=></span> op<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span>search<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>options<span class="token punctuation">,</span> search<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<hr />
<p>Wow, it's been a long post. Now we need a multi-part recap:</p>
<ul>
<li>State in a react app can be either a react state (<code>this.state</code>, <code>useState</code>, <code>useReducer</code>) or non-react state (<code>ref.current</code>, object properties, variable values, or anything else).</li>
<li>Only updates to react state make react re-render, so you <em>must</em> used it when the vDOM depends on it, or to trigger a <code>use*Effect</code>.</li>
</ul>
<p>Not using state has some advantages:</p>
<ul>
<li>Fewer renders</li>
<li>More stable callbacks</li>
<li>No cascading state updates aka <em>render thrashing</em></li>
<li>Synchronously mutating data is so nice</li>
<li>Overusing state makes a component seem complex</li>
</ul>
<p>Here are 4 powerful optimizations relying on non-react state:</p>
<ul>
<li>If a value is only used in callbacks – make it a ref (includes DOM refs).</li>
<li>A ref can be a buffer for pending state updates.</li>
<li>Use refs if you feel you can update the DOM yourself without involving react.</li>
<li>Derived state also relies on refs, carefully updated on core state changes.</li>
</ul>
<p>As a rule of thumb, refs are fine as long as you don't use their current value in render.</p>
<p>State vs non-state is a very powerful concept that I'll revisit in my future posts. As a homework, try thinking about how React's only job is actually synchronizing its state to the external DOM state. Or that state-of-the-univerese thing I talked about earlier. See you soon!</p>
Thanks React, I'm fine with an imperative setInterval2021-10-13T00:00:00Zhttps://thoughtspile.github.io/2021/10/13/really-declarative/<p>Like many of you, I've read Dan Abramov's excellent article, <a href="https://overreacted.io/making-setinterval-declarative-with-react-hooks">making setInterval declarative with React hooks.</a> It's a great introduction to hook thinking and gotchas, highly recommended to any react dev. But by now the insistence on being declarative in every hook ever has gone too far, and it's starting to annoy me. Hook libraries that don't expose imperative handles at all are less useful, and using them comes with a real performance cost. How so? Let me show.</p>
<p><img src="https://thoughtspile.github.io/images/set-timeout.jpg" alt="" /></p>
<h2>The example</h2>
<p>Let's jump straight into the code. I'm building a synthetic input with a nice "info" icon that explains what this input is for when the user hovers it. To prevent any jumpiness when the user just moves the mouse around, I open the tooltip after 100ms of hovering:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Input</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> details <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>showDetails<span class="token punctuation">,</span> setShowDetails<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>isHovered<span class="token punctuation">,</span> setHovered<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setShowDetails</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> isHovered <span class="token operator">?</span> <span class="token number">100</span> <span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onEnter</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setHovered</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onLeave</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setHovered</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">setShowDeatils</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span><br /> <span class="token attr-name">onMouseEnter</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onEnter<span class="token punctuation">}</span></span><br /> <span class="token attr-name">onMouseLeave</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onLeave<span class="token punctuation">}</span></span><br /> <span class="token punctuation">></span></span><span class="token plain-text">i</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>And here's the <code>useTimeout</code> hook — I'll skip the part where Dan explains why this code looks what it looks like, please check out his <a href="https://overreacted.io/making-setinterval-declarative-with-react-hooks">original post</a> if you have any questions. I only replaced the interval with a timeout, because, to tell you the truth, I have used intervals exactly zero times in the past 5 years, but I use timeouts every week.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">useTimeout</span><span class="token punctuation">(</span><span class="token parameter">callback<span class="token punctuation">,</span> delay</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> savedCallback <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">// Remember the latest callback.</span><br /> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> savedCallback<span class="token punctuation">.</span>current <span class="token operator">=</span> callback<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>callback<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">// Set up the interval.</span><br /> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>delay <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> id <span class="token operator">=</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> savedCallback<span class="token punctuation">.</span><span class="token function">current</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> delay<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearTimeout</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>delay<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>It's a nice, consistent hook that does many things right — in fact, it's similar to my idea of <a href="https://thoughtspile.github.io/2021/04/07/better-usecallback/">the perfect useCallback.</a> Let's first admire the things it does right:</p>
<ul>
<li>You can't forget to clear the timeout on unmount.</li>
<li>You never call a stale callback.</li>
<li>You don't even have to specify callback "dependencies"</li>
</ul>
<p>But then there's something I don't like that much. To set a callback, we switch the <code>hovered</code> state. This state change triggers the effect in <code>useTimeout</code> that actually sets the timeout. <em>But,</em> like every state change, it also happens to re-render a component. So, while we're calling our <code>setTimeout</code>, we also get to:</p>
<ol>
<li>Call setState</li>
<li>Schedule a re-render</li>
<li>Call the render function</li>
<li>Produce a bunch of objects and functions for our hooks</li>
<li>Compare some dependency arrays</li>
<li>Note that <code>hovered</code> has changed, and schedule that effect from <code>useTimeout</code></li>
<li>Generate a bunch of vDOM</li>
<li>Diff the old and new vDOMs to see that almost nothing happened</li>
<li>Bind new DOM event handlers, because their reference has changed, who knows</li>
<li>Finally, <code>setTimeout</code>!</li>
</ol>
<p>I mean, it will all probably happen pretty fast, but come on, is calling a <code>setTimeout</code> <em>really</em> worth all that fuss? Me, I don't think so. The idea of making my user's CPU go through all that hoops to call a function makes me very sad. Luckily, I know how to fix it.</p>
<h2>Give me back my imperative</h2>
<p>What if we were to skip the <em>declarative</em> part, and just tried to build a consistent hook wrapper around <code>setTimeout</code>? Here's my take (we use a <a href="https://github.com/VKCOM/VKUI/blob/288114403c892ae11e60fe65525cdef89a272c53/src/hooks/useTimeout.ts">very similar hook</a> in our production code):</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">useImperativeTimeout</span><span class="token punctuation">(</span><span class="token parameter">callback<span class="token punctuation">,</span> delay</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> timeoutId <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> savedCallback <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">// Remember the latest callback.</span><br /> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> savedCallback<span class="token punctuation">.</span>current <span class="token operator">=</span> callback<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>callback<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">// this handle clears the timeout</span><br /> <span class="token keyword">const</span> clear <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// this handle sets our timeout</span><br /> <span class="token keyword">const</span> set <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// but clears the old one first</span><br /> <span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> timeoutId<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> savedCallback<span class="token punctuation">.</span><span class="token function">current</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> delay<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>delay<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token comment">// also, clear the timeout on unmount</span><br /> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> clear<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span> set<span class="token punctuation">,</span> clear <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>We can finally call <code>timeout.set()</code> and just have it <code>setTimeout</code> for us and do nothing else. I've left the original <code>savedCallback</code> logic intact, nothing wrong with it.</p>
<blockquote>
<p>The hook behavior in some corner cases has changed. If I set the timeout to 300ms, and then 200ms later change the delay to 50ms, should it fire in 300 – 200 = 100ms, as originally intended (my behavior)? 50ms from now (Dan's behavior)? 50 – 200 = 150ms ago (haha, that's very correct but you can't do that)? RIGHT NOW if we're already past deadline? Who knows. All options are fine for such a weird case as long as it doesn't explode.</p>
</blockquote>
<p>But now our <code>Input</code> has to wrangle with the nasty <em>imperatives,</em> and it probably looks awful. Not at all:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Input</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> details <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>showDetails<span class="token punctuation">,</span> setShowDetails<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> showTimeout <span class="token operator">=</span> <span class="token function">useImperativeTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setShowDetails</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> onEnter <span class="token operator">=</span> showTimeout<span class="token punctuation">.</span>set<span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onLeave</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> showTimeout<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">setShowDeatils</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span><br /> <span class="token attr-name">onMouseEnter</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onEnter<span class="token punctuation">}</span></span><br /> <span class="token attr-name">onMouseLeave</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onLeave<span class="token punctuation">}</span></span><br /> <span class="token punctuation">></span></span><span class="token plain-text">i</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>In fact, we've not only eliminated the extra render, but also removed the <code>hovered</code> state whose only job was to toggle the timeout. I'd say good old imperatives just scored a goal.</p>
<h2>Were we imperative all along?</h2>
<p>Upon closer inspection, our initial <em>"declarative"</em> <code>useTimeout</code> is not that declarative. Take note:</p>
<ul>
<li><code>onMouseOver</code> event handler is imperative,</li>
<li><code>setHovered</code> is imperative — even grammatically, I sometimes say "come on React, <em>set hovered</em> to true",</li>
<li><code>setTimeout</code> is imperative, too.</li>
</ul>
<p>We're basically converting these imperative things into the declarative world, then back again.</p>
<p>Moreover, the mental model is slightly broken — while <code>hovered</code> flag supposedly means "timeout is running", it may not be the case. The timeout is either running or has already fired. But maybe that's just me being tedious.</p>
<h2>What declarative can't do</h2>
<p>Now suppose I want to implement a debounce with the <em>declarative useTimeout.</em> I want to track my user's mouse motion, and show a popup once he stops moving. For that, I normally set a small timeout to show the popup — 30ms will do — on <code>mousemove</code>. If the user moves the mouse again within the next 30ms, well, I set another timeout and try again. If the mouse stops, the timeout successfully fires, and the popup appears. Really simple (no React yet):</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> popupTimeout <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br />img<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'mousemove'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">clearTimeout</span><span class="token punctuation">(</span>popupTimeout<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> popupTimeout <span class="token operator">=</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>showPopup<span class="token punctuation">,</span> <span class="token number">30</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>But the only way to set our <em>decalrative useTimeout</em> is passing a non-null delay. How would you do this with our declarative timeout?</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Img</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> title<span class="token punctuation">,</span> <span class="token operator">...</span>props <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>hasPopup<span class="token punctuation">,</span> setPopup<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setPopup</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token operator">??</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> onMove <span class="token operator">=</span> <span class="token operator">??</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</span> <span class="token attr-name">onMouseMove</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onMove<span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>hasPopup <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">{</span>title<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>You could move the delay a little bit, like 30 -> 31 -> 30, or dance around with 30 -> null -> 30, but that's just dirty. In any case, <code>mousemove</code> is absolutely not the event you'd want to re-render on.</p>
<p>Imperative timeout to the rescue:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Img</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> title<span class="token punctuation">,</span> <span class="token operator">...</span>props <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>hasPopup<span class="token punctuation">,</span> setPopup<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> popupTimeout <span class="token operator">=</span> <span class="token function">useImperativeTimeout</span><span class="token punctuation">(</span><br /> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setPopup</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token number">30</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> onMove <span class="token operator">=</span> popupTimeout<span class="token punctuation">.</span>set<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</span> <span class="token attr-name">onMouseMove</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onMove<span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>hasPopup <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">{</span>title<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>It works, it's fast, it's simple. 2:0 in favor of old school!</p>
<h2>How we can have it all</h2>
<p>Before you point this out to me, I'd love to quote the original article's disclaimer myself: <em>This post focuses on a pathological case. Even if an API simplifies a hundred use cases, the discussion will always focus on the one that got harder.</em> I'll be the first to admit I'm now exploring a pathological case of a pathological case. Know why? Because that's the kind of stuff I enjoy.</p>
<p>Problem is, the fully declarative API most hooks offer is on a higher level of abstraction than imperative handles. JS culture of making lower-lever building blocks inaccessible to the library users has bothered me for a long time (ouch, I still remember that time I copy-pasted react-router source to modify link actions for an electron app). But I think this culture has probably peaked in hooks.</p>
<p>Declarative timeout is very convenient in many cases:</p>
<ul>
<li>If many different things can set a timeout — like maybe a <code>mousedown</code>, but also a <code>keydown</code> — separating cause and effect with an intermediate state works great.</li>
<li>If you're going to use the state for other things, you still need to re-render, so there's no <em>wasted</em> render.</li>
</ul>
<p>But, as we've seen, it makes some other cases impossibly difficult, and can introduce wasted renders.</p>
<p>What if we could have the best of both worlds — provide a nice declarative API for 90% use cases, and also an imperative one to please old grumpy people like me? Yes we can:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">useWrapTimeout</span><span class="token punctuation">(</span><span class="token parameter">callback<span class="token punctuation">,</span> delay</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> handle <span class="token operator">=</span> <span class="token function">useImperativeTimeout</span><span class="token punctuation">(</span>callback<span class="token punctuation">,</span> delay<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>delay <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> handle<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> handle<span class="token punctuation">.</span>clear<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>delay<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>This is what you think it is — the declarative timeout, built on top of our imperative timeout. Works absolutely the same. We could even expose <em>both</em> APIs from a single hook (just <code>return handle</code>), but the interaction between the declarative state and imperative overrides is not pleasant. On the other hand, declarative timeout can't be used to build an imperative timeout, period.</p>
<hr />
<p>A traditional recap:</p>
<ul>
<li>Hooks without an imperative API make re-rendering the only way to communicate with the hook, which is wasteful.</li>
<li>Re-rendering a component and checking if some variable has changed since last render <em>is</em> a convoluted way to call a function.</li>
<li>Communicating between imperative actions (event -> setTimeout call) through a declarative value is not always possible.</li>
<li>Imperative APIs can be harder to work with, but are also more flexible.</li>
<li>You can build declarative APIs on top of imperative ones, but not the other way around.</li>
</ul>
<p>Dear library authors, please do expose lower-level APIs. Don't make me copy-paste your code to do things a little differently from the 95% use case.</p>
<p>Want to learn more about pathological cases in React hooks? <a href="https://thoughtspile.github.io/tags/hooks/">I have a lot of that.</a> See you around!</p>
Are many useStates better than useState(object)?2021-10-11T00:00:00Zhttps://thoughtspile.github.io/2021/10/11/usestate-object-vs-multiple/<p>Lately I've converted <em>a lot of</em> class components to functional. One question left me curious every time — why do I feel like splitting the old class <code>state</code> into so many <code>useState(atom)</code> — one for each state key? Is there any real benefit in it? Should I just leave a single <code>useState(whatever this.state was)</code> to touch as little code as possible during refactoring? Today, we'll discuss if having many <code>useState(atom)</code> is better than one single <code>useState(object)</code> — and, exactly, why. (Spoiler: it depends).</p>
<h2>Collection => object state</h2>
<p>To get the obvious out of the way: if your state is a collection — multiple similar values, probably dynamic quantity, normally in an array or a key:value object — you have to use object state. Someone with a dirty mind could work around that with serialization or recursive components, but let's not go there.</p>
<h2>State decomposition</h2>
<p>Common architectural sense tells us to split totally unrelated pieces of state into multiple <code>useStates</code>. The ability of separating state like that is one of the better features hooks offer. If you have a component that tracks input value, but <em>also</em> happens to track wheter the input has focus, go ahead and separate focus-state and value-state:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// no</span><br /><span class="token keyword">const</span> <span class="token punctuation">[</span><span class="token punctuation">{</span> value<span class="token punctuation">,</span> isFocused <span class="token punctuation">}</span><span class="token punctuation">,</span> setState<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">isFocused</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token operator"><</span>input<br /> value<span class="token operator">=</span><span class="token punctuation">{</span>state<span class="token punctuation">.</span>value<span class="token punctuation">}</span><br /> data<span class="token operator">-</span>focus<span class="token operator">=</span><span class="token punctuation">{</span>isFocused<span class="token punctuation">}</span><br /> onChange<span class="token operator">=</span><span class="token punctuation">{</span><span class="token parameter">e</span> <span class="token operator">=></span> <span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">value</span><span class="token operator">:</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">,</span> isFocused <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span><br /> onFocus<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> value<span class="token punctuation">,</span> <span class="token literal-property property">isFocused</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span><br /> onBlur<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> value<span class="token punctuation">,</span> <span class="token literal-property property">isFocused</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span><br /><span class="token operator">/</span><span class="token operator">></span><br /><br /><span class="token comment">// yes</span><br /><span class="token keyword">const</span> <span class="token punctuation">[</span>isFocused<span class="token punctuation">,</span> setFocused<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token punctuation">[</span>value<span class="token punctuation">,</span> setValue<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token operator"><</span>input<br /> value<span class="token operator">=</span><span class="token punctuation">{</span>value<span class="token punctuation">}</span><br /> data<span class="token operator">-</span>focus<span class="token operator">=</span><span class="token punctuation">{</span>isFocused<span class="token punctuation">}</span><br /> onChange<span class="token operator">=</span><span class="token punctuation">{</span><span class="token parameter">e</span> <span class="token operator">=></span> <span class="token function">setValue</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">}</span><br /> onFocus<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setFocused</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">}</span><br /> onBlur<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setFocused</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">}</span><br /><span class="token operator">/</span><span class="token operator">></span></code></pre>
<p>I'm getting a bit ahead of myself, but the second variant is shorter and looks clearer to me. You also get to use the extra convenience useState provides for atomic values (more on that in a moment). Also, if at some point you decide to extract focus-management into a custom hook, you're well prepared.</p>
<p>— But wait, Vladimir, didn't you just tell us to <a href="https://thoughtspile.github.io/2021/10/04/react-context-dangers">wrap context value in an object,</a> even if it has a single item?</p>
<p>— Yes, but this time it's different! To change context value from atom to an object, you must edit all the places where you read the context — that's your whole codebase. Component state is local, so it takes a single destructuring — <code>[value, setValue] = useState(0) -> [{ value }, setValue] = useState({ value: 0 })</code>. Besides, unlike context, state shape is not likely to be your public API.</p>
<h2>useState(atom) benefits</h2>
<p>Now let's see, exactly, why useState works better with atomic values.</p>
<h3>Convention</h3>
<p>The feeling that <code>useState</code> should be used with atoms is there for a reason — the API is designed to push you towards this idea.</p>
<p>First, <code>setState({ value: 0 })</code> sets the state to that exact object — <code>{ 'value': 0 }</code>. Class component's <code>this.setState({ value: 0 })</code> will merge the update with the current state. You can <a href="https://ru.react.js.org/docs/hooks-reference.html#usestate">mimic this behavior</a> with a spread: <code>setState({ ...state, value: 0 })</code>, but note how you're fighting react. Manually constructing the next state object without spreads: <code>setState({ focus, value: 0 })</code> is explosive — it's easy to miss an update site if you're adding a new key to your state and erase a part of the state.</p>
<p>Next, as explained in my post on <a href="https://thoughtspile.github.io/2021/09/27/usestate-tricks">useState tricks</a>, <code>setState(value)</code> does nothing when value is equal to the current state. Working with atomic values makes it trivial to use this feature, because atoms are compared by value. By contrast, <code>this.setState({ value })</code> in a <code>PureComponent</code> is followed by a shallow object equality check.</p>
<p>So, while class components were designed to work best with objects (indeed, component state is always an object), <code>useState</code> has a speacial optimization for atomic values, and does includes no conveniences for working with objects. You can work around both issues with a custom hook, but why fight React if it politely asks you to prefer atomic state?</p>
<h3>Bundle size</h3>
<p>One possible advantage of not using objects is the reduced bundle size — the <a href="https://reactjs.org/docs/hooks-intro.html">original hooks announcement</a> by React team specifiaclly mentioned that <em>classes don’t minify very well.</em> Let's pass our sample component that tracks focus and value through the normal build toolchain — babel + terser + gzip, and see if that's true.</p>
<p>Looking at the minified object-state variant, we can see that the minifier can't do anything about the keys of our object. Terser is very smart, but it has no idea if <code>isFocused</code> and <code>value</code> keys mean anything to that <code>useState</code> function we're passing our object through, so it can't mangle the keys. Note, however, that this has nothing to do with <em>classes</em> — any object has the same problem. Here's the component — It's 338 bytes raw, and 128 bytes under gzip:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">var</span> t<span class="token operator">=</span><span class="token function">e</span><span class="token punctuation">(</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token literal-property property">isFocused</span><span class="token operator">:</span><span class="token operator">!</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token literal-property property">value</span><span class="token operator">:</span><span class="token string">""</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">,</span>n<span class="token operator">=</span>t<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span>r<span class="token operator">=</span>n<span class="token punctuation">.</span>value<span class="token punctuation">,</span>o<span class="token operator">=</span>n<span class="token punctuation">.</span>isFocused<span class="token punctuation">,</span>u<span class="token operator">=</span>t<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">return</span> React<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">"input"</span><span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token literal-property property">value</span><span class="token operator">:</span>state<span class="token punctuation">.</span>value<span class="token punctuation">,</span><span class="token string-property property">"data-focus"</span><span class="token operator">:</span>o<span class="token punctuation">,</span><span class="token function-variable function">onChange</span><span class="token operator">:</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token function">u</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token literal-property property">value</span><span class="token operator">:</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">,</span><span class="token literal-property property">isFocused</span><span class="token operator">:</span>o<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token function-variable function">onFocus</span><span class="token operator">:</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token function">u</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token literal-property property">value</span><span class="token operator">:</span>r<span class="token punctuation">,</span><span class="token literal-property property">isFocused</span><span class="token operator">:</span><span class="token operator">!</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token function-variable function">onBlur</span><span class="token operator">:</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token function">u</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token literal-property property">value</span><span class="token operator">:</span>r<span class="token punctuation">,</span><span class="token literal-property property">isFocused</span><span class="token operator">:</span><span class="token operator">!</span><span class="token number">1</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre>
<p>Now let's try the object-free version. It doesn't pass the state object anywhere, and symbolic variable names are successfully mangled:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">var</span> t<span class="token operator">=</span><span class="token function">e</span><span class="token punctuation">(</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">,</span>n<span class="token operator">=</span>t<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span>r<span class="token operator">=</span>t<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span>o<span class="token operator">=</span><span class="token function">e</span><span class="token punctuation">(</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">,</span>u<span class="token operator">=</span>o<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span>i<span class="token operator">=</span>o<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">return</span> React<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">"input"</span><span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token literal-property property">value</span><span class="token operator">:</span>u<span class="token punctuation">,</span><span class="token string-property property">"data-focus"</span><span class="token operator">:</span>n<span class="token punctuation">,</span><span class="token function-variable function">onChange</span><span class="token operator">:</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token function">i</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token function-variable function">onFocus</span><span class="token operator">:</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token function">r</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token function-variable function">onBlur</span><span class="token operator">:</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token function">r</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre>
<p>This minified component is 273 bytes. So, case solved — at 65 bytes, or 20% off, atoms win, objects suck, right? Not so fast: the gzip size is 112 bytes, only 16 bytes / 12.5% smaller, and that's an abyssmal difference, especially in absolute terms.</p>
<p>In case you're curious, I included React in both bundles to gize gzip some warm-up data. I also transpiled down to IE11. Have fun with your own measurements if you feel I missed something!</p>
<p>So, you'd have to try very hard, with hundreds of components, to get any meaningful post-gzip bundle size reduction from using atomic state over objects. Still, the difference exists, so that's half a point to atoms.</p>
<h2>Should you ever useState(object)?</h2>
<p>So far, we've seen that multiple <code>useState(atom)</code> work well for breaking state into independent fragments. Atomic state is often more convenient, more conventional and gives you a slightly smaller bundle. So, are there any reasons to use object state in hooks, other than managing collections? There is a couple.</p>
<h3>Update batching</h3>
<p>As we've discussed <a href="https://thoughtspile.github.io/2021/09/27/usestate-tricks">before</a>, React <18 will not batch state updates from outside event handlers. Let's look at a familiar data-fetch example:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Hints</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>isLoading<span class="token punctuation">,</span> setLoading<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>hints<span class="token punctuation">,</span> setHints<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/hints'</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">data</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setHints</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">setLoading</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>isLoading<br /> <span class="token operator">?</span> <span class="token string">'loading...'</span> <span class="token operator">:</span><br /> hints<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">h</span> <span class="token operator">=></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span><span class="token punctuation">></span></span><span class="token punctuation">{</span>h<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>The component mounts with a loading indicator, calls an API endpoint, then disables the loader and shows some data once loaded. The only problem here is that since <code>loading</code> and <code>hints</code> are set via 2 different state updates from a promise (that's not an event handler), you end up rendering and modifying the DOM twice after load.</p>
<p>Grouping the loading flag and data into an object allows us to update the state in one call, eliminating the extra render:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span><span class="token punctuation">{</span> isLoading<span class="token punctuation">,</span> hints <span class="token punctuation">}</span><span class="token punctuation">,</span> setSuggest<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">isLoading</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">hints</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/hints'</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">data</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setSuggest</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">hints</span><span class="token operator">:</span> data<span class="token punctuation">,</span><br /> <span class="token literal-property property">isLoading</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Granted, you can also work around this issue while keeping your state split with a scary-sounding <code>unstable_batchedUpdates</code> from <code>react-dom</code>:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span>isLoading<span class="token punctuation">,</span> setLoading<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token punctuation">[</span>hints<span class="token punctuation">,</span> setHints<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/hints'</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">data</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// triggers just one render</span><br /> <span class="token function">unstable_batchedUpdates</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setHints</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">setLoading</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Still, I'd prefer grouping state in an object over using <code>unstable_</code> things and trying not to forget it every time I update the state. That's one use case where wrapping related state in an object makes sense — until react 18, it produces fewer renders when updating these related values.</p>
<h3>Arrow updates</h3>
<p><a href="https://thoughtspile.github.io/2021/09/27/usestate-tricks">If you recall,</a> <code>useState</code> allows you to update state using a callback AKA <em>mini-reducer.</em> The callback gets the current value as an agrument. We can use it to avoid data fetch race condition in a typeahead:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Hints</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>search<span class="token punctuation">,</span> setSearch<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">query</span><span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">hints</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/hints/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>search<span class="token punctuation">.</span>query<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">hints</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setSearch</span><span class="token punctuation">(</span><span class="token parameter">s</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>s<span class="token punctuation">.</span>query <span class="token operator">!==</span> search<span class="token punctuation">.</span>query<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// skip the update if query has changed</span><br /> <span class="token keyword">return</span> s<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>search<span class="token punctuation">,</span> hints <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>search<span class="token punctuation">.</span>query<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span><br /> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>state<span class="token punctuation">.</span>query<span class="token punctuation">}</span></span><br /> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token parameter">e</span> <span class="token operator">=></span> <span class="token function">setSearch</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span>search<span class="token punctuation">,</span> <span class="token literal-property property">query</span><span class="token operator">:</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><br /> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>state<span class="token punctuation">.</span>hints<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">h</span> <span class="token operator">=></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span><span class="token punctuation">></span></span><span class="token punctuation">{</span>h<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Here, we look at the <em>current</em> query after loading the hints, and only show the hints we loaded if the query has not changed since. Not the most elegant solution, but it works, and so it's a valid state model. If you were to split query and hints into separate states, you'd lose the ability to read current query when setting hints, and have to solve this problem some other way.</p>
<p>More generally (maybe too generally), if updates to state B depend on state A, states A and B should probably we wrapped in an object.</p>
<h2>Appendix A: useObjectState</h2>
<p>I promised you can have all the convenience of class <code>setState</code> in a custom hook. Here we go:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">useObjectState</span><span class="token punctuation">(</span><span class="token parameter">init</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">useReducer</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">s<span class="token punctuation">,</span> patch</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> changed <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span>patch<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">[</span>k<span class="token punctuation">,</span> v<span class="token punctuation">]</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> s<span class="token punctuation">[</span>k<span class="token punctuation">]</span> <span class="token operator">!==</span> v<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> changed <span class="token operator">?</span> <span class="token punctuation">{</span> <span class="token operator">...</span>s<span class="token punctuation">,</span> <span class="token operator">...</span>patch <span class="token punctuation">}</span> <span class="token operator">:</span> s<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> init<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Here, we merge old and new state, and also preserve the old state object reference if the patch contains no changes. Easy breezy.</p>
<h2>Appendix B: Runtime performance</h2>
<p>For a tie-breaker, let's see if the amount of <code>useState</code> calls impacts your application performance.</p>
<p>I expect the runtime performance difference between single object state and multiple atomic states to be even more negligible than that of bundle size. Still, the fact that it could go both ways is made me curious: object state allocates an extra object (or function, with a lazy initializer) on every render, but atoms call more react internals. Is there a winner?</p>
<p>I've made a tiny <a href="https://jsbench.me/qvkumpoejp/1">benchmark</a> comparing several useState calls, single <code>useState(object)</code> call and single <code>useState(() => lazy object)</code>. The results are available in a <a href="https://docs.google.com/spreadsheets/d/1IpPuc77S9UODSpLELqGSy-_06xYo11BIEcHVadId1H0/edit?usp=sharing">google sheet</a>. I've also made a nice chart that shows percent increase in mount time over baseline — no hooks, just a stateless render:</p>
<p><img src="https://thoughtspile.github.io/images/usestate-perf.png?invert" alt="" /></p>
<p>I wouldn't dare interpret these results given how cool optimizing compilers are, but the general as I see it pattern makes sense:</p>
<ul>
<li>1 <code>useState</code> with atom is slightly better than with object because we skip object allocation.</li>
<li><code>useState</code> calls are more expensive than object allocations, so for 3+ items <code>useState(object)</code> wins.</li>
<li>Lazy initializer beats object creation — not sure why, if the initializer is always called on mount.</li>
</ul>
<p>Note that the difference here is in sub-microsecond range (yes, MICROsecond, 1/1000th of a millisecond, or 1/16000th of a 60FPS frame), so any practical implications are laughable. Still, good to know that using hooks is almost free.</p>
<hr />
<p>So, useState is probably better suited for storing atomic values, but object state still has its uses. Here's what we learnt:</p>
<ul>
<li><code>useState</code> update handle skips re-render by checking for <code>===</code> equality, and that's easier to achieve with atomic values.</li>
<li><code>useState</code> has no built-in object merging mechanism.</li>
<li>Atomic state makes your bundle a <em>little bit</em> smaller, because object keys are hard to mangle.</li>
<li>Collection state only works as an object.</li>
<li>Until React 18, async updates to several <code>useStates</code> result in useless renders. Use object state or <code>unstable_batchedUpdates</code> to render once.</li>
<li>You can't access the current state of another <code>useState</code> in a state update callback (ouch, that's a complex statement with many states involved) — use object state for values that depend on each other during update.</li>
<li>Any performace difference between <code>useState</code> variants is negligible.</li>
</ul>
<p>I feel the deciding factor here is state modelling — grouping several state items in an object signals that they are closely related, while splitting them apart shows they are orthogonal. Please model your state based on common sense, not some prejudices agains objects. Ah, and also — everything we just discussed also applies to <code>useReducer</code>, because <code>useState</code> is <code>useReducer</code>. Good luck and see you next time!</p>
Is your babel's transform-runtime getting lazy? You better check.2021-10-06T00:00:00Zhttps://thoughtspile.github.io/2021/10/06/babel-runtime-version/<p>IE11 is not dead yet, and our library is supposed to run there and make russian grandmas happy. As you can guess, we rely on babel's <a href="https://babeljs.io/docs/en/babel-preset-env">preset-env</a> a lot. We also don't want our code to be 55% babel helpers, so we use babel's <a href="https://babeljs.io/docs/en/babel-plugin-transform-runtime">transform-runtime</a> — it should make babel <code>import someHelper from '@babel/runtime/some-helper'</code> instead of inlining it into every file. After making some build chain updates I went to see if the transpiled version was OK. And guess what I noticed? Some babel helpers were still there, inlined:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> _defineProperty <span class="token keyword">from</span> <span class="token string">"@babel/runtime/helpers/defineProperty"</span><span class="token punctuation">;</span><br /><span class="token comment">// go away transform do you have any idea who I am?</span><br /><span class="token keyword">function</span> <span class="token function">ownKeys</span><span class="token punctuation">(</span><span class="token parameter">object<span class="token punctuation">,</span> enumerableOnly</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* blah-blah-blah */</span> <span class="token punctuation">}</span><br /><span class="token keyword">function</span> <span class="token function">_objectSpread</span><span class="token punctuation">(</span><span class="token parameter">target</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* more blah-blah-blah */</span> <span class="token punctuation">}</span><br /><br /><span class="token keyword">var</span> <span class="token function-variable function">copy</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token function">copy</span><span class="token punctuation">(</span><span class="token parameter">obj</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">_objectSpread</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> options<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>WTF? I want to <code>import _objectSpread</code>, you lazy code! What's wrong with you? A leak from an external library? An unexpected interaction with <code>preset-react</code> or <code>preset-typescript</code>? Corrupt installation? Babel bugs? No, no, no, no.</p>
<p><img src="https://thoughtspile.github.io/images/sherlock.jpg" alt="" /></p>
<p>The answer was simple — transform-runtime wants me to tell it what <code>@babel/runtime</code> version I have via <a href="https://thoughtspile.github.io/2021/10/06/babel-runtime-version/%60@babel/runtime%60">the <code>version</code> option</a>. For some reason, transform-runtime assumes you have <code>@babel/runtime</code> version <code>7.0.0</code>, and if a helper was not in <code>runtime@7.0.0</code>, it won't bother importing it. Babel is at <code>7.15.x</code> now, and a lot has changed. Anyways, if you pass the real runtime version you installed:</p>
<pre class="language-js"><code class="language-js">exports <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token string-property property">"plugins"</span><span class="token operator">:</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">[</span><span class="token string">"@babel/plugin-transform-runtime"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><br /> <span class="token comment">// this is the magic line</span><br /> <span class="token string-property property">"version"</span><span class="token operator">:</span> <span class="token string">"7.15.0"</span><br /> <span class="token punctuation">}</span><span class="token punctuation">]</span><br /> <span class="token punctuation">]</span><br /><span class="token punctuation">}</span></code></pre>
<p><code>transform-runtime</code> will finally do its job as it should:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> _objectSpread <span class="token keyword">from</span> <span class="token string">"@babel/runtime/helpers/objectSpread2"</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> copy <span class="token operator">=</span> <span class="token function">_objectSpread</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> props<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>If you'd rather do it once, use <code>babel.config.js</code> and read the runtime version from <code>package.json</code> — both your dependency range and the version in node_modules work fine, though I feel the latter is cleaner:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// in babel.config.js</span><br /><span class="token keyword">const</span> requiredVersion <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./package.json'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>dependencies<span class="token punctuation">[</span><span class="token string">'@babel/runtime'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> installedVersion <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@babel/runtime/package.json'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>version<span class="token punctuation">;</span></code></pre>
<hr />
<p>If you want your <code>@babel/plugin-transform-runtime</code> not to get lazy and really deduplicate all the helpers, set transform-runtime's <code>version</code> option to the current <code>@babel/runtime</code> version. Also keep your babel stack updated, and try to match <code>@babel/*</code> versions. You're welcome.</p>
How to destroy your app performance using React contexts2021-10-04T00:00:00Zhttps://thoughtspile.github.io/2021/10/04/react-context-dangers/<p><code>useContext</code> hook has made React Context API so pleasant to work with that many people are even <a href="https://www.sitepoint.com/replace-redux-react-hooks-context-api/">suggesting</a> that we drop external state management solutions and rely on the built-in <em>alternative</em> instead. This is dangerous thinking that can easily push your app's performance down the drain if you're not careful. In this article, I explore the perils of using contexts, and provide several tips to help you optimize context usage. Let's go!</p>
<p><img src="https://thoughtspile.github.io/images/die-redux.png" alt="" /></p>
<h2>Context change re-renders every consumer</h2>
<p>We're building a library of react components, and sometimes the design depends on viewport size. Most of the time breakpoint status (mobile / desktop) is enough, but in some cases we need the exact pixel size. We store that data in a context:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> AdaptivityContext <span class="token operator">=</span> <span class="token function">useContext</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">AdaptivityProvider</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>width<span class="token punctuation">,</span> setWidth<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span>window<span class="token punctuation">.</span>innerWidth<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onResize</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setWidth</span><span class="token punctuation">(</span>window<span class="token punctuation">.</span>innerWidth<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'resize'</span><span class="token punctuation">,</span> onResize<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> window<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'resize'</span><span class="token punctuation">,</span> onResize<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> adaptivity <span class="token operator">=</span> <span class="token punctuation">{</span><br /> width<span class="token punctuation">,</span><br /> <span class="token literal-property property">isMobile</span><span class="token operator">:</span> width <span class="token operator"><=</span> <span class="token number">680</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">return</span> <span class="token operator"><</span>AdaptivityContext<span class="token punctuation">.</span>Provider value<span class="token operator">=</span><span class="token punctuation">{</span>adaptivity<span class="token punctuation">}</span><span class="token operator">></span><br /> <span class="token punctuation">{</span>props<span class="token punctuation">.</span>children<span class="token punctuation">}</span><br /> <span class="token operator"><</span><span class="token operator">/</span>AdaptivityContext<span class="token punctuation">.</span>Provider<span class="token operator">></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Life's good: instead of wrangling with <code>window.innerWidth</code> and global event listeners in every component, we can just read the context and get automatic updates. Here's for a single-breakpoint design:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">InfoBar</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> text<span class="token punctuation">,</span> info <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> isMobile <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useContext</span><span class="token punctuation">(</span>AdaptivityContext<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>text<span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>isMobile <span class="token operator">?</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>i</span> <span class="token attr-name">title</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>info<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span> <span class="token operator">:</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>small</span><span class="token punctuation">></span></span><span class="token punctuation">{</span>info<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>small</span><span class="token punctuation">></span></span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>And here's for pixel-width:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">FullWidth</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> width <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useContext</span><span class="token punctuation">(</span>AdaptivityContext<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">position</span><span class="token operator">:</span> <span class="token string">'fixed'</span><span class="token punctuation">,</span> <span class="token literal-property property">left</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> width <span class="token punctuation">}</span><span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>But there's a catch. If we resize the window a little bit without crossing the 620px breakpoint, both components will re-render, since <code>useContext</code> subscribes to context value changes, and doesn't care that you use only a part of that value that didn't change (<code>isMobile</code>). Of course, <code>InfoBar</code> does not actually depend on <code>width</code>, and React will not touch the DOM, but I'd still much prefer not trying to re-render it at all.</p>
<h3>Rule 1: make smaller contexts</h3>
<p>In this case, the fix is fairly easy. We can split the original <code>AdaptivityContext</code> into two parts, so that every component can explicitly state if it depends on <code>width</code> or the breakpoint:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> SizeContext <span class="token operator">=</span> <span class="token function">useContext</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> MobileContext <span class="token operator">=</span> <span class="token function">useContext</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">AdaptivityProvider</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>width<span class="token punctuation">,</span> setWidth<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span>window<span class="token punctuation">.</span>innerWidth<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">onResize</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setWidth</span><span class="token punctuation">(</span>window<span class="token punctuation">.</span>innerWidth<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'resize'</span><span class="token punctuation">,</span> onResize<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> window<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'resize'</span><span class="token punctuation">,</span> onResize<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> isMobile <span class="token operator">=</span> width <span class="token operator"><=</span> <span class="token number">680</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token operator"><</span>SizeContext<span class="token punctuation">.</span>Provider value<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> width <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">></span><br /> <span class="token operator"><</span>MobileContext<span class="token punctuation">.</span>Provider value<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> isMobile <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">></span><br /> <span class="token punctuation">{</span>props<span class="token punctuation">.</span>children<span class="token punctuation">}</span><br /> <span class="token operator"><</span><span class="token operator">/</span>MobileContext<span class="token punctuation">.</span>Provider<span class="token operator">></span><br /> <span class="token operator"><</span><span class="token operator">/</span>SizeContext<span class="token punctuation">.</span>Provider<span class="token operator">></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Now we can <code>{ width } = useContext(SizeContext)</code>, <code>{ isMobile } = useContext(MobileContext)</code>, or even both. The code is a little more verbose, but the change is worth it: if a component relies on <code>MobileContext</code>, it does not re-render on <code>width</code> change. Or does it? My bad:</p>
<ul>
<li>We create a new context-value object on every render</li>
<li><code>setWidth</code> triggers a re-render</li>
<li>Therefore, <code>setWidth</code> creates new MobileContext value</li>
<li>Since <code>MobileContext</code> value changed by reference, every <code>MobileContext</code> consumer re-renders.</li>
</ul>
<p>We need a fix.</p>
<h3>Rule 2: stabilize context values</h3>
<p>Context tracks value, object or not, using simple equality. This means that we have to stabilize object reference ourselves:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> sizeContext <span class="token operator">=</span> <span class="token function">useMemo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> width <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>width<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> mobileContext <span class="token operator">=</span> <span class="token function">useMemo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> isMobile <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>isMobile<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token operator"><</span>SizeContext<span class="token punctuation">.</span>Provider value<span class="token operator">=</span><span class="token punctuation">{</span>sizeContext<span class="token punctuation">}</span><span class="token operator">></span><br /> <span class="token operator"><</span>MobileContext<span class="token punctuation">.</span>Provider value<span class="token operator">=</span><span class="token punctuation">{</span>mobileContext<span class="token punctuation">}</span><span class="token operator">></span><br /> <span class="token punctuation">{</span>props<span class="token punctuation">.</span>children<span class="token punctuation">}</span><br /> <span class="token operator"><</span><span class="token operator">/</span>MobileContext<span class="token punctuation">.</span>Provider<span class="token operator">></span><br /> <span class="token operator"><</span><span class="token operator">/</span>SizeContext<span class="token punctuation">.</span>Provider<span class="token operator">></span><br /><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>If listing dependencies feels boring, try <code>useObjectMemo</code> hook I proposed in an <a href="https://blog.thoughtspile.tech/2021/04/05/useref-usememo/">earlier post</a>. Now, finally, the components that depend on <code>isMobile</code> only will not re-render on every width change.</p>
<h3>Rule 2, option b: Maybe use atomic context values</h3>
<p>Making context value an atomic type, not an object, may seem smart:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// ha, atomic types are compared by value</span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">SizeContext.Provider</span></span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>width<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"></span></code></pre>
<p>But what happens if we want to pass height? Changing SizeContext type to an object requires you to rewrite every <code>width = useContext(SizeContext)</code> to accept objects instead. Unpleasant, and impossible if <code>SizeContext</code> is your public API.</p>
<p>We can create a new <code>HeightContext</code>, but this quickly escalates into <strong>context hell</strong> with very little reward, since width and height tend to change together and you won't avoid many re-renders by observing only one of them.</p>
<p>I'd only use atomic types for context values if I'm absolutely sure there are no values with similar change patterns and use cases that I might want to pass along later.</p>
<h2>Rule 3: Make smaller context consumers</h2>
<p>On a side note, you can have a huge component that only has a few parts that depend on context. Re-rendering this component is hard even though the DOM change itself is small. Maybe something like a modal that only closes via gestures on mobile, but has a special close button on desktop:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Modal</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children<span class="token punctuation">,</span> onClose <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> isMobile <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useContext</span><span class="token punctuation">(</span>MobileContext<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// a lot of modal logic with timeouts, effects and stuff</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Modal<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span><span class="token comment">/* a lot of modal layout */</span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span><span class="token operator">!</span>isMobile <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Modal__close<span class="token punctuation">"</span></span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onClose<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Here, you could move the context usage to a separate component and re-render just the close icon on resize:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">ModalClose</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> isMobile <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useContext</span><span class="token punctuation">(</span>MobileContext<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> isMobile <span class="token operator">?</span> <span class="token keyword">null</span> <span class="token operator">:</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Modal__close<span class="token punctuation">"</span></span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onClose<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">Modal</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children<span class="token punctuation">,</span> onClose <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// a lot of modal logic with timeouts, effects and stuff</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Modal<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span><span class="token comment">/* a lot of modal layout */</span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">ModalClose</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Or you can use <code>Context.Consumer</code> without creating an extra component:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Modal</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children<span class="token punctuation">,</span> onClose <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// a lot of modal logic with timeouts, effects and stuff</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Modal<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span><span class="token comment">/* a lot of modal layout */</span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">MobileContext.Consumer</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> isMobile <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span><br /> isMobile <span class="token operator">?</span> <span class="token keyword">null</span> <span class="token operator">:</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Modal__close<span class="token punctuation">"</span></span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onClose<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">MobileContext.Consumer</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h2>Collection context</h2>
<p>A single-object context with pre-defined keys can be easily split into several parts. Sadly, this does not work for a <em>collection context</em> — when you have many dynamic items, and the consumer only depends on one of them. Let's kick off our second example with a smart form controller:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> FormState <span class="token operator">=</span> <span class="token function">createContext</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function-variable function">setValue</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">Form</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// collection of form item values</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>value<span class="token punctuation">,</span> setValue<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// basic submit handler</span><br /> <span class="token keyword">const</span> <span class="token function-variable function">handleSubmit</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> props<span class="token punctuation">.</span><span class="token function">onSubmit</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token comment">// stabilize the context object</span><br /> <span class="token keyword">const</span> contextValue <span class="token operator">=</span> <span class="token function">useMemo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span><br /> value<span class="token punctuation">,</span><br /> setValue<br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>value<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">FormState.Provider</span></span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>contextValue<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>form</span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token attr-name">onSubmit</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>handleSubmit<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">FormState.Provider</span></span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token comment">// only exposes a single item by name</span><br /><span class="token keyword">const</span> <span class="token function-variable function">useFormState</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">name</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> value<span class="token punctuation">,</span> setValue <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useContext</span><span class="token punctuation">(</span>FormState<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> onChange <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setValue</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span>v<span class="token punctuation">,</span> <span class="token punctuation">[</span>props<span class="token punctuation">.</span>name<span class="token punctuation">]</span><span class="token operator">:</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>props<span class="token punctuation">.</span>name<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">[</span>value<span class="token punctuation">[</span>name<span class="token punctuation">]</span><span class="token punctuation">,</span> onChange<span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">FormInput</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>value<span class="token punctuation">,</span> onChange<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useFormState</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>value<span class="token punctuation">}</span></span> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onChange<span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Looks neat! We can now put any markup in <code><Form></code>, and then bind to the form value using <code><FormItem></code>:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Form</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">FormInput</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>phone<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">FormInput</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>fieldset</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">FormInput</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>firstName<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">FormInput</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>lastName<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>fieldset</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">FormInput</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">submit</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">FormInput</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">Form</span></span><span class="token punctuation">></span></span></code></pre>
<p>Watch closely! <code>FormState</code> context changes on every form item change. <code>FormInput</code> uses the full <code>FormState</code> context. This means that every <code>FormItem</code> re-renders on every form item change, even though it only depends on <code>value[name]</code>. This time we can't give every form item an individual context, since the items can be highly dynamic. There's no easy fix this time, but let's see what we can do.</p>
<blockquote>
<p>Disclaimer: our incredible form has seven HTML elements, and updating the is a breeze for react. Please play along and pretend that <code>FormInput</code> is an incredibly tweakable synthetic field with icons and dropdowns, and we have 100 items in a form.</p>
</blockquote>
<h3>Tip: consider a HOC</h3>
<p>We can't prevent <code>useContext</code> from running the whole render function on every context change. What we can do instead is make the render function lighter and leverage <code>memo</code> to tell React not to re-render. It's similar to what we did the modal example, but the context-dependent part is the wrapper now, not the child. If you still remember, this pattern is called container / presentation (aka smart / dumb) components:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> FormItemDumb <span class="token operator">=</span> <span class="token function">memo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">FormItem</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>value<span class="token punctuation">,</span> onChange<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useFormState</span><span class="token punctuation">(</span>props<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">FormItemDumb</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>value<span class="token punctuation">}</span></span> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onChange<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>We still run the whole <code>FormItem</code> render on every context change, but now the <em>render</em> is just the <code>useContext</code> call. From there, <code>FormItemDumb</code> will see if the change was relevant, and skip re-rendering if it wasn't. Much better! Just for kicks, let's try again, with a higher-order component:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">FormItemDumb</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator"><</span>input <span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">withFormState</span> <span class="token operator">=</span> <span class="token parameter">Wrapped</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> PureWrapped <span class="token operator">=</span> <span class="token function">memo</span><span class="token punctuation">(</span>Wrapped<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>value<span class="token punctuation">,</span> onChange<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useFormState</span><span class="token punctuation">(</span>props<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token operator"><</span>PureWrapped <span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span> value<span class="token operator">=</span><span class="token punctuation">{</span>value<span class="token punctuation">}</span> onChange<span class="token operator">=</span><span class="token punctuation">{</span>onChange<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> FormItem <span class="token operator">=</span> <span class="token function">withFormState</span><span class="token punctuation">(</span>FormItemDumb<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><code>withFormState</code> can wrap any component, not only <code>input</code>, and gives us the same flexibility as <code>useFormState</code> hook, but without the extra re-renders.</p>
<h2>How the big guys do it</h2>
<p>People who write state management libraries, could benefit from context the most, and know the inner workings of react much better than you or me. Let's see how they approach these problems.</p>
<p><code>mobx</code> API for binding components is <code>observer(Component)</code>, which might lead you to believe it uses our HOC method, but it <a href="https://github.com/mobxjs/mobx/blob/main/packages/mobx-react-lite/src/observer.ts">actually doesn't</a>. Instead, it calls your component as a function, and then uses mobx dependency detection. No contexts involved at all — makes sense, since we didn't have a provider in the first place. But, fine, mobx is an oddball.</p>
<p>Redux seems to do things the react way, and <code>react-redux</code> <a href="https://react-redux.js.org/introduction/getting-started">does use</a> a <code>Provider</code> — maybe it knows a way to optimize context usage? Nope, <code>useSelector</code> subscribes to the store <a href="https://github.com/reduxjs/react-redux/blob/master/src/hooks/useSelector.ts">via a custom subscription</a> runs custom shallow comparison and only triggers a render if the selected fragment has changed. The context just injects the store instance.</p>
<p>OK, redux and mobx are mature libraries that don't pretend to be super tiny. Maybe newer state managers have fresh ideas. Zustand? <a href="https://github.com/pmndrs/zustand/blob/main/src/index.ts">Custom subscription.</a> Unistore? <a href="https://github.com/developit/unistore/blob/master/src/integrations/react.js">Custom subscription.</a> Unstated? <a href="https://github.com/jamiebuilds/unstated-next/blob/master/src/unstated-next.tsx">Raw context for hooks version,</a> but it's 200 bytes and it works.</p>
<p>So, none of the major state managers rely on the context API — not even those that could. They avoid the performance problems by using custom subscriptions and only updating if the <em>relevant</em> state has changed.</p>
<h2>The react future</h2>
<p>React core team is, of course, aware of this shortcoming — <a href="https://github.com/facebook/react/issues/14110">this issue</a> is an interesting read. Context API even had a weird <a href="https://hph.is/coding/bitmasks-react-context">observedBits feature,</a> but it's <a href="https://github.com/facebook/react/pull/20953">gone now.</a></p>
<p>The way forward appears to be <em>context selectors</em> — used like <code>useContext(Context, c => c[props.id])</code>. An <a href="https://github.com/reactjs/rfcs/pull/119">RFC</a> has been open since 2019, and an <a href="https://github.com/facebook/react/pull/20646">experimental PR implementing it</a> is in the works. Still, this feature is not coming in <a href="https://github.com/reactwg/react-18/discussions/73">react 18</a>. In the meantime, Daishi Kato has made two cool libraries: <a href="https://github.com/dai-shi/use-context-selector">use-context-selector</a>, that implements the RFC, and a proxy-based <a href="https://github.com/dai-shi/react-tracked">react-tracked</a>, to eliminate the wasted renders.</p>
<hr />
<p><code>Context</code> API is a nice feature, but, since <em>every</em> context update always re-renders <em>every</em> consumer of this context, may cause performance problems if not used carefully. To mitigate this:</p>
<ul>
<li>Move context values with different change patterns into separate contexts.</li>
<li>Always stabilize context value object reference or use atomic types.</li>
<li>Make components that use context as small as possible, so that their re-renders are fast.</li>
<li>Split a component into a HOC-like wrapper with <code>useContext</code>, and a simple renderer wrapped in <code>memo()</code></li>
<li>Look into <a href="https://github.com/dai-shi">dai-shi's amazing useContext wrappers.</a></li>
<li>Context is not suitable for complex state management. Try using a real state manager.</li>
</ul>
<p>As usual, have fun, make good apps, don't ride the hype train. If you like what I have to say about React, see if <a href="https://blog.thoughtspile.tech/2021/09/27/usestate-tricks/">setState has some features you don't know</a> (a big hit!) or <a href="https://blog.thoughtspile.tech/2021/09/21/useeffect-derived-state/">why you shouldn't setState in useLayoutEffect</a>.</p>
7 things you may not know about useState2021-09-27T00:00:00Zhttps://thoughtspile.github.io/2021/09/27/usestate-tricks/<p>Doing code reviews for our hook-based project, I often see fellow developers not aware of some awesome features (and nasty pitfalls) <code>useState</code> offers. Since it's one of my favourite hooks, I decided to help spread a word. Don't expect any huge revelations, but here're the 7 facts about <code>useState</code> that are essential for anyone working with hooks.</p>
<h2>Update handle has constant reference</h2>
<p>To get the obvious out of the way: the update handle (second array item) is the same function on every render. You don't need to include it in array dependencies, no matter what <a href="https://reactjs.org/docs/hooks-rules.html#eslint-plugin">eslint-plugin-react-hooks</a> has to say about this:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">[</span>count<span class="token punctuation">,</span> setCount<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> onChange <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// setCount never changes, onChange doesn't have to either</span><br /> <span class="token function">setCount</span><span class="token punctuation">(</span><span class="token function">Number</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h2>Setting state to the same value does nothing</h2>
<p><code>useState</code> is pure by default. Calling the update handle with a value that's equal (by reference) to the current value does nothing — no DOM updates, no wasted renders, nothing. Doing this yourself is useless:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">[</span>isOpen<span class="token punctuation">,</span> setOpen<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span>props<span class="token punctuation">.</span>initOpen<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// useState already does this for us</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isOpen<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">setOpen</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This doesn't work with shallow-equal objects, though:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">[</span><span class="token punctuation">{</span> isOpen <span class="token punctuation">}</span><span class="token punctuation">,</span> setState<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">isOpen</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// always triggers an update, since object reference is new</span><br /> <span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">isOpen</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<h2>State update handle returns undefined</h2>
<p>This means setState can be returned from effect arrows without triggering <em>Warning: An effect function must not return anything besides a function, which is used for clean-up.</em> These code snippets work the same:</p>
<pre class="language-js"><code class="language-js"><span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setOpen</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setOpen</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h2>useState <em>is</em> useReducer</h2>
<p>In fact, <code>useState</code> is implemented in React code like a <code>useReducer</code>, just with a pre-defined reducer, at least as of 17.0 — ooh yes I actually did check <a href="https://github.com/facebook/react/blob/82c8fa90be86fc0afcbff2dc39486579cff1ac9a/packages/react-reconciler/src/ReactFiberHooks.new.js#L1464">react source</a>. If anyone claims <code>useReducer</code> has a hard technical advantage over <code>useState</code> (reference identity, transaction safety, no-op updates, etc) — call him a liar.</p>
<h2>You can initialize state with a callback</h2>
<p>If creating a new state-initializer object on every render just to throw away is concerning to you, feel free to use the initializer function:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">[</span>style<span class="token punctuation">,</span> setStyle<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">transform</span><span class="token operator">:</span> props<span class="token punctuation">.</span>isOpen <span class="token operator">?</span> <span class="token keyword">null</span> <span class="token operator">:</span> <span class="token string">'translateX(-100%)'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">opacity</span><span class="token operator">:</span> <span class="token number">0</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>You can access props (or anything from the scope, really) in the initializer. Frankly, it looks like over-optimization to me — you're about to create a bunch of vDOM, why worry about one object? This may help with <em>heavy</em> initialization logic, but I have yet to see such case.</p>
<p>On a side note, if you want to put a function in your state (it's not forbidden, is it?), you have to wrap it in an extra function to bypass the lazy initializer logic: <code>useState(() => () => console.log('gotcha!'))</code></p>
<h2>You can update state with a callback</h2>
<p>Callbacks can also be used for updating state — like a mini-reducer, sans the action. This is useful since the <em>current state value</em> in your closure may not be the value if you've updated the state since rendering / memoizing. Better seen by example:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">[</span>clicks<span class="token punctuation">,</span> setClicks<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">onMouseDown</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// this won't work, since clicks does not change while we're here</span><br /> <span class="token function">setClicks</span><span class="token punctuation">(</span>clicks <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">setClicks</span><span class="token punctuation">(</span>clicks <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">onMouseUp</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// this will</span><br /> <span class="token function">setClicks</span><span class="token punctuation">(</span>clicks <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// see, we read current clicks here</span><br /> <span class="token function">setClicks</span><span class="token punctuation">(</span><span class="token parameter">clicks</span> <span class="token operator">=></span> clicks <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Creating constant-reference callbacks is more practical:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">[</span>isDown<span class="token punctuation">,</span> setIsDown<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// bad, updating on every isDown change</span><br /><span class="token keyword">const</span> onClick <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setIsDown</span><span class="token punctuation">(</span><span class="token operator">!</span>isDown<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>isDown<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// nice, never changes!</span><br /><span class="token keyword">const</span> onClick <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setIsDown</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> <span class="token operator">!</span>v<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h2>One state update = one render in async code</h2>
<p>React has a feature called <em>batching,</em> that forces multiple setState calls to cause <em>one</em> render, but is's not always on. Consider the following code:</p>
<pre class="language-js"><code class="language-js">console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'render'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token punctuation">[</span>clicks<span class="token punctuation">,</span> setClicks<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token punctuation">[</span>isDown<span class="token punctuation">,</span> setIsDown<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setClicks</span><span class="token punctuation">(</span>clicks <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">setIsDown</span><span class="token punctuation">(</span><span class="token operator">!</span>isDown<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>When you call <code>onClick</code>, the number of times you <code>render</code> depends on how, exactly, <code>onClick</code> is called (see <a href="https://codesandbox.io/s/setstate-multi-29z2u?file=/src/App.js">sandbox</a>):</p>
<ul>
<li><code><button onClick={onClick}></code> is batched as a React event handler</li>
<li><code>useEffect(onClick, [])</code> is batched, too</li>
<li><code>setTimeout(onClick, 100)</code> is <em>not</em> batched and causes an extra render</li>
<li><code>el.addEventListener('click', onClick)</code> is <em>not</em> batched</li>
</ul>
<p>This <a href="https://github.com/reactwg/react-18/discussions/21">should change</a> in React 18, and in the meantime you can use, ahem, <code>unstable_batchedUpdates</code> to force batching.</p>
<hr />
<p>To recap (as of v17.0):</p>
<ul>
<li><code>setState</code> in <code>[state, setState] = useState()</code> is the same function on every render</li>
<li><code>setState(currentValue)</code> does nothing, you can throw <code>if (value !== currentValue)</code> away</li>
<li><code>useEffect(() => setState(true))</code> does not break the effect cleanup function</li>
<li><code>useState</code> is implemented as a pre-defined reducer in react code</li>
<li>State initializer can be a calback: <code>useState(() => initialValue)</code></li>
<li>State update callback gets current state as an argument: <code>setState(v => !v)</code>. Useful for <code>useCallback</code>.</li>
<li>React <em>batches</em> multiple setState calls in React event listeners and effects, but not in DOM listeners or async code.</li>
</ul>
<p>Hope you've learnt something useful today! If exploring obscure react corners is your thing, see if there's <a href="https://thoughtspile.github.io/2021/05/17/everything-about-react-refs">something about DOM refs you didn't know</a> or <a href="https://thoughtspile.github.io/2021/04/05/useref-usememo">what useRef and useMemo have in common.</a></p>
Zero-setup bundle size checker2021-09-24T00:00:00Zhttps://thoughtspile.github.io/2021/09/24/quick-size-check/<p>We all love keeping bundle size under control. There are many great tools that help you with that — <a href="https://github.com/webpack-contrib/webpack-bundle-analyzer">webpack-bundle-analyzer</a>, <a href="https://github.com/siddharthkp/bundlesize">bundlesize</a>, <a href="https://github.com/ai/size-limit">size-limit</a>, what not. But sometimes you you're lazy, or you're stuck choosing the tool, or the project is too small to justify spending extra time. Don't worry, I'll show you a way to check bundle size without a single extra dependency on mac and linux!</p>
<h3>Raw bundle size</h3>
<p>To view the raw JS bundle size, just build your app (say, <code>npm run build</code>), and then (assuming your built files are in <code>./dist</code>) run this snippet:</p>
<pre class="language-sh"><code class="language-sh"><span class="token function">wc</span> <span class="token parameter variable">-c</span> dist/**/*.js</code></pre>
<p><code>wc</code> (short for Word Count) is a shell command that counts words in a file. Since we care about byte size, not words, we use the <code>-c</code> flag. Don't ask me why it's <code>c</code>, maybe for Char? Anyways, this gizes us the byte size of every generated JS file, as well as the total size, in a nice table:</p>
<p><img src="https://thoughtspile.github.io/images/raw-bundle-size.png" alt="" /></p>
<p>You can change the asset extension like <code>dist/**/*.css</code>, or view the total asset size by omitting the extension altogether. This won't give you a breakdown by entrypoints or any idea <em>why</em> the size is what it is, but hey, you spent like 3 seconds on it!</p>
<p>You could try <code>du -sh dist/**/*.js</code> — it shows you <em>some</em> sizes of your assets, too. Those sizes are rounded up to the nearest FS page (or whatever it's called, my systems programming is rusty) — 4K in my case. 4K is not much, but <code>wc -c</code> is more precise.</p>
<h3>gzip size</h3>
<p>But your assets are compressed, aren't they? No propblem, shell can <code>gzip</code> for us:</p>
<pre class="language-sh"><code class="language-sh"><span class="token function">gzip</span> <span class="token parameter variable">-c</span> dist/**/*.js <span class="token operator">|</span> <span class="token function">wc</span> <span class="token parameter variable">-c</span></code></pre>
<p>Here, we <code>gzip</code> every JS file and concatenate them together, <code>-c</code> writes the result to stdout, then <code>wc -c</code> counts the bytes of <code>gzip</code>ped data. You can also adjust compression level using <code>gzip -[1..9]</code>, but that doesn't drastically change the result.</p>
<p>Viewing the sizes of individual JS files is a touch more complicated:</p>
<pre class="language-sh"><code class="language-sh"><span class="token function">gzip</span> <span class="token parameter variable">-k</span> dist/**<br /><span class="token function">wc</span> <span class="token parameter variable">-c</span> dist/**/*.js.gz</code></pre>
<p>Here, we gzip all the assets and actually write them to disk (<code>-k</code> makes sure the original files are not deleted), then <code>wc -c</code> them (<code>.gz</code> is appended to every filename) as usual.</p>
<p><img src="https://thoughtspile.github.io/images/gzip-size.png" alt="" /></p>
<p>You could also <code>cat dist/**/*.js | gzip -c</code> — this compresses the JS files as one huge file. If you have several JS files, this would probably be smaller that per-file gzip. You can use this to see how much you would save by bundling all your code together.</p>
<hr />
<p>To check bundle size of any project, build it and use these 2 commands:</p>
<ul>
<li><code>wc -c dist/**/*.js</code> shows you byte sizes of all your JS files</li>
<li><code>gzip -c dist/**/*.js | wc -c</code> shows you the total bundle size of your JS files after gzip (what would be transfered over the network)</li>
</ul>
<p>Happy optimization!</p>
Build better libraries, use dev warnings2021-09-22T00:00:00Zhttps://thoughtspile.github.io/2021/09/22/dev-warnings/<p>Suppose you're making a cool library that sums numbers in an array. You add a new option, <code>inital</code>, that lets users specify an initial value for the summation:</p>
<pre class="language-js"><code class="language-js"><span class="token function">sum</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">inital</span><span class="token operator">:</span> <span class="token number">10</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// 13</span></code></pre>
<p>Oh no! You made a typo — of course you meant <code>initial</code>, not <code>inital</code>. What's done is done, and you're stuck with a million users relying on your <code>inital</code> option. Here's what you can do:</p>
<ol>
<li>Keep the <code>inital</code> option forever. You bconfuse the users and become known as <em>that guy who can't spell.</em></li>
<li>Rename <code>inital</code> to <code>initial</code> immediately. Everyone has to rewrite their code that was working fine (thinking you're a jerk), and the apps whose authors don't follow the changelog explode.</li>
</ol>
<p>As a responsible maintainer, you decide to go the third way — support both <code>initial</code> and <code>inital</code> for now, schedule dropping <code>inital</code> in v2, and let your users know a breaking change is coming. You fix the issue with this ingenious code:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">sum</span><span class="token punctuation">(</span><span class="token parameter">arr<span class="token punctuation">,</span> ops <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token string">'inital'</span> <span class="token keyword">in</span> ops<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'dont use inital option'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> initial <span class="token operator">=</span> <span class="token punctuation">(</span>ops<span class="token punctuation">.</span>inital <span class="token operator">||</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token operator">=</span> ops<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> arr<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">s<span class="token punctuation">,</span> a</span><span class="token punctuation">)</span> <span class="token operator">=></span> s <span class="token operator">+</span> a<span class="token punctuation">,</span> initial<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Not so fast! Here are some problems with this fix:</p>
<ol>
<li>Production bundle size of your library has grown by 25% thanks to the bundled error messages.</li>
<li>The apps relying on <code>inital</code> option run slower, since <code>console.warn</code> is fairly heavy.</li>
<li>Dev console is all covered in your <code>inital</code> message, and it's easier to miss important warnings from other libraries.</li>
<li>If the dev uses a lot of libraries, it's not clear exactly what caused the error.</li>
</ol>
<p>Let's handle these issues one by one.</p>
<h2>Remove warning from production</h2>
<p>The first two issues can be fixed by removing the warning code from production bundle. Easier than it seems:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">if</span> <span class="token punctuation">(</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">===</span> <span class="token string">'development'</span> <span class="token operator">&&</span> <span class="token punctuation">(</span><span class="token string">'inital'</span> <span class="token keyword">in</span> ops<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'dont use inital option'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Your user's bundler replaces <code>process.env.NODE_ENV</code> with a literal string, <code>"production"</code>, in production mode, turning the condition into <code>if ('production' === 'development')</code>, which is <code>if (false)</code>, and then the minifier's <a href="https://lihautan.com/dead-code-elimination/">dead code elimination</a> removes the <code>if</code> block altogether, since it can never get executed. All the warning code, and even the <code>ops.inital</code> check, are gone. Pretty smart!</p>
<p>This works in <a href="https://webpack.js.org/guides/production/#specify-the-mode">webpack</a>, <a href="https://parceljs.org/production.html#optimisations">parcel</a>, and <a href="https://github.com/rollup/rollup/issues/487">rollup (with plugin-replace)</a>. Some of the biggest JS libraries, like <a href="https://github.com/facebook/react/blob/cae635054e17a6f107a39d328649137b83f25972/packages/react/npm/index.js">react</a> and <a href="https://github.com/vuejs/vue/search?q=node_env">vue,</a> use this technique, so you're in good company.</p>
<p>One nasty thing is that you must wrap the warning in a full <code>process.env.NODE_ENV === 'development'</code> condition manually each time — using any indirection <em>may</em> confuse the replacement algorithms and prevent code removal:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// devWarn becomes () => {}</span><br /><span class="token keyword">function</span> <span class="token function">devWarn</span><span class="token punctuation">(</span><span class="token parameter">msg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">===</span> <span class="token string">'development'</span> <span class="token operator">&&</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>msg<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token comment">// but the call with the string is not removed</span><br /><span class="token function">devWarn</span><span class="token punctuation">(</span><span class="token string">'oh no'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// looking across module boundaries may not work</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> isDev<span class="token punctuation">,</span> env<span class="token punctuation">,</span> dev <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../env'</span><span class="token punctuation">;</span><br />isDev <span class="token operator">&&</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'ooh no'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">(</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">===</span> dev<span class="token punctuation">)</span> <span class="token operator">&&</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'no'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">(</span>env <span class="token operator">===</span> <span class="token string">'development'</span><span class="token punctuation">)</span> <span class="token operator">&&</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'no'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h2>Error tracing</h2>
<p>Modern web apps use many different libraries. When a developer sees your message, it may not be obvious where it came from. A good first step is replacing <code>console.log</code> with a <code>warn</code> or <code>error</code> that fires with a nice expandable stack trace:</p>
<p><img src="https://thoughtspile.github.io/images/warn-trace.png?invert" alt="" /></p>
<p>In some frameworks (looking at you, React) the stack trace may not be that useful. If that's your case, provide extra identification inside the message:</p>
<p><code>console.warn('[sum/useSum] dont use inital')</code></p>
<p>Wording the warnings is important, too. Our current "inital is bad" is confusing — what's wrong with that option? Is my code broken? What should I do? Make sure to provide the motivation for the change and an actionable fix. Here: we made a typo, the code is OK until v2, please move to an option with a normal name when you have the time. Here we go: <code>"inital" option was a typo and will be removed in v2 - use "initial"</code> Feel free to provide a link to relevant docs / discussions if it's a particularly complicated matter. Remember, the messages are removed from production bundle, so there's no reason to save keystrokes here.</p>
<h2>Log once</h2>
<p>Don't assume your users are stupid and bombard them with warnings — it's annoying and may drown other, more important, messages. I prefer showing every warning once — the users who care will clean up their code until no more warnings are left, and those who don't are free to go on with their business. Here's a way to do that:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">warnOnce</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> logged <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token parameter">msg</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>logged<span class="token punctuation">[</span>msg<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> logged<span class="token punctuation">[</span>msg<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br /> console<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span>msg<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><span class="token keyword">export</span> <span class="token keyword">const</span> warn <span class="token operator">=</span> <span class="token function">warnOnce</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>If you a particular warning really needs to fire several times, play around with <code>warnOnce</code> instancing — like <code>useMemo(warnOnce, [])</code> to warn once per React component instance.</p>
<hr />
<p>Dev warnings are not only helpful for deprecations and breaking change announcements. Incorrect API uses deserve a warning, too — for example, the user could pass two conflicting options, or an obscure error would be thrown soon and you'd like to explain what caused it. Most dev warnings are a sign of bad API design, but they're a helpful tool nonetheless.</p>
<p>Here are some tips for great dev warnings:</p>
<ul>
<li>Wrap your dev warnings into a <code>process.env.NODE_ENV === 'development'</code> condition to strip them from the production bundle. No abstractions here, please.</li>
<li>Make sure the source of the error is clear — use <code>console.warn / error</code> to show a nice stack trace and include the library name in the message itself.</li>
<li>Clearly explain what caused the warning, the consequences of ignoring it, and suggest a fix.</li>
<li>Don't drown the users in warnings — warn once.</li>
</ul>
<p>Hopefully, these tips will help you improve your library developer experience. Warning is caring.</p>
useLayoutEffect is a bad place for deriving state2021-09-21T00:00:00Zhttps://thoughtspile.github.io/2021/09/21/useeffect-derived-state/<p>Today we'll talk about updating state inside useLayoutEffect in reaction to prop changes. Will it work? Is it safe? Are there <em>better</em> ways to implement such state changes? TLDR: it works, but leaves you with an extra DOM update that may break stuff.</p>
<p>As we all know, useLayoutEffect is called before the browser has painted the DOM. This might lead you to believe it's OK to create derived state with useLayoutEffect — we'll immediately update with the new state, and the user won't notice anything, right? As we'll shortly see, wrong. Let me introduce an example — transition manager.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Transition</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children<span class="token punctuation">,</span> active <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> prevActive <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>exiting<span class="token punctuation">,</span> setExiting<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> prevActive<span class="token punctuation">.</span>current <span class="token operator">&&</span> <span class="token function">setExiting</span><span class="token punctuation">(</span>prevActive<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> prevActive<span class="token punctuation">.</span>current <span class="token operator">=</span> active<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>active<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">onTransitionEnd</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setExiting</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>children<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> isExiting <span class="token operator">=</span> c<span class="token punctuation">.</span>key <span class="token operator">===</span> exiting<span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>c<span class="token punctuation">.</span>key <span class="token operator">!==</span> active <span class="token operator">&&</span> <span class="token operator">!</span>isExiting<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token comment">// assume a suitable transform + transition on .exit</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>c<span class="token punctuation">.</span>key<span class="token punctuation">}</span></span> <span class="token attr-name">className</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>isExiting <span class="token operator">?</span> <span class="token string">"exit"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>c<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><span class="token plain-text">);<br />};</span></code></pre>
<p><code>Transition</code> accepts app screens as keyed children and only renders the one with <code>active</code> key, but also adds a nice effect when moving between the screens — like a lightweight react-transition-group. For that, we keep the previously active child mounted while the transition is playing — that's handled by <code>exiting</code>. When <code>active</code> changes, we activate <code>exiting</code>, play the animation, then remove <code>exiting</code>. Should be good.</p>
<p>But (see for yourself <a href="https://codesandbox.io/s/funny-goodall-txp3m?file=/src/App.js">in the codesandbox</a>) the transition doesn't play. What went wrong? Thinking about it, useLayoutEffect lets you peek into the actual DOM created by your render. So, even though the browser didn't paint, the DOM for the "virtual" state was there. The DOM sequence is:</p>
<ol>
<li>DOM is <code><Screen1 /></code>, as expected</li>
<li>paint</li>
<li>Change DOM to <code><Screen2 /></code> <strong>(OH SHI)</strong></li>
<li>Change DOM to <code><Screen1 exiting /><Screen2 /></code>, as expected</li>
<li>paint, paint, paint transition frames</li>
<li>Change DOM to <code><Screen2 /></code>, as expected</li>
<li>paint</li>
</ol>
<p>While changing the DOM to <code><Screen2 /></code> for a moment without even painting seems like nothing, it's not. Instead of setting a class on the exiting screen1 and rendering screen2, react unmounts screen1, then mounts screen2 and еру exiting screen1, leaving us with a whole extra screen1 remount. Being wasteful is not the main problem here. The browser loses track of screen1 in between remounts, thinks the screen with the "exiting" class had just appeared out of nowhere, and doesn't play the transition.</p>
<p>Technically, we could work around this by replacing transition with an animation, but that would still leave us with a useless remount (whole screens are rather heavy) and lost DOM state (like the inner scrolls in screen1 would get unset, leading to strange flickering). We obviously need to activate <code>exiting</code> and change <code>active</code> in the same render — but how can we do that?</p>
<h2>Derived state</h2>
<p>So, as far as we know, setting state derived from props in an effect causes a parasitical DOM update that is wasted at best, and may break stuff. So, the state model that requires us to change state in reaction to prop change will not work. Let's find a more suitable state model, then!</p>
<p>When your state is purely derived from props, it's an easy feat:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">[</span>fullName<span class="token punctuation">,</span> setFullName<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setFullName</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>props<span class="token punctuation">.</span>firstName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>props<span class="token punctuation">.</span>lastName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>props<span class="token punctuation">.</span>firstName<span class="token punctuation">,</span> props<span class="token punctuation">.</span>lastName<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// becomes...</span><br /><span class="token keyword">const</span> fullName <span class="token operator">=</span> <span class="token function">useMemo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>props<span class="token punctuation">.</span>firstName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>props<span class="token punctuation">.</span>lastName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>props<span class="token punctuation">.</span>firstName<span class="token punctuation">,</span> props<span class="token punctuation">.</span>lastName<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// useMemo is obviously optional:</span><br /><span class="token keyword">const</span> fullName <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>props<span class="token punctuation">.</span>firstName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>props<span class="token punctuation">.</span>lastName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code></pre>
<p>Our case is more complex, since we want to derive state from props, but <em>also</em> to set it imperatively from <code>onTransitionEnd</code>. For that, we need to <em>decompose</em> the state into a part that depends on props, and some other state doesn't need to be updated on prop change. An alternate model that cleanly plays with react here would be:</p>
<ol>
<li><code>props.active</code> is the currently active screen</li>
<li><code>lastSettled</code> via <code>useState</code> is the last screen we've cleanly transitioned to</li>
</ol>
<p>Transition then plays as long as <code>lastSettled !== props.active</code>, and <code>lastSettled</code> is updated on transition end. Once you know the trick, the code is straightforward:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Transition</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children<span class="token punctuation">,</span> active <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>lastSettled<span class="token punctuation">,</span> setLastSettled<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span>active<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> exiting <span class="token operator">=</span> lastSettled <span class="token operator">===</span> active <span class="token operator">?</span> <span class="token keyword">null</span> <span class="token operator">:</span> lastSettled<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">onTransitionEnd</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setLastSettled</span><span class="token punctuation">(</span>active<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>children<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// same as before</span><br /> <span class="token keyword">const</span> isExiting <span class="token operator">=</span> c<span class="token punctuation">.</span>key <span class="token operator">===</span> exiting<span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>c<span class="token punctuation">.</span>key <span class="token operator">!==</span> active <span class="token operator">&&</span> <span class="token operator">!</span>isExiting<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>c<span class="token punctuation">.</span>key<span class="token punctuation">}</span></span> <span class="token attr-name">className</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>isExiting <span class="token operator">?</span> <span class="token string">"exit"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>c<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>It works <a href="https://codesandbox.io/s/funny-goodall-txp3m?file=/src/App.js">(sandbox)</a>, and it's brilliant! The transition now plays, we get exactly 3 renders and 3 DOM updates, and we <em>also</em> handle a tricky case when <code>active</code> changes again while playing transition: in our previous model, we would abruptly finish the <code>s1 -> s2</code> transition and transition again <code>s2 -> s3</code>, while here the transition changes to <code>s1 -> s3</code>. Also, <code>s1 -> s2 -> s1</code> transition is automatically canceled and needs no special handling.</p>
<h2>Buffering prop updates</h2>
<p>The only problem with our "find the right model" approach is is that doing it is much harder than saying. I'm not some state genius, and honestly spent a whole day trying different options, and the final idea only came once I gave up and opened a beer. We need something more reliable and reproducible.</p>
<p>We can't set <code>exiting</code> state immediately when <code>props.active</code> changes (bring setState closer to props change). Instead, we can make <code>active</code> and <code>exiting</code> change simultaneously by delaying the <code>active</code> update. Let's ignore <code>props.active</code> during render, but use an effect to copy it into <code>state.active</code> (setting <code>exiting</code> simultaneously) and render based on the state. I'd call this technique "buffering":</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Transition</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children<span class="token punctuation">,</span> <span class="token literal-property property">active</span><span class="token operator">:</span> _active <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span><span class="token punctuation">{</span> active<span class="token punctuation">,</span> exiting <span class="token punctuation">}</span><span class="token punctuation">,</span> setTransition<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">active</span><span class="token operator">:</span> _active<span class="token punctuation">,</span><br /> <span class="token literal-property property">exiting</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// this line skips transition on mount</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>_active <span class="token operator">!==</span> active<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">setTransition</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">active</span><span class="token operator">:</span> _active<span class="token punctuation">,</span> <span class="token literal-property property">exiting</span><span class="token operator">:</span> active <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>_active<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">onTransitionEnd</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setTransition</span><span class="token punctuation">(</span><span class="token punctuation">{</span> active <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>children<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// same as before</span><br /> <span class="token keyword">const</span> isExiting <span class="token operator">=</span> c<span class="token punctuation">.</span>key <span class="token operator">===</span> exiting<span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>c<span class="token punctuation">.</span>key <span class="token operator">!==</span> active <span class="token operator">&&</span> <span class="token operator">!</span>isExiting<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>c<span class="token punctuation">.</span>key<span class="token punctuation">}</span></span> <span class="token attr-name">className</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>isExiting <span class="token operator">?</span> <span class="token string">"exit"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>c<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This works, too (see <a href="https://codesandbox.io/s/funny-goodall-txp3m?file=/src/App.js">the sandbox</a> again). The DOM sequence here is:</p>
<ol>
<li><code><screen1 /></code></li>
<li><code><screen1 /></code> again with prop <code>active="v2"</code> — not a big deal, React handles that</li>
<li><code><screen1 exiting /><screen2 /></code> our transition, rendered cleanly</li>
<li><code><screen2 /></code></li>
</ol>
<p>Much better! We haven't completely eliminated a parasitical render, but instead of a full <code><screen1 /></code> remount we just compare the vDOM and do nothing with the real DOM.</p>
<h2>Stuff that didn't work</h2>
<h3>setState in render</h3>
<p>As a dirty experiment, we could skip the <code>effect</code> and set state in render just to see what happens:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Transition</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children<span class="token punctuation">,</span> active <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> prevActive <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> prevActive<span class="token punctuation">.</span>current <span class="token operator">=</span> active<br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>active<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>exiting<span class="token punctuation">,</span> setExiting<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>active <span class="token operator">!==</span> prevActive<span class="token punctuation">.</span>current<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">setExiting</span><span class="token punctuation">(</span>prevActive<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token comment">// etc</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>As expected, this is a clusterfuck — the renders triggered by <code>setExiting</code> appear to run, but the resulting DOM is ignored and react appears to throw away the state update (<code>exiting</code> is not there on the next normal render).</p>
<h3>Fancy "semi-state" hook</h3>
<p>As we've discovereed, there is no way to set state after a prop update without an itermediate DOM update. But you know what can be set during render? A <code>ref</code>! We set the ref synchronously inside render, we unset it as usual on transition end. Since assigning to <code>ref.current</code> does not trigger a re-render, we also force rerender by "setting" a useless state. Here you go:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">Transition</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children<span class="token punctuation">,</span> active <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// basic usePrevious technique</span><br /> <span class="token keyword">const</span> prevActive <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> prevActive<span class="token punctuation">.</span>current <span class="token operator">=</span> active<br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>active<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// exiting "state"</span><br /> <span class="token keyword">const</span> exiting <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// useless state to force rerender on transition finish</span><br /> <span class="token keyword">const</span> rerender <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">function</span> <span class="token function">flushExiting</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// unset exiting "state"</span><br /> exiting<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /> <span class="token comment">// force rerender</span><br /> <span class="token function">rerender</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>active <span class="token operator">!==</span> prevActive<span class="token punctuation">.</span>current<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// immediately set exiting "state" on change</span><br /> exiting<span class="token punctuation">.</span>current <span class="token operator">=</span> prevActive<span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token comment">// as before</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This appears to work in react basic mode, but, as expected when setting ref inside render, <em>will not work in concurrent mode™</em> Without getting into too much detail, the ref is shared between currently mounted and WIP branches, and both can unexpectedly set or unset it.</p>
<hr />
<p>So, wrapping up:</p>
<ol>
<li>Calling synchronizing state to props in <code>useLayoutEffect</code> makes react put the real DOM into an inconsistent state for a moment.</li>
<li>This parasitical render might be just wasted cycles, but can also mess with your CSS transitions and lose DOM state (like scrolls and focus).</li>
<li>The best approach is to come up with another state model that does not require you to change anything based on a prop change, but this might not always be trivial.</li>
<li>A simpler way is to "delay" the prop udate by copying the prop into a state, and then updating it together with the derived state.</li>
</ol>
How we made our pre-commit check 7x faster2021-06-14T00:00:00Zhttps://thoughtspile.github.io/2021/06/14/faster-pre-commit/<p>As a guy who's somewhat responsible for a large chunk of front-end development infrastructure at our company, I've spent the last couple of months woried about the performance of our pre-commit checks. We have around 50 projects on a standard react + typescript stack, and a corresponding set of pre-commit checks: <code>eslint</code> + <code>stylelint</code> + <code>tsc</code> + sometimes, <code>jest</code>. This suite was taking anywhere from 10s on a starter project to 50s on a monstrous app — not fun. I set out to fix this — and I did.</p>
<h2>Cache your linters</h2>
<p>The quick fix was to add <code>--cache</code> flag to <a href="https://eslint.org/docs/user-guide/command-line-interface#options">eslint</a> and <a href="https://stylelint.io/user-guide/usage/options/#cache">stylelint</a> calls. These tools process one file at a time, and caching makes them run very fast (around 1s for a normal commit instead of 10+). A quick <a href="https://github.com/search?l=JSON&q=eslint+src&type=Code">github search</a> makes me sad, because few people seem to do this. Also don't forget to gitignore <code>.stylelintcache</code> and <code>.eslintcache</code>. <strong>Gain:</strong> 50 -> 30s.</p>
<h2>Run the checks concurrently</h2>
<p>Most checks were written like <code>eslint src && stylelint src/**/*.css && tsc --noEmit</code> — I assume the code was just being copied over. It's a waste for multi-core developer machines, and has an extra drawback of being unusable on windows (I don't think many front-end devs run windows, anyways). Making the checks run in parallel using <a href="https://github.com/kimmobrunfeldt/concurrently"><code>concurrently</code></a> or <a href="https://github.com/mysticatea/npm-run-all/blob/master/docs/npm-run-all.md"><code>npm-run-all</code></a> essentially makes the check run as fast as the slowest check — in our case, we were getting linters and jest for free, and <code>tsc</code> became the limiting factor. <strong>Gain:</strong> 30 -> 28s.</p>
<h2>Cache tsc</h2>
<p><a href="https://www.typescriptlang.org/tsconfig/#noEmit"><code>tsc --noEmit</code></a> sounds like the way to go if you run <code>tsc</code> to type-check your code, not to build anything. However, it was impossible to combine <code>--noEmit</code> with <code>--incremental</code> for a long time, leaving you with no caching and slow builds. Luckily, <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-4-0-beta/#noemit-and-incremental">TS 4.0+ supports</a> this combination — just drop an <code>--incremental</code> flag and save time. If you're not ready to upgrade, <a href="https://stackoverflow.com/a/62622318">a workaround</a> exists — you want the check to be faster, not to write exactly zero files, don't you? <strong>Gain:</strong> 28 -> 7s.</p>
<h2>Do not break jest dependency detection</h2>
<p>Lastly, I wanted to cover several ways to speed up <a href="https://jestjs.io/">jest</a> if you happen to run it in your pre-commit (this is pretty rare). Obviously, you want to use <a href="https://jestjs.io/docs/cli#--onlychanged"><code>jest --onlyChanged</code></a> (or <code>jest -o</code>) to test only the files changed in the commit, not all the project. <code>jest</code> uses simple file-based dependency detection, no tree-shaking or anything — if you change file A, all the files that <code>import A</code> may have changed, and so on, and jest must run the tests for all the files that depend on A, too. You can work with this if you follow 2 rules:</p>
<ol>
<li>Do no import <code>index.js</code> inside your project — this erases granular change checks for individual modules re-exported via index. In the worst case, if you import from a root-level index, <em>every</em> change triggers all the tests.</li>
<li>Break frequently changed files into smaller chunks. Granted, it's good to use smaller modules in any case, but I bet you could start with your <code>utils.js</code> that contains 200 helpers. This will allow jest to make better guesses about what actually changed.</li>
</ol>
<hr />
<p>When pre-commit checks get slower, I see a lot of pressure to drop some checks and move them to CI. If you stick with slow checks instead, rest assured many developers will just <code>--no-verify</code> when commiting, which is probably not what you wanted to achieve. Lukily, you can easily make your pre-commit checks run in under 10 seconds:</p>
<ol>
<li>Use <code>eslint --cache</code> and <code>stylelint --cache</code></li>
<li>Run <code>tsc</code> with <code>--incremental</code> flag, or use a <a href="https://stackoverflow.com/a/62622318">workaround</a> for TS <4.0</li>
<li>Parallelize the checks using <code>concurrently</code> or <code>npm-run-all</code></li>
<li>Use <code>jest -o</code>, don't <code>import index</code>, and use smaller modules.</li>
</ol>
<p>This can be done in 15 minutes, really. I've run some calculations for you — if you manage to strip 30s off your check time, assuming you make 5 commits a day and have a 3-person team (all this sound plausible), you're saving your team <code>3 * 5 * 0.5 * 250 / 60</code> = 31 hours a year, that's almost a week to spend better than waiting for pre-commit cheks. I really really hope you go and see if you can apply some of these techniques right now.</p>
Cleaner ways to build dynamic JS arrays2021-06-11T00:00:00Zhttps://thoughtspile.github.io/2021/06/11/cleaner-dynamic-arrays/<p>Building dynamic arrays in JS is often messy. It goes like this: you have a default array, and you need some items to appear based on a condition. So you add an <code>if (condition) array.push(item)</code>. Then you need to shuffle things around and bring in an <code>unshift</code> or two, and maybe even a <code>splice</code>. Soon, your array building code is a crazy mess of <code>if</code>s with no way to tell what <em>can</em> be in the final array, and in which order. Something like this (yes, I'm building a CLI lint runner):</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> args <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'--ext'</span><span class="token punctuation">,</span> <span class="token string">'.ts,.tsx,.js,.jsx'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">if</span> <span class="token punctuation">(</span>cache<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> args<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><br /> <span class="token string">'--cache'</span><span class="token punctuation">,</span><br /> <span class="token string">'--cache-location'</span><span class="token punctuation">,</span> path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span>__dirname<span class="token punctuation">,</span> <span class="token string">'.cache'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token keyword">if</span> <span class="token punctuation">(</span>source <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> args<span class="token punctuation">.</span><span class="token function">unshift</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token keyword">if</span> <span class="token punctuation">(</span>isTeamcity<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> args <span class="token operator">=</span> args<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">'--format'</span><span class="token punctuation">,</span> <span class="token string">'node_modules/eslint-teamcity'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Luckily, I'm here to end the struggle with three great ways to clean up this mess! As a bonus, I'll show you how to apply these techniques to strings as well!</p>
<h2>Chained concat</h2>
<p>The first trick is to replace every <code>if</code> block with a <code>.concat(cond ? [...data] : [])</code>. Luckily, <code>concat</code> is chainable, and working with it is a joy:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> args <span class="token operator">=</span> <span class="token punctuation">[</span><br /> <span class="token string">'--ext'</span><span class="token punctuation">,</span> <span class="token string">'.ts,.tsx,.js,.jsx'</span><span class="token punctuation">,</span><br /><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>cache <span class="token operator">?</span> <span class="token punctuation">[</span><br /> <span class="token string">'--cache'</span><span class="token punctuation">,</span><br /> <span class="token string">'--cache-location'</span><span class="token punctuation">,</span> path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span>__dirname<span class="token punctuation">,</span> <span class="token string">'.cache'</span><span class="token punctuation">)</span><br /><span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>isTeamcity <span class="token operator">?</span> <span class="token punctuation">[</span><br /> <span class="token string">'--format'</span><span class="token punctuation">,</span> <span class="token string">'node_modules/eslint-teamcity'</span><br /><span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Much better! The array is consistently formatted and easier to read, with clear conditional blocks. If you're paying attention, you'll notice I missed the <code>unshift</code> bit — that's because at the beginning, you don't have an array to <code>.concat()</code> to. Why don't we just create it?</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> args <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>source <span class="token operator">!==</span> <span class="token keyword">null</span> <span class="token operator">?</span> <span class="token punctuation">[</span><br /> source<br /><span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span><span class="token punctuation">[</span><br /> <span class="token string">'--ext'</span><span class="token punctuation">,</span> <span class="token string">'.ts,.tsx,.js,.jsx'</span><span class="token punctuation">,</span><br /><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>cache <span class="token operator">?</span> <span class="token punctuation">[</span><br /> <span class="token string">'--cache'</span><span class="token punctuation">,</span><br /> <span class="token string">'--cache-location'</span><span class="token punctuation">,</span> path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span>__dirname<span class="token punctuation">,</span> <span class="token string">'.cache'</span><span class="token punctuation">)</span><br /><span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>isTeamcity <span class="token operator">?</span> <span class="token punctuation">[</span><br /> <span class="token string">'--format'</span><span class="token punctuation">,</span> <span class="token string">'node_modules/eslint-teamcity'</span><br /><span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The <code>...spread</code> variant looks horrendous to me, but has less syntax and makes conditional blocks stand out from the static ones:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> args <span class="token operator">=</span> <span class="token punctuation">[</span><br /> <span class="token operator">...</span><span class="token punctuation">(</span>source <span class="token operator">!==</span> <span class="token keyword">null</span> <span class="token operator">?</span> <span class="token punctuation">[</span><br /> source<br /> <span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token string">'--ext'</span><span class="token punctuation">,</span> <span class="token string">'.ts,.tsx,.js,.jsx'</span><span class="token punctuation">,</span><br /> <span class="token operator">...</span><span class="token punctuation">(</span>cache <span class="token operator">?</span> <span class="token punctuation">[</span><br /> <span class="token string">'--cache'</span><span class="token punctuation">,</span><br /> <span class="token string">'--cache-location'</span><span class="token punctuation">,</span> path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span>__dirname<span class="token punctuation">,</span> <span class="token string">'.cache'</span><span class="token punctuation">)</span><br /> <span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token operator">...</span><span class="token punctuation">(</span>isTeamcity <span class="token operator">?</span> <span class="token punctuation">[</span><br /> <span class="token string">'--format'</span><span class="token punctuation">,</span> <span class="token string">'node_modules/eslint-teamcity'</span><br /> <span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><br /><span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre>
<h2>Truthy filtering</h2>
<p>There's another great option that works best when conditional fragments are single items. It's inspired by <a href="https://reactjs.org/docs/conditional-rendering.html#inline-if-with-logical--operator">React's conditional rendering patterns</a> and relies on boolean short-circuiting:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> args <span class="token operator">=</span> <span class="token punctuation">[</span><br /> <span class="token comment">// here, we have either "source" or "false"</span><br /> source <span class="token operator">!==</span> <span class="token keyword">null</span> <span class="token operator">&&</span> source<span class="token punctuation">,</span><br /> <span class="token string">'--ext'</span><span class="token punctuation">,</span><br /> <span class="token string">'.ts,.tsx,.js,.jsx'</span><span class="token punctuation">,</span><br /> cache <span class="token operator">&&</span> <span class="token string">'--cache'</span><span class="token punctuation">,</span><br /> cache <span class="token operator">&&</span> <span class="token string">'--cache-location'</span><span class="token punctuation">,</span><br /> cache <span class="token operator">&&</span> path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span>__dirname<span class="token punctuation">,</span> <span class="token string">'.cache'</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> isTeamcity <span class="token operator">&&</span> <span class="token string">'--format'</span><span class="token punctuation">,</span><br /> isTeamcity <span class="token operator">&&</span> <span class="token string">'node_modules/eslint-teamcity'</span><span class="token punctuation">,</span><br /><span class="token comment">// filter() removes falsy items</span><br /><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>Boolean<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The reads like a flat array, with the important conditional logic consistently formatted to the left. Be careful, though, as this removes <em>any</em> falsy stuff, like empty strings and zeroes. You can work your way around it with <code>filter(x => x !== false)</code>, but there's no way on earth to use it on an array that can have real <code>false</code> values.</p>
<p>Developing this method further, we can combine it with the conditional concat to get the best of both worlds: ability to group several items with one condition (repeating <code>cache &&</code> is not nice) and the conciseness of filtering:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> args <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span><br /> source <span class="token operator">!==</span> <span class="token keyword">null</span> <span class="token operator">&&</span> source<span class="token punctuation">,</span><br /> <span class="token string">'--ext'</span><span class="token punctuation">,</span> <span class="token string">'.ts,.tsx,.js,.jsx'</span><span class="token punctuation">,</span><br /> cache <span class="token operator">&&</span> <span class="token punctuation">[</span><br /> <span class="token string">'--cache'</span><span class="token punctuation">,</span><br /> <span class="token string">'--cache-location'</span><span class="token punctuation">,</span> path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span>__dirname<span class="token punctuation">,</span> <span class="token string">'.cache'</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token punctuation">]</span><span class="token punctuation">,</span><br /> isTeamcity <span class="token operator">&&</span> <span class="token punctuation">[</span><br /> <span class="token string">'--format'</span><span class="token punctuation">,</span> <span class="token string">'node_modules/eslint-teamcity'</span><span class="token punctuation">,</span><br /> <span class="token punctuation">]</span><br /><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>Boolean<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Here, we use the fact that <code>concat</code> <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat#concatenating_values_to_an_array">accepts any number of mixed items and arrays,</a> and <code>concat(false)</code> just appends a <code>false</code> to the end of the array. If <code>cache</code> and <code>isTeamcity</code> were false, you'd end up with</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> args <span class="token operator">=</span> <span class="token punctuation">[</span><br /> source<span class="token punctuation">,</span><br /> <span class="token string">'--ext'</span><span class="token punctuation">,</span><br /> <span class="token string">'.ts,.tsx,.js,.jsx'</span><span class="token punctuation">,</span><br /> <span class="token boolean">false</span><span class="token punctuation">,</span><br /> <span class="token boolean">false</span><span class="token punctuation">,</span><br /><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>Boolean<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>And the unneeded <code>false</code> values would then just be filtered away. This is my personal favorite technique for building dynamic arrays. And we can apply it to strings!</p>
<h2>Expanding to strings</h2>
<p>Working with ES6 template strings is pleasant, but inserting fragments conditionaly is not:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> className <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">btn </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>isLarge <span class="token operator">?</span> <span class="token string">'btn--lg'</span> <span class="token operator">:</span> <span class="token string">''</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>isAccent <span class="token operator">?</span> <span class="token string">'btn--accent'</span> <span class="token operator">:</span> <span class="token string">''</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code></pre>
<p>There are two things I don't like about this version: the <code>: ''</code> blocks are pretty useless, and you often get irregular whitespace around skipped items — in this case, you'd have <code>"btn "</code> (two extra trailing spaces) for a regular button. Luckily, we can apply the filter pattern to solve both problems:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> className <span class="token operator">=</span> <span class="token punctuation">[</span><br /> <span class="token string">'btn'</span><span class="token punctuation">,</span><br /> isLarge <span class="token operator">&&</span> <span class="token string">'btn--lg'</span><span class="token punctuation">,</span><br /> isAccent <span class="token operator">&&</span> <span class="token string">'btn--md'</span><br /><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>Boolean<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">' '</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This works even better for multiline strings:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">renderCard</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> title<span class="token punctuation">,</span> text <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span><br /> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><section class="card"></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span><br /> title <span class="token operator">&&</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> <h1></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>title<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"></h1></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span><br /> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"> <div class="card__body"><br /> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>text<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"><br /> </div></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span><br /> footer <span class="token operator">&&</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br /> <div class="card__footer"><br /> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>footer<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"><br /> </div></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span><br /> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"></section></span><span class="token template-punctuation string">`</span></span><br /><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>Boolean<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The formatting might seem a bit weird at first, but I honestly prefer it this way, and I built a code-generator thing that was 90% of this. Feel free to play around with indentation, though, if it's not your cup of tea.</p>
<hr />
<p>Today, we've covered three techniques to bring messy array building code back under control:</p>
<ol>
<li>Replace conditional blocks with <code>.concat(cond ? [...data] : [])</code></li>
<li>Set some array items to <code>false</code> via <code>cond && item</code>, then <code>.filter()</code> them away.</li>
<li>Combine the two using <code>concat(item, cond && [...data]).filter(Boolean)</code>.</li>
</ol>
<p>You can employ these methods for building strings as well: build an array of <em>string parts</em> first, and <code>join</code> it together at the end. Good luck cleaning up your code!</p>
Two practical uses for capture event listeners2021-06-07T00:00:00Zhttps://thoughtspile.github.io/2021/06/07/event-capture/<p>Normally, JS event are handled while <em>bubbling</em> up the DOM tree, and we've all had the pleasure to catch an event from a child node on its parent. You'd even be excused for thinking that's the only way DOM events move. Many also know there's something else — events start at the document root, then go down to the affected element in a phase called "capture", and only then "bubble" back up (not all do — more on this in a minute). See <a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling_and_capture">MDN article on events</a> for more details on this mechanism.</p>
<p>I always saw this capture / bubble thing as a trick interview question similar to "what is the value of <code>i++ + ++i</code>", not something useful in normal life. Yet, I have found several good uses for this knowledge nugget, and now I want to pass it over to you.</p>
<h2>Observe non-bubbling events</h2>
<p>It turns out that some events don't bubble at all. I first encountered this when working on a zoom-to-fit feature in Yandex.mail. When an image is loaded, the email DOM box size may change, and you need to resize it a bit more. But to do this, you need to know when an image loads, and <code>load</code> is one naughty event that does not bubble. The original code was therefore a wild mess of</p>
<pre class="language-js"><code class="language-js"><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'img'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">img</span> <span class="token operator">=></span> img<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'load'</span><span class="token punctuation">,</span> resize<span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre>
<p>As far as I'm concerned, though, all events go through the <code>capture</code> phase with no exceptions, so I happily replaced it with</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">onLoadCapture</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>resize<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>email<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>See this in action in a <a href="https://codesandbox.io/s/img-load-capture-382m0">sandbox</a> I made.</p>
<p>I'm not sure the exact choice of non-bubbling events makes any sense. <code>load</code> and <code>error</code> do not bubble, so my first guess was "of course, it's an image that loads, not the <code>div</code> itself". However, <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event"><code>input</code> event</a> bubbles, and by the same logic input happened in the input, not in the containing <code>div</code>. Your best bet is to consult the MDN.</p>
<h2>Selectively prevent events inside a container</h2>
<p>Another practical use of <code>capture</code> is when you don't want the content of a container to respond to some user interactions. Capture handler on the container fires before any handlers on the content, so you get a chance to <code>stopPropagation</code>, and the listeners attached to the inner elements will never know something happened. I have used this on two occassions.</p>
<p>By coincidence, just today I used a capture listener to stop <code>click</code> event from firing on content during custom gesture detection. Touch devices nicely do it natively — if you happen to hit a button when scrolling a page, the button will not be clicked when you release a finger. We have a Gallery / Slider component that supports mouse drag, and firing <code>click</code> when switching slides might be unexpected. Fixed with a capture click listener.</p>
<p>Another case was making a react-based form readonly during synchronization with backend using <code>change</code> capture — see <a href="https://codesandbox.io/s/react-prevent-change-gtdzu">sandbox.</a> I'll be the first one to admit this was not the cleanest approach, but when choosing between this and reimplementing all the form controls to support disabling via context for a one-off feature, I think I made the right call.</p>
<h2>How to attach a capture event handler</h2>
<p>If you've ever messed with the trird argument of <code>addEventListener</code>, you know it comes with two signatures: the legacy <code>addEventListener(evt, cb, true)</code> where <code>true</code> is the boolean capture argument, and the more modern <code>addEventListener(evt, cb, { capture: true })</code> designed to support the other flag, <code>passive</code>. Contrary to the popular belief, if you just want <code>capture</code>, you don't need complicated feature detection — using the boolean parameter is fine and safe, since no backwards compatibility was ever broken here. So, to add a capture event listener using the DOM API:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span><br />element<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> onClick<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// NOTE: removing a capture listener requires capture flag, too</span><br />element<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> onClick<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>In React, you just <code><div onClickCapture={onClick}></code> — all the events support this except <code>onMouseEnter / Leave</code>, because <a href="https://reactjs.org/docs/events.html#mouse-events">React is trying to be smart</a>. I'm no expert on other frameworks, but I'm hearing Vue has <code><div v-on:click.capture="onClick">...</div></code>, and Angular <a href="https://github.com/angular/angular/issues/11200">can't</a> — don't despair and fall back to <code>addEventListener</code> instead.</p>
<hr />
<p>And that's basically it — capture event listeners are useful for handling non-bubbling events (like image <code>load</code> / <code>error</code>) and for preventing certain events inside a container. There is an extra case I've used it in the past for — observing events when <code>.stopPropagation</code> is called by a third-party library, but it's super hacky and not recommended. Trying to find a common theme, capture events work well wherever you feel like grabbing some DOM nodes you don't own and slapping an event handler on them.</p>
Go beyond eslint limits with these 3 tricks2021-06-04T00:00:00Zhttps://thoughtspile.github.io/2021/06/04/eslint-workarounds/<p>My current obsession with statically checking JS code got me to appreciate eslint even more. Recently, I've shown you <a href="https://thoughtspile.github.io/2021/06/02/eslint-restrict-syntax" target="_blank">how to use <code>no-restricted-syntax</code></a> to lint almost anything. Still, like any tool, eslint has its limits — often a precise rule bends eslint too much, and is not practical to support. For example, eslint can't look into another module. Some smart plugins (like <a href="https://github.com/benmosher/eslint-plugin-import">plugin-import</a>) can work around that, but it's not something I'd be comfortable doing myself. Luckily, I know several tricks that let you bypass these limitations!</p>
<p>I'm currently building infrastructure to support mini-apps (microfrontends, plugins, call them whatever you want) — smaller front-end applications that run side-by-side in a single JS context. As you can guess, such apps need special restrictions not to collide with each other — CSS must be scoped, DOM <code>id</code>s are prohibited, and so on. Enforcing these restrictions with eslint was a breeze. Ensuring unique <code>localStorage</code> keys across all apps is trickier: even if we specify that the keys must follow <code><appName>:<key></code> convention, there's no clear way to lint it.</p>
<p>Forcing an explicit string prefix, as in <code>localStorage.setItem('settings:name')</code> prevents the users from extracting the keys to a common module, which is a good practice. Once the <code>localStorage</code> is accessed with a variable key, <code>localStorage.setItem(nameKey)</code>, all bets are off as you can't peek into the contents of <code>nameKey</code>. But here's what you can do instead.</p>
<h2>Facade API + banning the raw version</h2>
<p>The most convenient choice in this case was to provide a wraper API, <code>appLocalStorage</code>, that would prefix all keys with <code>appName</code>, along the lines of</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">export</span> <span class="token keyword">const</span> appLocalStorage <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token function">setItem</span><span class="token punctuation">(</span><span class="token parameter">name</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> localStorage<span class="token punctuation">.</span><span class="token function">setItem</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>appName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token comment">// etc</span><br /><span class="token punctuation">}</span></code></pre>
<p>Then we could ban the raw <code>localStorage</code> with a combination of <a href="https://eslint.org/docs/rules/no-restricted-globals"><code>no-restricted-globals</code></a> and <a href="https://eslint.org/docs/rules/no-restricted-properties"><code>no-restricted-properties</code></a> or even a <code>no-restricted-syntax: ['error', 'Identifier[name=localStorage]']</code>, forcing everyone to use the safe wrapper.</p>
<p>In other cases, however, the raw API is not easily bannable either — how to force all React components to use <a href="https://reactjs.org/docs/forwarding-refs.html"><code>forwardRef</code></a> if the raw component is just a function? Read on!</p>
<h2>Runime check</h2>
<p>Another possibility to restrict some patterns is enforcing a runtime JS check that warns the developer if he does something wrong. Going on with our <code>localStorage</code> example, we could monkey-patch localStorage to give a warning whenever the key requested is not prefixed:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> setItem <span class="token operator">=</span> localStorage<span class="token punctuation">.</span>setItem<span class="token punctuation">;</span><br /><span class="token keyword">const</span> prefix <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>appName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br />window<span class="token punctuation">.</span>localStorage<span class="token punctuation">.</span><span class="token function-variable function">setItem</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">key<span class="token punctuation">,</span> value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>key<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span>prefix<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">localStorage: "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" must start with "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>prefix<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token function">setItem</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>This approach has a standard drawback of runtime checks — if the developer doesn't hit the code that fails when working on a feature, he'll never know he's done something wrong. With some client error monitoring in place, you could replace <code>console.error</code> with</p>
<pre class="language-js"><code class="language-js"><span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">localStorage: "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" must start with "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>prefix<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The error is thrown asynchronously, so that the access does not explode. If <em>any</em> user, or even your QA, hits the violation, the error message will be caught by your monitoring, and you'll have a chance to fix it sooner. Still not perfect, because there's a large gap between introducting the error and seeing it, but better than nothing.</p>
<p>If you're against modifying builtins, you can instead set up a periodic check:</p>
<pre class="language-js"><code class="language-js"><span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>localStorage<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><span class="token parameter">k</span> <span class="token operator">=></span> <span class="token operator">!</span>k<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>appName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'...'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// throw new Error('...') works just as well</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This method is best suited to new apps. Hopefully, when developing a feature you run it to see if it works, and get a helpful warning. Integrating this approach into an app that already has a lot of code potentially violating the rule requires you to manually check all the possibly affected scenarios, which is usually not fun. Hopefully, we can improve this a bit.</p>
<h2>Unit test-time checks</h2>
<p>Runtime checks work, but rely on developer's luck (or QA testing a case that causes the error), and may report the error too late (worst case — in production). Unit tests are a great way to <em>make sure</em> a certain piece of code is run during the CI process. A jest expectation for our localStorage case would be...</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> keys <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>localStorage<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">expect</span><span class="token punctuation">(</span>keys<span class="token punctuation">.</span><span class="token function">every</span><span class="token punctuation">(</span><span class="token parameter">k</span> <span class="token operator">=></span> k<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>appName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBeTrue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>And you can even put this into <a href="https://jestjs.io/docs/api#aftereachfn-timeout"><code>afterEach</code></a> / <a href="https://jestjs.io/docs/api#afterallfn-timeout"><code>afterAll</code></a> to automatically check for invalid keys after each test. From here, it's a matter of ensuring all <code>localStorage</code> uses are covered with tests — the ease of this task depends on your coverage thresholds. Not universally applicable, but a helpful technique nevertheless.</p>
<hr />
<p>Today we've learnt three methods that overcome the limitations of eslint and effectively let you enforce <em>very</em> complicated restrictions:</p>
<ul>
<li>Enforcing facade API (the most convenient method)</li>
<li>Runtime check (works best for new apps)</li>
<li>Extra check in unit tests (requires high coverage)</li>
</ul>
<p>And this is probably all I can tell you about eslint.</p>
Become the master of your eslint with no-restricted-syntax2021-06-02T00:00:00Zhttps://thoughtspile.github.io/2021/06/02/eslint-restrict-syntax/<p>The other day I was doing my normal thing trying to force <code>import '*.css'</code> to be the last import in a file, which ensures a predicatbale CSS order. I spent hours looking for a eslint plugin to do that, but with little luck. Without getting into too much details:</p>
<ul>
<li>The <a href="https://eslint.org/docs/rules/sort-imports">built-in <code>sort-imports</code></a> can only group by syntax (eg <code>import { a, b }</code> before <code>import def</code>) — weird.</li>
<li><a href="https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/order.md"><code>eslint-plugin-import/order</code></a> has a more useful grouping, but ignores side-effect imports (just like out <code>import '*.css'</code>).</li>
<li>The amazing <a href="https://github.com/lydell/eslint-plugin-simple-import-sort/"><code>eslint-plugin-simple-import-sort</code></a> is tweakable enough to detect CSS imports, but also forces alphabetic order on all other imports.</li>
</ul>
<p>I got entrenched in an argument about alphabetizing imports, so the issue was swept under the rug for a while. Just as I was about to write a custom plugin, the help arrived in the form of <a href="https://eslint.org/docs/rules/no-restricted-syntax"><code>no-restricted-syntax</code> — an amazing eslint rule</a> that allows you to enforce almost anything, including my CSS import ordering. It lets you describe the code you don't want using ESQuery, a CSS-selector-like query language for ES AST. Sounds fancy and complicated, but if you know CSS (as a front-end developer, you probably do), you quickly get the hang of it.</p>
<p>Let's walk through an example, forcing CSS imports to be last:</p>
<ol>
<li><code>ImportDeclaration</code> matches the AST node for <code>import ...;</code>. A good start, but too loose.</li>
<li>To be more specific, we match only imports of files with <code>.css</code> extension using the amazing regex attribute selector: <code>ImportDeclaration[source.value=/\\.css$/]</code> Much better, but we don't want to ban all CSS imports.</li>
<li>Finally, we can find (watch closely) imports following a CSS import with general sibling selector <code>ImportDeclaration[source.value=/\\.css$/i] ~ ImportDeclaration[source.value!=/\\.css$/i]</code> and ban them!</li>
</ol>
<p>All in all, we end up with</p>
<pre class="language-json"><code class="language-json"><span class="token property">"no-restricted-syntax"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"error"</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">{</span><br /> <span class="token property">"selector"</span><span class="token operator">:</span> <span class="token string">"ImportDeclaration[source.value=/\\.css$/i] ~ ImportDeclaration[source.value!=/\\.css$/i]"</span><span class="token punctuation">,</span><br /> <span class="token property">"message"</span><span class="token operator">:</span> <span class="token string">"CSS import must be last"</span><br /><span class="token punctuation">}</span><span class="token punctuation">]</span></code></pre>
<p>The warning is shown on imports following the CSS import, not the CSS import itself. Not ideal, but it's is a tradeoff you have to make since ESQuery selectors can't look ahead in the tree, just like CSS. On a bright note, you can use the dope <a href="https://drafts.csswg.org/selectors-4/#has-pseudo">CSS4 <code>:has</code> selector,</a> which is not supported in any browser yet.</p>
<p>Two resources I find helpful when working with <code>no-restricted-syntax</code> are:</p>
<ul>
<li><a href="https://esprima.org/demo/parse.html">Esprima demo</a> prints the AST for a JS snippet you provide. Very handy to see what node types and attributes the AST has.</li>
<li><a href="https://github.com/estools/esquery">ESQuery docs</a> describe the supported AST selectors with links to their CSS counterparts. There's also a <a href="https://estools.github.io/esquery/">live playground</a> that lets you try the selectors in a browser, but it doesn't work that great.</li>
</ul>
<p>More and more often I find that writing a quick query for <code>no-restricted-syntax</code> is faster and simpler than looking for a plugin that does what you want, and then configuring it. Hell, it's even easier than trying to recall the name of a built-in rule that you know exists.</p>
<p>Even when a rule <em>is</em> available and you remember the name, <code>no-restricted-syntax</code> may offer some benefits:</p>
<ul>
<li>More maintainable, as in <code>MemberExpression[property.name=/^(add|remove)EventListener$/]</code> vs <a href="https://eslint.org/docs/rules/no-restricted-properties"><code>no-restricted-properties</code></a> with several copies of the rule to ban explicit <code>addEventListener</code> and <code>removeEventListener</code>.</li>
<li>More flexible, as in <code>MemberExpression[property.name=/^(add|remove)EventListener$/][object.name!=/^(document|window)$/]</code> that <em>only</em> allows explicit listeners on <code>document</code> and <code>window</code>.</li>
</ul>
<p>As with anything, there are some weaker points:</p>
<ul>
<li>ESQuery (and regexes) do have a learning curve, and other team members may struggle with editing the rules.</li>
<li>Autofix is clearly not available.</li>
<li>You can't disable a specific restriction per line with <code>/* eslint-disable */</code>, only the whole rule.</li>
<li>A minor inconvenience (or my stupidity), but I could not get slashes in regex attribute matchers to work no matter how much I escaped them (and I went all the way from <code>/</code> to <code>\\\\/</code>).</li>
</ul>
<hr />
<p>Overall, <code>no-restricted-syntax</code> is a very cool rule. It covers probably 95% of the cases where you might want a custom eslint plugin. If you ever wanted to ban some pretty specific thing, but abandoned that idea after a fruitless search for a fitting eslint plugin, it might be your turn to give it a shot!</p>
<p>ESQuery is not almighty — you still can't match across multiple modules or maintain a complex context. Is it time to write a plugin for that? Probably not — stay tuned for my next post on working around eslint limitations!</p>
How to increase test coverage FAST2021-05-31T00:00:00Zhttps://thoughtspile.github.io/2021/05/31/moar-coverage/<p>The second quarter is coming to an end. I suppose a lot of my fellow developers are struggling to meet their ambitious KPI of "20% more test coverage". Fear not — I'll show you a couple of neat tricks that will up your coverage game in no time, so that you can go on with your life (a handy bonus for meeting your goals and exceeding all expectations warranted).</p>
<h2>Verbose code is bro</h2>
<p>When it comes to cope coverage, verbose code covered with tests is your best friend. Compare the following snippets:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// not bro</span><br /><span class="token keyword">const</span> <span class="token function-variable function">isEvenBad</span> <span class="token operator">=</span> <span class="token parameter">x</span> <span class="token operator">=></span> x <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">;</span><br /><span class="token comment">// bro</span><br /><span class="token keyword">function</span> <span class="token function">isEvenBro</span><span class="token punctuation">(</span><span class="token parameter">x</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> counter<span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> counter <span class="token operator">=</span> <span class="token operator">-</span>x<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br /> counter <span class="token operator">=</span> x<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">while</span> <span class="token punctuation">(</span>counter <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> counter <span class="token operator">-=</span> <span class="token number">2</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>counter <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>These functions do the same thing. The key difference is that the first one, when fully tested, gives you just 1 covered line, while the second one is 140% better with 16! Moreover, if you write new tests for the second function, you'll earn a lot of credit with the team for maintaining such a complicated piece of code.</p>
<p>If you notice a co-worker rewriting a well-tested function into a simpler version, make sure to reject his pull request — lowering test coverage and "not understanding the whole complexity" are two great objections.</p>
<h2>Choose the right metrics</h2>
<p>The next point is quite obvious — if you measure several coverage metrics, report the highest one. Usually, function coverage is the one to use, while conditional / branch coverage is normally lower and should not be mentioned. It's not to say that it's not helpful at all — you can refer to its value at the start of the quarter to impress your boss! Learn the formula "We went from 20% <em>(branch, of course)</em> coverage to 60% (<em>function coverage</em>) in just 3 months". Make sure to include a screenshot of a coverage report with a lot of green stuff in your PowerPoint presentation.</p>
<p>Another great trick is to exclude untested code from coverage calculations. <a href="https://jestjs.io/">Jest</a> is an amazing tool that does it by default for JS — only the code that is imported in your tests takes is taken into account.</p>
<h2>Decompose the smart way</h2>
<p>So, function coverage is the right metric to report — but how to increase it? You might naively think that fewer function = better, since stuffing your code into one huge function and testing it yields 100% coverage. That's the spirit, but cramming all the code into a single function is difficult for modern programs. Instead, use the 2 patterns:</p>
<ul>
<li>Find a well-tested function, and split it into as many functions as possible — every one is +1 covered function! Make sure to cal it "decomposing".</li>
<li>Look for functions that are not tested and try to stick their code into the tested ones.</li>
</ul>
<h2>Write generic tests</h2>
<p>I heard modern test frameworks come with lots of differnt assertions — you can compare things, test them with regexes and what not. I don't know who came up with that, but you only need one assertion: <em>function X does not throw.</em> It'll give you the same coverage as the other types, but is easier to write and less likely to break. A perfect test case for our <code>isEvenBro</code> would be</p>
<pre class="language-js"><code class="language-js"><span class="token function">expect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">isEvenBro</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>not<span class="token punctuation">.</span><span class="token function">toThrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>So just take a function, stick some arguments that don't make it explode, and call it a day. Oh, and if it does explode, you can add an <code>expect(...).toThrow()</code> as a corner case test.</p>
<h2>Configuration over code</h2>
<p>Conditionals are bad, because they lower branch coverage. In a perfect world, you'd be working with</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">return</span> <span class="token function">ifElse</span><span class="token punctuation">(</span>x <span class="token operator">></span> y<span class="token punctuation">,</span> x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// not some</span><br /><span class="token keyword">return</span> x <span class="token operator">></span> y <span class="token operator">?</span> x <span class="token operator">:</span> y<span class="token punctuation">;</span></code></pre>
<p>See? The first version has 0 branches, and you can get 100% coverage with 2 tests for <code>ifElse</code> helper.</p>
<p>If a co-worker of yours is a traitor and doesn't like <code>ifElse</code>, try something less in-your-face:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// Good pattern to replace switch {}</span><br /><span class="token keyword">const</span> months <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'January'</span><span class="token punctuation">,</span> <span class="token string">'February'</span><span class="token punctuation">,</span> <span class="token operator">...</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">monthName</span> <span class="token operator">=</span> <span class="token parameter">mNumber</span> <span class="token operator">=></span> months<span class="token punctuation">[</span>mNumber<span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre>
<p>Oh, and while we're at it, a great place for front-end devs to hide the conditionals is CSS:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.Input[data-active=true]</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> black <span class="token punctuation">}</span><br /><span class="token comment">/* setAttribute('data-active', isActive) */</span></code></pre>
<h2>Don't test if you are not obliged to</h2>
<p>The final thing is what I call strategic thinking. Writing tests is easier when you have fewer tests. In the next quarter, avoid promising anything test-related, and just code happily and freely, like a bird, without the tests to slow you down. By the time you commit to another "20% more test this quarter" you'll hopefully have a lot of untested code waiting around for you to meet your goals.</p>
<hr />
<p>On a more serious note, though:</p>
<ol>
<li>Stop being so fucking serious about code coverage.</li>
<li>Coverage is a bad KPI, don't use it just because it's the easiest quality metric to measure.</li>
<li>Havig X% coverage only tells you that X% of your code does not throw under <em>some</em> conditions.</li>
<li>When you do talk about X% coverage, make sure it's the <em>lowest</em> metric measured on your <em>whole</em> codebase (especailly when X=100).</li>
<li>Code-as-configuration flies under the radar of most coverage metrics, be careful with it.</li>
</ol>
<p>Coverage is a tool to help you find missed requirements that are described in your code, not an end goal.</p>
So you think you know everything about React refs2021-05-17T00:00:00Zhttps://thoughtspile.github.io/2021/05/17/everything-about-react-refs/<p>React <a href="https://reactjs.org/docs/refs-and-the-dom.html">refs</a> appear to be a very simple feature. You pass a special prop to a DOM component, and you can access <em>the current DOM node</em> for that component in your JS. This is one of those great APIs that work just the way you'd expect, so you don't even think about how, exactly, it happens. Along my descent into React internals I started noticing that there was more to the ref API than I always thought. I dug deeper, and in this post I'll share my findings with you and provide a few neat ref tricks to help you write better code.</p>
<h2>How react refs are set</h2>
<p>To get the basics out of the way, <code>ref</code> is set to the DOM node when it's mounted, and set to null when the DOM node is removed. No surprises this far.</p>
<p>One thing to note here is that a ref is, strictly speaking, never <em>updated.</em> If a DOM node is replaced by some other node (say, its DOM tag or <code>key</code> changes), the ref is <em>unset,</em> and then set to a new node. (You may think I'm being picky here, but it's goint to prove useful in a minute.) The following code would log <code>null -> <div></code> on rerender (also see <a target="_blank" href="https://codesandbox.io/s/stoic-tereshkova-h51o2?file=/src/App.js">sandbox</a>):</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> ref <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"ref"</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token punctuation">[</span>iter<span class="token punctuation">,</span> rerender<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>ref<span class="token punctuation">}</span></span> <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>iter<span class="token punctuation">}</span></span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">rerender</span><span class="token punctuation">(</span>iter <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> click to remount<br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The part I was not aware of is that the identity of <code>ref</code> prop also forces it to update. When a <code>ref</code> prop is added, it's set to DOM node. When a <code>ref</code> prop is removed, the old ref is set to null. Here, again, the ref is unset, than set again. This means that if you pass an inline arrow as a <code>ref</code>, it'll go through <em>unset / set</em> cycle on every render (<a href="https://codesandbox.io/s/reverent-stallman-swv7q?file=/src/App.js">sandbox</a>):</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> rerender <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"ref"</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">rerender</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> click to remount<br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>So, why does it work that way? In short, it allows you to attach <code>refs</code> conditionally and even swap them between components, as in</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>ul</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>items<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">e<span class="token punctuation">,</span> i</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>i <span class="token operator">===</span> items<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span> <span class="token operator">?</span> lastRef <span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>e<span class="token punctuation">.</span>text<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>li</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> ))}<br /></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>ul</span><span class="token punctuation">></span></span><span class="token plain-text"></span></code></pre>
<p>So far we've leant that refs are <em>set</em> node when the DOM mounts <em>or</em> when the ref prop is added, and <em>unset</em> when the DOM unmounts <em>or</em> the ref prop is removed. As far as I'm concerned, nothing else causes a ref to update. A changing ref always goes through <code>null</code>. If you're fluent in hooks, it works as if the code for DOM components had:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> ref<span class="token punctuation">.</span>current <span class="token operator">=</span> domNode<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> ref<span class="token punctuation">.</span>current <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>ref<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h2>Ref update ordering</h2>
<p>Another important principle specifies the order in which refs are set and unset. The part we rely on the most is that the ref is always set <em>before</em> <code>useLayoutEffect / componentDidMount / Update</code> for the corresponing DOM update is called. This, in turn, means that <code>useEffect</code> and parent <code>useLayoutEffect</code> are also called after the ref is set.</p>
<p>In a single render, all the ref <em>unsets</em> happen before any <em>set</em> — otherwise, you'd get a chance to unset a ref that's already been set during this render.</p>
<p>Next, <code>useLayoutEffect</code> cleanup during re-rendering runs right between ref unset and set, meaning that <code>ref.current</code> is always <code>null</code> there. To be honest, I'm not sure why it works this way, as it's a prime way to shoot yourself in the foot, but this seems to be the case for all react versions with hooks. <a href="https://codesandbox.io/s/polished-sunset-fbs6q?file=/src/App.js">See for yourself</a>.</p>
<p>In contrast, <code>componentWillUnmount</code> and unmount <code>useLayoutEffect()</code> cleanup are called <em>before</em> the ref is unset, so that you get a chance to cleanup anything you've attached to the DOM node, as you can see in a <a href="https://codesandbox.io/s/determined-hamilton-05t27?file=/src/App.js">sandbox</a>.</p>
<p>Here's a chart that summarizes all this timing:</p>
<p><img src="https://thoughtspile.github.io/images/react-ref-order.png?invert" alt="" /></p>
<p>Now I feel like we're getting somewhere in our understanding of <code>refs</code> — but does it have any practical value? Read on!</p>
<h2>Don't use ref.current in useLayoutEffect cleanup</h2>
<p>First off — using dynamic refs in <code>useLayoutEffect</code> cleanup callback is unsafe since you can get an unexpected <code>null</code>. Store <code>ref.current</code> in a closure variable and use that instead:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> ref<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> onClick<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> ref<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> onClick<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">.</span> <span class="token punctuation">[</span>onClick<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// becomes...</span><br /><span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> node <span class="token operator">=</span> ref<span class="token punctuation">.</span>current<br /> node<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> onClick<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> node<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> onClick<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">.</span> <span class="token punctuation">[</span>onClick<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Granted, this only works for arrow refs or when you attach a ref conditionaly, but better safe than sorry, right? At least it's good to know exactly why this breaks and not wrap everything in <code>if (ref.current)</code> just in case.</p>
<h2>You can side effect in ref callback</h2>
<p>A cool and useful implication of this is that you can safely put expensive side effects in a callback ref (or a <code>set current()</code> of a ref object) as long as ref identity does not change. For example, a typical DOM measuring logic:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> el <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token punctuation">[</span>size<span class="token punctuation">,</span> setSize<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setSize</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">getBoundingClientRect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>el<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">;</span></code></pre>
<p>Becomes...</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span>size<span class="token punctuation">,</span> setSize<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> measureRef <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token parameter">node</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setSize</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span><span class="token function">getBoundingClientRect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>measureRef<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>children<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">;</span></code></pre>
<p>Which is slightly cleaner and has one variable less.</p>
<h2>Ref arrows</h2>
<p>There's a subtle difference between having an arrow as your <code>ref</code> prop and a ref object or a stable callback — the arrow has a new identity on every render, forcing the ref to go through an update cycle <code>null</code>. This is normally not too bad, but good to know.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// this does node -> null -> node on every render</span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token parameter">e</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>node <span class="token operator">=</span> e<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br /><span class="token comment">// this doesn't</span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token parameter">e</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>node <span class="token operator">=</span> e<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br /><span class="token comment">// neither does this</span><br /><span class="token function-variable function">setRef</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>node <span class="token operator">=</span> e<span class="token punctuation">;</span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>setRef<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br /><span class="token comment">// this is fine, too</span><br /><span class="token keyword">const</span> ref <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>ref<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span></code></pre>
<h2>setState can be a callback ref</h2>
<p>If you want setting ref to trigger a rerender, you can just pass <code>setState</code> updater as a ref prop. This code will give <code>children</code> access to root DOM node, and will not fall into infinite re-rendering or anything:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span>root<span class="token punctuation">,</span> setRoot<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setRoot<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">RootContext.Provider</span></span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token function">useMemo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> root<span class="token punctuation">,</span> <span class="token punctuation">[</span>root<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>root <span class="token operator">?</span> children <span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">RootContext.Provider</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h2>Merging refs is hard</h2>
<p>Finally, if you implement some kind of ref merging (when you have a <code>forwardRef</code> / <code>innerRef</code>, but also need te DOM node for yourself), you should take care to preserve the guarantees native ref provides, because they are there for a reason. Almost all ref merging mechanisms I've seen in the wild miss some points we've discussed today. The web is full of tutorials that offer you subtly broken solutions. A library with 22K stars <a href="https://github.com/streamich/react-use/blob/master/src/useEnsuredForwardedRef.ts">fails to do it right.</a> Here's <a href="https://github.com/VKCOM/VKUI/blob/master/src/hooks/useExternRef.ts">my best shot</a> at this problem, and I'm still not sure it ticks all the boxes:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">useExternRef</span><span class="token punctuation">(</span><span class="token parameter">externRef</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> stableRef <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token function">useMemo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token keyword">get</span> <span class="token function">current</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> stableRef<span class="token punctuation">.</span>current<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token keyword">set</span> <span class="token function">current</span><span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> stableRef<span class="token punctuation">.</span>current <span class="token operator">=</span> el<span class="token punctuation">;</span><br /> <span class="token function">setRef</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> externRef<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>externRef<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Knowing this, I wouldn't be comfortable with any advanced ref patterns (conditional refs / side effects) on non-DOM components.</p>
<hr />
<p>Now on to a brief recap:</p>
<ul>
<li>Refs are set when the DOM is mounted or a <code>ref</code> prop is added.</li>
<li>Refs are unset when the DOM is removed or a <code>ref</code> prop is removed.</li>
<li>Refs are always unset, then set, and never switch between two nodes directly.</li>
<li>It's safe to use <code>refs</code> conditionaly and even move them between nodes.</li>
<li>The order in which refs are set and unset relative to <code>useLayoutEffect</code> and lifecycle hooks is well defined.</li>
<li>Callback ref can be a side effect or a <code>useState</code> setter</li>
<li>Useing <code>ref.current</code> in <code>useLayoutEffect</code> cleanup is unsafe.</li>
<li>Merging refs is hard, so take care yourself and don't trust the <code>ref</code> prop in components you didn't write.</li>
</ul>
<p>Phew. Now I think we really know everything about react refs.</p>
Did I just build a better useCallback?2021-04-07T00:00:00Zhttps://thoughtspile.github.io/2021/04/07/better-usecallback/<blockquote>
<p>Edit: the technique initially proposed in this post was not concurrent-mode safe. I've added a new section describing a fix to this problem. Thanks to the readers who noticed it!</p>
</blockquote>
<p><code>useCallback</code> has always been one of my least favorite hooks:</p>
<ul>
<li>it does not provide much value over <code>useMemo</code> (as we learnt in my previous post on hooks),</li>
<li>it weirdly treats function as <em>derived data,</em> recreating it on dependency changes, a pattern I haven't seen anywhere else</li>
<li>it requires you to list the variables you reference within a closure, which is boring and flaky, and relies on imperfect static analysis to enforce this.</li>
</ul>
<p>Luckily, we can build a better <code>useCallback</code> ourselves using nothing but <code>useRef</code> and our JS ninja skills.</p>
<h2>A working example</h2>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">FormItem</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> name<span class="token punctuation">,</span> value<span class="token punctuation">,</span> onChange<span class="token punctuation">,</span> <span class="token operator">...</span>props <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> onChange <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">onChange</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span>value<span class="token punctuation">,</span> <span class="token punctuation">[</span>name<span class="token punctuation">]</span><span class="token operator">:</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>onChange<span class="token punctuation">,</span> name<span class="token punctuation">,</span> value<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">HeavyInput</span></span> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onChange<span class="token punctuation">}</span></span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>value<span class="token punctuation">[</span>name<span class="token punctuation">]</span><span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">LoginForm</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>formValue<span class="token punctuation">,</span> setFormValue<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">username</span><span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">password</span><span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">FormItem</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>password<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>formValue<span class="token punctuation">}</span></span> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setFormValue<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">FormItem</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>username<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>formValue<span class="token punctuation">}</span></span> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setFormValue<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>This example perfectly summarizes the downsides of <code>useCallback</code>. Not only did we duplicate all the props we used in a closure, but also consider what happens when we update the password field:</p>
<ol>
<li>Password <code>HeavyInput</code> triggers <code>setFormValue({ password: '123', username: '' })</code></li>
<li><code>formValue</code> reference updates</li>
<li><em>Both</em> <code>FormItem</code>s re-render, which is fair enough</li>
<li><code>onChange</code> in username <code>FormItem</code> updates, too, since value reference updated</li>
<li><code>HeavyInput</code> in username <code>FormItem</code> re-renders, because <code>FormItem</code>'s <code>onChange</code> has a new reference</li>
</ol>
<p>This may be OK with 2 fields, but what about a hundred? What about when your callback has so many dependencies something updates on every render? You might argue that the components should have been modeled some other way, but there is nothing conceptually wrong with this one that can't be fixed with a better <code>useCallback</code>.</p>
<h2>The classic solution</h2>
<p>Back with class components we had no hooks, but changes in callback prop reference did trigger useless child component update, just as it does now (hence <a href="https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md"><code>react/jsx-no-bind</code> eslint rule</a>). The solution was simple: you create a class method (or, lately, into a property initializer) to wrap all the <code>props</code> references you need, and pass this method as a prop instead of an arrow:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">class</span> <span class="token class-name">FormItem</span> <span class="token keyword">extends</span> <span class="token class-name">Component</span> <span class="token punctuation">{</span><br /> <span class="token function-variable function">onChange</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span><span class="token function">onChange</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>value<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>name<span class="token punctuation">]</span><span class="token operator">:</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">HeavyInput</span></span> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>onChange<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p><code>onChange</code> method is created in constructor and has a stable reference throughout the lifetime of the class, yet accesses fresh props when called. What if we just applied this same technique, just without the class?</p>
<h2>The proposal</h2>
<p>So, without further adue, let me show you an improved <code>useCallback</code>:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">useStableCallback</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">callback</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> onChangeInner <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> onChangeInner<span class="token punctuation">.</span>current <span class="token operator">=</span> callback<span class="token punctuation">;</span><br /> <span class="token keyword">const</span> stable <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>args</span><span class="token punctuation">)</span> <span class="token operator">=></span> onChangeInner<span class="token punctuation">.</span><span class="token function">current</span><span class="token punctuation">(</span><span class="token operator">...</span>args<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> stable<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Watch closely:</p>
<ol>
<li><code>onChangeInner</code> is a <em>box</em> that always holds the fresh value of our <code>callback</code>, with all the scope it has.</li>
<li>Old <code>callback</code> is thrown away on each render, so I'm pretty sure it does not leak.</li>
<li><code>stable</code> is a callback that never changes and only references <code>onChangeInner</code>, which is a stable <em>box</em>.</li>
</ol>
<p>Now we can just swap <code>useCallback</code> for <code>useStableCallback</code> in our working example. The dependency array, <code>[onChange, name, value]</code>, can be safely removed — we don't need it any more. The unnecessary re-renders of <code>HeavyInput</code> magically disappear. Life is wonderful once again.</p>
<p><strong>There is one problem left: this breaks in concurrent mode!</strong></p>
<h2>Concurrent mode</h2>
<p>While React's <a href="https://reactjs.org/docs/concurrent-mode-intro.html" target="_blank">concurrent mode</a> is still experimental and this code is completely safe when used outside it, it's good to be future-proff when you can. A concurrent-mode call to render function does not guarantee the DOM will update right away, so by changing the value of <code>onChangeInner.current</code> we're essentially making future <code>props</code> available to the currently mounted DOM, which may give you surprising and unpleasant bugs.</p>
<p>Following in the footsteps of an exciting <a href="https://github.com/facebook/react/issues/14099#issuecomment-440013892" target="_blank">github issue in react repo,</a> we can fix this:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">useStableCallback</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">callback</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> onChangeInner <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span>callback<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Added useLayoutEffect here</span><br /> <span class="token function">useLayoutEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> onChangeInner<span class="token punctuation">.</span>current <span class="token operator">=</span> callback<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> stable <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>args</span><span class="token punctuation">)</span> <span class="token operator">=></span> onChangeInner<span class="token punctuation">.</span><span class="token function">current</span><span class="token punctuation">(</span><span class="token operator">...</span>args<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> stable<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>The only thing we've changed was wrapping the update of <code>onChangeInner</code> in a <code>useLayoutEffect</code>. This way, the callback will update <em>immediately after</em> the DOM has been updated, fixing our problem. Also note that <code>useEffect</code> would not cut it — since it's not called right away, the user might get a shot at calling a stale callback.</p>
<p>One drawback of this solution is that now we can't use the function returned inside the render function since it has not been updated yet. Specifically:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> logValue <span class="token operator">=</span> <span class="token function">useStableCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>props<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// will log previous value</span><br /><span class="token function">logValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>logValue<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">What is the value?</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span></code></pre>
<p>We don't need a stable function reference to call it during render, so that works for me.</p>
<h2>Wrapping up</h2>
<p>When compared to React's default <code>useCallback</code>, our proposal with a totally stable output:</p>
<ul>
<li>Simplifies the code by removing explicit dependency listing.</li>
<li>Eliminated useless updates of child components.</li>
<li>Obtained a totally stable wrapper for callback props that can be used in <code>setTimeout</code> or as a native event listener.</li>
</ul>
<p>At a cost of not being able to call it during render. For me, this sounds like a fair deal.</p>
How useRef turned out to be useMemo's father2021-04-05T00:00:00Zhttps://thoughtspile.github.io/2021/04/05/useref-usememo/<p>It's no secret that react's <code>useCallback</code> is just sugar on top of <code>useMemo</code> that saves the children from having to see an arrow chain. As <a href="https://reactjs.org/docs/hooks-reference.html" target="_blank">the docs</a> go:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">onChange</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>onChange<span class="token punctuation">,</span> id<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// is equivalent to</span><br /><span class="token function">useMemo</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">onChange</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>onChange<span class="token punctuation">,</span> id<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><img src="https://thoughtspile.github.io/images/hook-vader.jpg" alt="" /></p>
<blockquote>
<p>A less known, probably useless, but very fun, fact: you can actually pass something other than a function to <code>useCallback</code> and have it memoized.</p>
<pre class="language-js"><code class="language-js"></code></pre>
</blockquote>
<p>const stableValue = useCallback({ please: 'dont do this' }, []);
// yes-yes, stableValue is that object, as of react@17.0.2</p>
<pre><code>
As I got more into hooks, I've been surprised to realize how similar `useMemo` itself is to `useRef`. Think about it that way: `useRef` does a very simple thing — persists a value between render function calls and lets you update it as you wish. `useMemo` just provides some automation on top for updating this value when needed. Recreating `useMemo` is fairly straightforward:
```jsx
const memoRef = useRef();
const lastDeps = useRef(deps);
// some shallow array comparator, beside the point
if (!arrayEquals(deps, lastDeps.current)) {
memoRef.current = factory();
lastDeps.current = deps;
}
const memoized = memoRef.current;
// ... is equivalent to const memoized = useMemo(factory, deps);
</code></pre>
<p>As a special case, raw <code>useRef</code> is <em>almost</em> the same as <code>useMemo</code> with no deps, save for actually building the initial value on every render and then throwing it away:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> stableData <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span>current<span class="token punctuation">;</span> <span class="token comment">// same as useMemo(() => {}, []);</span></code></pre>
<p>Treating <code>useRef</code> as a stripped-down <code>useMemo</code> can prove useful in some cases. If the built-in caching mechanism does not work for you, <code>useRef</code> is a perfect way to tweak it. Some motivational examples:</p>
<ul>
<li>Actually cache all the previous results using eg <a href="https://github.com/caiogondim/fast-memoize.js" target="_blank">fast-memoize.</a> <code>useMemo</code> appears to just cache the last result, which is a good default.</li>
<li>Support true array dependencies with dynamic length: <code>useArrayMemo(() => hash(arrayValues), arrayValues)</code></li>
<li>Use an object instead of an array: <code>useObjectMemo(() => props, props)</code> gives you the same reference unless a prop has changed.</li>
<li>More generally, allow any custom comparator for deps: <code>useCustomMemo(() => lib.sum(table1, table2), [table1, table2], (a, b) => a.equals(b))</code></li>
</ul>
<p>These may not be the most common use cases, but it's good to know that this is doable, and that <code>useRef</code> is there to help you in case you ever need it.</p>
<blockquote>
<p>On to another fun fact — you can make <code>useMemo</code> return a constant-reference <code>RefObject</code> box, equivalent to <code>useRef</code>. It's not clear why you would want that.</p>
<pre class="language-js"><code class="language-js"></code></pre>
</blockquote>
<p>useMemo(() => ({ current: initialValue }), [])</p>
<pre><code>
---
So, wrapping up:
1. `useCallback` is just tiny sugar on top of `useMemo`.
2. `useMemo` is just `useRef` with auto-update functionality.
3. You can build customized versions of `useMemo` with `useRef`.
4. You _can_ bend `useCallback` to be a `useMemo`, and you can get `useMemo` to be a `useRef`, but that doesn't mean you _should._
On the other hand, `useState` (and `useReducer`) is an entirely different cup of tea, since they can trigger a rerender on update. More on these guys in the next post!
</code></pre>
How to timeout a promise2021-04-02T00:00:00Zhttps://thoughtspile.github.io/2021/04/02/promise-timeout/<p>Timeouts are one of the key building blocks to make your app stable. In short, if you send a request to an endpoint and a response does not, for whatever reason, come soon, we act as if the request failed and fall back to plan B — try again, show an error message and let the user decide what to do next, or use cached data. This is a great remedy for all kinds of flaky-web trouble: slow networks, clogged backends, overloaded databases — your user will never have to watch a spinner forever.</p>
<p>Fetch API has a <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal" target="_blank">signal</a> option to abort the request, but I wondered if it could be done using promises only. So, starting with a simple fetch, let's see if we can fit a timeout in there:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> load <span class="token operator">=</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>I bet we can, and let me show you how! First things first, we need to pull a callback-based JS timeout into the promise world. Here's how we do it:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">ok</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setTimeout</span><span class="token punctuation">(</span>ok<span class="token punctuation">,</span> <span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Pretty basic stuff, and very useful. We use the promise constructor to set the timeout that resolves the promise created after 5 seconds.</p>
<p>The next bit we need is <code>Promise.race</code>, the little brother of the famous <code>Promise.all</code> that is useful for about one real world thing, so no one really cares about it. Quoting <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race" target="_blank">MDN,</a> <em>Promise.race() returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.</em> That's exactly what we need!</p>
<ul>
<li>If load promise resolves before timeout — we're good to go.</li>
<li>If load promise rejects — fall back to plan B.</li>
<li>If the timeout fires first — fall back to plan B, just as if loading failed.</li>
</ul>
<p>Putting it all together and tweaking our timeout-promise to make it <em>reject</em> (we're <em>bad,</em> not <em>good,</em> if we hit it) gives us this wonderful snippet:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> load <span class="token operator">=</span> Promise<span class="token punctuation">.</span><span class="token function">race</span><span class="token punctuation">(</span><span class="token punctuation">[</span><br /> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">_<span class="token punctuation">,</span> fail</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">fail</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Timeout'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><br /> <span class="token punctuation">(</span><span class="token parameter">res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">/* process as you wish */</span> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">/* retry or display error */</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>What's even better, this technique, unlike <code>AbortSignal</code>, works not only for <code>fetch</code>, but for any promise-based operation: just replace <code>fetch</code> call above with <code>yourApiLayer.load()</code>, <code>DBQuery.execute()</code>, <code>serviceMesh.callRPC()</code> or whatever async stuff you want to timeout, and you're good to go.</p>
Extravagantly fast rendering with React benders2019-01-06T00:00:00Zhttps://thoughtspile.github.io/2019/01/06/vdom-bend-memo/<p>The other day I was working on a React-based library of huge, reusable SVG images, and I ran into performance problems. Just kidding, I've never had a problem I'm solving here, but I've had great fun working around it. I wanted to make components producing mostly static DOM as fast to render as humanly possible. And I'm not talking just about updates — I wanted to optimize mounting. Of course, normally you'd just skip remounting and hide / show the component with CSS, but that's not fun enough for me. Let's say we want to render it fast in random locations of your app.</p>
<p>Let's start with something simple — a component with no props. Since the renders are pure, the component always returns the same markup, which makes it a perfect candidate for our game. I know, I know, context and connected components, but let's ignore these for a while. An SVG image works well — think this one, but huge:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Icon</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>svg</span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://www.w3.org/2000/svg<span class="token punctuation">"</span></span> <span class="token attr-name">version</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>1.1<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>192.25<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>66.056<span class="token punctuation">"</span></span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>4.5 -5.222 192.25 66.056<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>circle</span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#000000<span class="token punctuation">"</span></span> <span class="token attr-name">cx</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>37.637<span class="token punctuation">"</span></span> <span class="token attr-name">cy</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>28.418<span class="token punctuation">"</span></span> <span class="token attr-name">r</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>28.276<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>svg</span><span class="token punctuation">></span></span><br /><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Also check out the <a href="https://codesandbox.io/s/j9xp6pqo5">codesandbox</a> with the complete code.</p>
<h2>Pure</h2>
<p>Our first intuitive take is to make the component pure:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">class</span> <span class="token class-name">PureIcon</span> <span class="token keyword">extends</span> <span class="token class-name">PureComponent</span> <span class="token punctuation">{</span><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Icon</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><span class="token comment">// or, with a dash recompose</span><br /><span class="token keyword">const</span> PureIcon <span class="token operator">=</span> <span class="token function">pure</span><span class="token punctuation">(</span>Icon<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Fine, this helps make the updates almost free. But mounting the component still takes time. Honestly, does it make sense to build the vDOM every time we mount, given that we know it'll always be the same? No. Great, we can make it better.</p>
<h2>Global vDOM memoization</h2>
<p>So, we want to cache the vDOM globally instead of building it from scratch every time we mount. Note that we can't use <code>_.memoize(Icon)</code> because Icon is still called with the props argument that gets a new reference every time. No problem, we'll write it ourselves:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> cache <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">ManualMemoIcon</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>cache<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> cache <span class="token operator">=</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Icon</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">return</span> cache<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>So, we only build the vDOM (<code><Icon /></code>) on the first mount, application-wide. Should be good? It's not. The premise of React that building vDOM is cheap is true, after all. Once React quickly gets the vDOM, it goes and starts building the real DOM — painfully slowly. We can strip a fraction of the mount time, but the updates were better with <code>Pure</code>. We have to skip the vDOM-to-DOM step if we want to succeed. Can I do that? You bet I can!</p>
<h2>DOM memoization</h2>
<p>So, caching vDOM does not help us much — we need to cache the real DOM. The plan is simple: when we first (application-wide first) mount the component, we take the resulting DOM and put it into a variable. On subsequent mounts we don't really render anything, but clone the saved DOM subtree into our mount point. In code:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> domCache<span class="token punctuation">;</span><br /><span class="token keyword">class</span> <span class="token class-name">DomMemoIcon</span> <span class="token keyword">extends</span> <span class="token class-name">PureComponent</span> <span class="token punctuation">{</span><br /> <span class="token function">componentDidMount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>domCache<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>el<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>domCache<span class="token punctuation">.</span><span class="token function">cloneNode</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br /> domCache <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>el<span class="token punctuation">.</span>firstChild<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// yes, there may be minor trouble with simultaneous first mounts</span><br /> <span class="token keyword">const</span> children <span class="token operator">=</span> domCache <span class="token operator">?</span> <span class="token keyword">null</span> <span class="token operator">:</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Icon</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token parameter">e</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>el <span class="token operator">=</span> e<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span> children <span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>This works! In my truck benchmark, we're down from 25 to 5 ms — pretty amazing! Let's now focus on what we've lost in the process:</p>
<ul>
<li>No props. We can get away with some <em>if</em> we promise that they'll always be the same.</li>
<li>No context, no connected children.</li>
</ul>
<p>Sounds like a fair deal to me. But can we go faster yet? Ha-ha, we can. If you never have more than one component instance mounted at the same time, you can skip the <code>.cloneNode</code>. Or you could add instance counting to treat this as a special case.</p>
<h2>Adding props</h2>
<p>But we like props, props are cool! Can we please have them back? Maybe we can. If the component's <em>prop space</em> — that is, all the prop combinations we ever use — is fairly small, we could put the DOMs for each prop object into a map of sorts: <code>{ [prop object] -> resulting DOM }</code>, then try to retrieve the DOM from cache on each mount / update. I'll leave implementing this to you as an exercise.</p>
<hr />
<p>So, do I suggest actually using this? Probably not, it's very tricky and not very useful. If you can make it work for you — great, let me know. Anyways, it's very cool that we can stuff like this is doable with React.</p>
10 Design Rules for Programmers2018-12-17T00:00:00Zhttps://thoughtspile.github.io/2018/12/17/design-crash-course/<p>For some reason, many developers disdain design. We are programmers, we are smart and rational, and we think technically. Designers are weird and artistic, they wear black sweaters and long scarves, they are no match to us. I never quite understood how you can ignore design if you do any front-end job.</p>
<p>Knowing core design principles will help you understand <em>why</em> your designer did what he did, will allow you to spot and fix "design typos", and will also go great lengths to freeing you from pixel-perfect layout slavery. Also, if you ever land in a team without a designer, your product might not make users' eyes bleed.</p>
<p>These rules are fundamental, and not related to <em>web</em> design specifically: use them for documents, presentations, posters — anything you want. Good design does not necessarily mean beautiful; your primary goal is to get information through to someone, and this is exactly where design helps. Just 10 simple rules (OK, to be honest, it's 5 simple rules, 2 difficult rules and 3 tricks to cover-up your mess) — let's go.</p>
<h2>1. Layout</h2>
<p>Layout does not only refer to CSS, but also to the overall arrangement of things on the page. It should guide your way whether you're working with text, images, controls or color blocks. If you're getting bored reading this, please get bored <em>after</em> this section, as it's crucial.</p>
<h3>1.1. Align things</h3>
<p>Our brains love order. Even if you live in a messy apartment, loathe military and corporations, and watch Jean-Luc Godard every day, your brain still enjoys order a great deal. The principal way of adding visual order (and thus pleasing the brain) is through alignment. When elements on your page have their boundaries misaligned, the overall design looks messy. When they lie on a common line, it looks sharp and nice.</p>
<p>Align left sides:</p>
<figure>
<img style="display: inline-block; max-width: 50%; vertical-align: top;" src="https://thoughtspile.github.io/images/design-rules/align-bad-2-gmail.png" /><img style="display: inline-block; max-width: 50%; vertical-align: top;" src="https://thoughtspile.github.io/images/design-rules/align-good-2-yamail.png" />
<div style="color: #555; text-align: center;">Mobile Gmail alignment is very weird — look at that pyramid! We at Yandex.mail have done a much better job here.</div>
</figure>
<p>And also align the baselines:</p>
<figure>
<img style="display: block; width: 100%;" src="https://thoughtspile.github.io/images/design-rules/align-bad-3.png" /><img style="display: block; width: 100%;" src="https://thoughtspile.github.io/images/design-rules/align-better-3.png" />
<div style="color: #555; text-align: center;">UX aside (are we searching in this category? are these even related?), it looks better aligned.</div>
</figure>
<h3>1.2. Level up: align things <em>visually</em></h3>
<p>Mechanically aligning the CSS boxes is not enough. Depending on the shape of an object, it may appear aligned when sitting to the left or to the right of the real alignment guide. Generally, rectangular things are fine right on that line, round things want to cross it a bit, and pointy things cross it even further. If you want a physical analogy, think of aligning objects' centers of gravity, not the endpoints.</p>
<figure>
<img style="display: block; width: 100%;" src="https://thoughtspile.github.io/images/design-rules/align-good-1.png" />
<div style="color: #555; text-align: center;">Smart and clean design from DDG — note how the text is aligned, and the input padding hangs left.</div>
</figure>
<figure>
<img style="display: block; width: 100%;" src="https://thoughtspile.github.io/images/design-rules/align-good-3.png" />
<div style="color: #555; text-align: center;">See the pointy shield bottom hang below the baseline.</div>
</figure>
<h3>1.3. Don't crush things together</h3>
<p>The single best way to make your design look amateurish is putting things too close together. I understand the motivation — you want more information to fit on the screen, or maybe the management just gave you one more menu item and you decided to stick it there since it <em>just fits</em>. The sad truth is, things crushed together look bad.</p>
<figure>
<img style="display: block; width: 100%;" src="https://thoughtspile.github.io/images/design-rules/whitespace-bad-1.jpg" />
<div style="color: #555; text-align: center;">Meh, why are the card paddings so tiny? Most of the time you're tempted to use borders because the content is not spaced enough, and by "fixing" it you accidentally create a visual conflict instead.</div>
</figure>
<p>This is also the hardest point to formulate as a precise rule — I have no idea exacly how much space you should leave between which things. Just don't be afraid to leave some parts of the screen blank. Move things around, ask your friends, and see what works. You'll develop a sense for it over time.</p>
<figure>
<img style="display: block; width: 100%;" src="https://thoughtspile.github.io/images/design-rules/whitespace-bad-2.png" />
<img style="display: block; width: 100%;" src="https://thoughtspile.github.io/images/design-rules/whitespace-better-2.png" />
<div style="color: #555; text-align: center;">I understand the cues from phone book design, but it's not that we can save money on paper when making a website. Also, the font size could be a bit larger — see below.</div>
</figure>
<h3>1.4. Level up: move unrelated things even further apart</h3>
<p>Tne distance between objects in your layout is the visual way of telling people what belongs together, and what is unrelated. The thought experiment to do here is: replace the text with grey rectangles — can you still tell which headers, captions and info blocks (or, simply put, grey rectangles) belong to which other items?</p>
<figure>
<img style="display: inline-block; max-width: 50%; vertical-align: top;" src="https://thoughtspile.github.io/images/design-rules/closeness-bad-1.png" /><img style="display: inline-block; max-width: 50%; vertical-align: top;" src="https://thoughtspile.github.io/images/design-rules/closeness-better-1.png" />
<div style="color: #555; text-align: center;">This bold text is a caption for an image that failed to load — or is it? Nice combo with *too* much whitespace and inconsistent closeness. Quick fix — move the headers closer to the related section text.</div>
</figure>
<h2>2. Text</h2>
<p>Typography might not seem immediately applicable to a typical web application, but text plays a major role in content-based websites. These rules guide mostly the body text — you can skip it for headers, navigation links, form labels and other short items. This also depends on the font and the background, but we'd be going too deep for this post. Maybe next time.</p>
<h3>2.1. Opt for legibility over design</h3>
<p>Yes, thin, grey and small letters for some reason look more sophisticated. However, when you actually intend the text to be read, go for legibility over <em>design</em> every day. That means:</p>
<ul>
<li>Font size that is readable on all screens. The 16px default works well, and never make body text smaller than 14px.</li>
<li>High contrast. Go for black text on white background. #333 would be the lightest possible, if you want to stretch it. If you decide to invert it (light text, dark background), make sure you have a good reason — "night mode" is about all I can think of.</li>
</ul>
<figure>
<img style="display: block; width: 100%;" src="https://thoughtspile.github.io/images/design-rules/contrast-bad-1.png" />
<div style="color: #555; text-align: center;">This greyish blue over greyish blue is too much even for 2 words.</div>
</figure>
<figure>
<img style="display: block; width: 100%;" src="https://thoughtspile.github.io/images/design-rules/contrast-bad-2.png" />
<div style="color: #555; text-align: center;">No, using grey text does not make you a cool designer.</div>
</figure>
<figure>
<img style="display: block; width: 100%;" src="https://thoughtspile.github.io/images/design-rules/contrast-bad-3.jpg" />
<div style="color: #555; text-align: center;">Using generic text for your designs is a straight way to fail here — no one reads it during the design phase.</div>
</figure>
<h3>2.2. Align left</h3>
<p>There is one true alignment for the web, and that's left alignment. People read from left to right, and it makes sense to help your readers' eyes find the next line. Right and center alignment definitely don't give you predictable line location.</p>
<p>But what about fitting text to width, you might ask? The books do it that way, and text blocks are nice and rectangular. True, but that requires hyphens — since the quantity of words on a line is not fixed, you have to stretch the spaces either between the words, or between the letters. The text becomes harder to read — the brain doesn't know where to look for the next character. Hyphenation on the web kinda works, but usually puts a hyphen on each line — something you don't see in books. TeX hyphenation is remarkably good, but we're not there yet, and I don't think it's possible in real-time yet. Granted, this sin rarely appears in website design (thsnk god!), but keep it in mind when making documents.</p>
<figure>
<img style="display: block; width: 100%;" src="https://thoughtspile.github.io/images/design-rules/text-align-justify.jpg" />
<div style="color: #555; text-align: center;">Just look at these spaces jump!</div>
</figure>
<p>By now, relate the rules together: if you treat the lines as abstract blocks, this corresponds to the "adjust stuff" rule, and gives your design a cleaner appearance.</p>
<h3>2.3. Keep line length around 60 characters</h3>
<p>With long lines, the readers quickly lose track of where they are on a line. With short lines, you have to move your eyes around too much. Also, left alignment + short lines leaves you with an overtly jarred right edge, because the word length varies. 50 to 70 characters works best.</p>
<figure>
<img style="display: block; width: 100%;" src="https://thoughtspile.github.io/images/design-rules/long-lines-3.png" />
<div style="color: #555; text-align: center;">Long lines make you super sleepy — I couldn't force myslef to read this even for the sake of this post.</div>
</figure>
<p>If you can't quite fit it there, try changing the font size. You can also bypass it for shorter text: 3 lines of 20 chars each is not a big deal.</p>
<h2>3. Notes on beauty</h2>
<p>If you educate yourself on the above rules and consistently apply them to your designs, you'll end up with bearable but boring pages. We're getting dangerously deep here, but I find these three tips to give you most bang for your buck.</p>
<h3>3.1. Use colors the smart way</h3>
<p>The first thing people do when trying to make their design more <em>beautiful</em> is painting things into random colors. While some designers can pull it off on some occasions, thinking <em>you</em> can is arrogant. Restrict yourself to shades of grey for text and background and a bright accent color to highlight random things — you might want to use your corporate color here.</p>
<figure>
<img style="display: block; width: 100%;" src="https://thoughtspile.github.io/images/design-rules/dieter-rams.jpg" />
<div style="color: #555; text-align: center;">No need to pretend — we're all under a big shadow of modernist design. [Dieter Rams](https://hackernoon.com/dieter-rams-10-principles-of-good-design-e7790cc983e9) here.</div>
</figure>
<p>You don't even need all the shades of grey (no allusions here) — 7 (including white and black) is more than enough. Add more, and the users' brains would run around trying to prioritize random grey colors that happen to be close by, then settle on the impression that this design is garbage.</p>
<p>If you want more colors, I really like the <a href="https://medium.com/eightshapes-llc/color-in-design-systems-a1c80f65fa3">approach</a> design systems community has taken here with semantic color schemes. Basically, you define a set of action types, and consistently apply colors to them across the interface — think "red for destruction, green for creation" and further fun like "purple for drag-n-drop".</p>
<h3>3.2. Every interaction is better with an animation</h3>
<p>You don't need to be a motion designer to slap a <code>transition: all 300ms ease</code> into your stylesheet. A sharp transition is harsh and old. An animated transition is smooth and pleasant. These values work particularly well by default — any shorter, and the transition is not noticeable, any longer and the transition becomes annoying.</p>
<p>If you develop for desktop, hover effects work nice. Hover styles by themselves have a reassuring effect on the user — the element is interactive, the mouse is in the right place. With a transition, it's even better.</p>
<figure>
<style>.btn { line-height: 2; padding: 3px 20px; border-radius: 5px; background: #7d7; border: none; font-size: 20px; color: #111; margin-bottom: 10px; } .btn:active, .btn:focus { outline: none } .btn-hov:hover { background-color: #afa } .btn-trans { transition: all 300ms 0ms ease }</style>
<button class="btn">I am a dumb button. Am I even active?</button>
<button class="btn btn-hov">I am more usable but harsh</button>
<button class="btn btn-hov btn-trans">I am a pleasant button</button>
</figure>
<p>More generally, any transition that involves elements appearing from nowhere or disappearing look more natural with a transition. I know that means more React work — for one, you have to delay unmounting — but trust me, it's worth it.</p>
<h3>3.3. Everything is better with icons</h3>
<p>I'm not quite sure how it works, but text-only designs look, for lack of a better word, poor and amateurish. But drop some icons in there, and they become smart and professional! Maybe it's that icons contain enough detail to distract the brain from layout imperfections.</p>
<figure>
<img style="display: inline-block; max-width: 50%; vertical-align: top;" src="https://thoughtspile.github.io/images/design-rules/icons-bad-1.png" /><img style="display: inline-block; max-width: 50%; vertical-align: top;" src="https://thoughtspile.github.io/images/design-rules/icons-good-1.png" />
<div style="color: #555; text-align: center;">No icons — looks like a poor man's try. Add icons, and you're good to go.</div>
</figure>
<p>I hear you objecting that it takes a designer to draw icons. Fear not, creative commons got you covered. I'm sure you already know font awesome and its friend, glyphicons. If you want to go above that, there's a couple fo websites with CC icons: <a href="https://thenounproject.com/">The Noun Project</a> and <a href="https://icons8.com/">Icons8</a>. If you need something really special, don't be afraid to draw your own in Inkscape — you can handle circle + line (that's search), a couple of lines (close, menu) or even a triangle (arrows, or, if you add a line, pencil for edit). Don't be shy, unleash your creativity.</p>
<p>One thing to be aware of here is the stylistic integrity of your icon set. This means, mostly: choose filled or outlined icons, sharp or round corners, detail / stroke width, and stick with it. One of the more overlooked consequences here is that you can't just randomly scale icons — the widths would look off. Take care.</p>
<h2>Bonus point: mind the context</h2>
<p>Good design is always contextual. The principles I described here have their roots in western modernism, that happens to be appealing to educated western people, and, coincidentally, is a safe default for an average webapp / website. They are also pretty generic and neutral. Don't blindly slap these rules everywhere and claim that you've just made it better:</p>
<ul>
<li>A website for Chinese audience — from my point of view, you should stuff as much as you can there, and make some of it golden, but there might be some more sophisticated design language underlying these beauties.</li>
<li>A children's book. Don't assume that children are stupid adults with a knack for bright colors, either.</li>
<li>The promotional website of a pop star tour — you have to do better than neutral to make people drop their monthly salary on a fiesta,</li>
</ul>
<hr />
<p>You haven't just magically become a designer by knowing these principles. Go and apply them to every single thing you make, year after year. You'll find that everything makes more sense now, but you're still not a designer. There's much more to learn. Also: yes, you can find these rules broken right here on this page. Go on and tell me — that counts as practice!</p>
Keep a React component mounted2018-12-02T00:00:00Zhttps://thoughtspile.github.io/2018/12/02/react-keep-mounted/<p>The second most important React optimization technique after <code>shouldComponentUpdate</code> and friends is remount management. Some portions of the UI can be hidden or shown — sidebars, drop-down menus, modals and draggable widgets are all prominent examples. The basic React pattern for conditional rendering is boolean short-circuiting:</p>
<pre class="language-js"><code class="language-js"><span class="token punctuation">{</span>condition <span class="token operator">&&</span> <span class="token operator"><</span>Component data<span class="token operator">=</span><span class="token punctuation">{</span>data<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">}</span></code></pre>
<p>However, if you go this way, you create DOM elements every time the component is displayed. As the component grows in size, the lag between the interaction and mounting can become noticeable. You can combat this (Vue and Angular even have this functionality built-in) by keeping the component rendered unconditionally and hiding it with CSS:</p>
<pre class="language-js"><code class="language-js"><span class="token operator"><</span>div style<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">display</span><span class="token operator">:</span> condition <span class="token operator">?</span> <span class="token keyword">null</span> <span class="token operator">:</span> <span class="token string">'none'</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">></span><br /> <span class="token operator"><</span>Component data<span class="token operator">=</span><span class="token punctuation">{</span>data<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><br /><span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span></code></pre>
<p>You also get to preserve <code>Component</code>'s state for free along with the DOM state (scroll, focus, and input position). However, this solution has drawbacks, too:</p>
<ol>
<li>You mount the component on startup, even if the user never accesses it. Mounting multiple components at the same time can accumulate to very sluggish start-up performance.</li>
<li>You update the component even when it's invisible, which may or may not be what you want.</li>
</ol>
<p>I'm about to propose a solution that walks the middle ground between the two: you mount the component when the user first sees it, subsequent toggles use CSS. You can also control whether you want the hidden component to update with an option — I'll provide an extended study of the use cases below. This is more of a straw-man proposal than something I'm ready to wrap into a library, so any discussion is welcome. Play with the code (I've opted for preact to show how this method applies to any JSX-based solution) at <a href="https://codesandbox.io/s/82jo98o708">codesandbox</a>.</p>
<h2>The solution</h2>
<p>Let's start by wrapping the CSS solution into a component with a render prop:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">KeepMounted</span> <span class="token keyword">extends</span> <span class="token class-name">Component</span> <span class="token punctuation">{</span><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> isMounted<span class="token punctuation">,</span> render <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token operator"><</span>div style<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">display</span><span class="token operator">:</span> isMounted <span class="token operator">?</span> <span class="token keyword">null</span> <span class="token operator">:</span> <span class="token string">'none'</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">></span><br /> <span class="token punctuation">{</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><br /> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token comment">// use as</span><br /><span class="token operator"><</span>KeepMounted<br /> isMounted<span class="token operator">=</span><span class="token punctuation">{</span>condition<span class="token punctuation">}</span><br /> render<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator"><</span>Component data<span class="token operator">=</span><span class="token punctuation">{</span>data<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">}</span><br /><span class="token operator">/</span><span class="token operator">></span></code></pre>
<p>Now we make one minor adjustment: only call <code>render</code> once <code>isMounted</code> has been set to <code>true</code> once. I do it this way:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">KeepMounted</span> <span class="token keyword">extends</span> <span class="token class-name">Component</span> <span class="token punctuation">{</span><br /> hasBeenMounted <span class="token operator">=</span> <span class="token boolean">false</span><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> isMounted<span class="token punctuation">,</span> render <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>hasBeenMounted <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>hasBeenMounted <span class="token operator">||</span> isMounted<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token operator"><</span>div style<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">display</span><span class="token operator">:</span> isMounted <span class="token operator">?</span> <span class="token keyword">null</span> <span class="token operator">:</span> <span class="token string">'none'</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">></span><br /> <span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>hasBeenMounted <span class="token operator">?</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">}</span><br /> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>You might argue that <code>hasBeenMounted</code> belongs in <code>state</code>, but in this case it works better this way. <code>KeepMounted</code> never triggers visibility change by itself, and synchronizing store and prop updates either limits compatibility with older versions of <code>React</code> given the <code>componentWillUpdate</code> havoc, or forces double rendering if using <code>componentDid*</code>. But what was that thing about bypassing updates that I wanted?</p>
<h3>Preventing updates</h3>
<p>At a first glance, there's no reason to re-render the component when it's hidden. But the component can produce very different DOM depending on the state: say, it's a list that grows from 1 to 1000 items while it's hidden. In that case, updating it once it's displayed is not much cheaper than mounting it from scratch. Adding items one at a time while it's hidden will not be noticeable. There's no right solution here, simply thinking about this helps a lot.</p>
<p>React component updates when either their parent updates (possibly inducing prop changes), or the component's own state changes (this includes connecting to a state manager). We can bypass the update-from-parent when the component is hidden by not calling <code>render()</code>. We need some fallback elements to use — the last <code>render</code> output will do.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">KeepMounted</span> <span class="token keyword">extends</span> <span class="token class-name">Component</span> <span class="token punctuation">{</span><br /> children <span class="token operator">=</span> <span class="token keyword">null</span><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// And I even have an option to choose the desired behaviour:</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> isMounted<span class="token punctuation">,</span> updateUnmounted<span class="token punctuation">,</span> render <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>children <span class="token operator">=</span> <span class="token punctuation">(</span>isMounted <span class="token operator">||</span> updateUnmounted<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>children<span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token operator"><</span>div style<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">display</span><span class="token operator">:</span> isMounted <span class="token operator">?</span> <span class="token keyword">null</span> <span class="token operator">:</span> <span class="token string">'none'</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">></span><br /> <span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>children<span class="token punctuation">}</span><br /> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>You also need the wrapped component (the one we return in <code>render()</code>) to be pure — some edge case, but its render method is called on every <code>KeepMounted</code> update if you don't do that.</p>
<p>Unless you're too tricky, hidden components don't call <code>setState</code> — the user has no way to interact with them. This leaves us with preventing the updates from the state manager. Careful there, if we stop listening to store updates altogether, we might render stale UI once we show it again. I haven't gone too deep, but injecting <code>isMounted</code> through the context and using it right below the connector HOC should do the trick (for HOC-based connectors).</p>
<h2>Alternative designs</h2>
<p>I've also evaluated two alternative designs: using a HOC and <code>children</code>. I find the render-prop-based solution the cleanest and most convenient, but here they are for completeness.</p>
<h3>Higher-order component</h3>
<p>You can obviously do that. However, render prop allows you to hide an arbitrary segment of layout instead of one single component, and you also have prop name collisions to handle (granted, <code>isMounted</code> is probably not the most popular prop name). You'll use it like:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> LazyComponent <span class="token operator">=</span> <span class="token function">keepMounted</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">updateMounted</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">(</span>Component<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token operator"><</span>LazyComponent isMounted<span class="token operator">=</span><span class="token punctuation">{</span>cond<span class="token punctuation">}</span> data<span class="token operator">=</span><span class="token punctuation">{</span>data<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span></code></pre>
<h3><code>children</code> trick</h3>
<p>An interesting option since it leaves the <code>&&</code> conditional rendering pattern intact:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// usage</span><br /><span class="token operator"><</span>KeepChildrenMounted<span class="token operator">></span><br /> <span class="token punctuation">{</span> condition <span class="token operator">&&</span> <span class="token operator"><</span>Component <span class="token operator">/</span><span class="token operator">></span> <span class="token punctuation">}</span><br /><span class="token operator"><</span><span class="token operator">/</span>KeepChildrenMounted<span class="token operator">></span><br /><br /><span class="token keyword">class</span> <span class="token class-name">KeepChildrenMounted</span> <span class="token keyword">extends</span> <span class="token class-name">Component</span> <span class="token punctuation">{</span><br /> children <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> emptyChildren <span class="token operator">=</span> <span class="token function">isEmptyChildren</span><span class="token punctuation">(</span>children<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>emptyChildren<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>children <span class="token operator">=</span> children<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token operator"><</span>div style<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">display</span><span class="token operator">:</span> emptyChildren <span class="token operator">?</span> <span class="token string">"none"</span> <span class="token operator">:</span> <span class="token keyword">null</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">></span><br /> <span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>children<span class="token punctuation">}</span><br /> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>However, it requires children introspection (conveniently hidden inside <code>isEmptyChildren</code>). This is not that hard, but always feels hacky. The major problem is that it gives you an impression that you can write:</p>
<pre class="language-js"><code class="language-js"><span class="token operator"><</span>KeepChildrenMounted<span class="token operator">></span><br /> <span class="token punctuation">{</span>cond1 <span class="token operator">&&</span> <span class="token operator"><</span>Component1 <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">}</span><br /> <span class="token punctuation">{</span>cond2 <span class="token operator">&&</span> <span class="token operator"><</span>Component2 <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">}</span><br /><span class="token operator"><</span><span class="token operator">/</span>KeepChildrenMounted<span class="token operator">></span></code></pre>
<p>And expect it to keep everything mounted. It is doable, but children need a <code>key</code>, you have to introspect them even more, and track it manually. Very hacky. You could also check the child count and give a warning, but you won't always hit that condition when testing.</p>
<h2>Lazy loading and code splitting</h2>
<p>This pattern also enables two more interesting use cases. Since the component does not mount immediately, you can delay fetching the data needed to render it until the user sees it. Also, if the child component is heavy, you can slap a code-split boundary on it and only load the actual code when it's necessary. This way, the users who never see the component will not have to pay for using it. Very exciting.</p>
<hr />
<p>The idea appears so cool and useful I'm surprised there isn't an npm module for it yet (have I looked in the wrong place?). <a href="https://codesandbox.io/s/82jo98o708">Play with the code</a>, choose your preferred API option, point out what gotchas I haven't thought of. Feel free to wrap it into a library yourself if you're brave enough. Above all, have a nice weekend.</p>
Major Garbage Producers in JS2018-11-24T00:00:00Zhttps://thoughtspile.github.io/2018/11/24/garbage-producing-js/<p>The reckless coding culture of JS favors producing garbage. In real life, if you're environmentally conscious (hey there, my European readers), you probably do all sorts of crazy thinks to cut down on garbage — reject plastic bags in a supermarket, recycle bottles, keep the paper garbage in a closet until the special paper-garbage truck comes on Thursday. But when it comes to JS, the general sentiment magically becomes "let's litter like crazy, then let the engine designers do their thing and come up with something to make that work at the speed of C". Apparently, there's only that much the poor guys can do.</p>
<p>Even if you do a quick complexity analysis here and there, and know your way around a profiler, hot garbage is going to bite you. It won't be a literal memory leak — occasionally garbage collector would come and clean up your mess — but it places strain on the user's PC resources, and in the worst case you might end up with a 10+ seconds GC pause.</p>
<p>It's time that we learn to stand up for ourselves. We should at least identify eco-unfriendly JS patterns, so that we know whom to blame. In this post, I describe three patterns that lead to excess garbage production, and give you an insight into static-memory JS programming — the kind you want to use in low-level hot code.</p>
<h2>Array method chains</h2>
<p>Chaining array methods might be concise and functional, but it's a terrible memory buster. Count with me:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> res <span class="token operator">=</span> arr <span class="token comment">// Say we have 110 elements here</span><br /> <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">e</span> <span class="token operator">=></span> e<span class="token punctuation">.</span>user<span class="token punctuation">)</span> <span class="token comment">// say, + 100 elements</span><br /> <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">e</span> <span class="token operator">=></span> e<span class="token punctuation">.</span>user<span class="token punctuation">)</span> <span class="token comment">// + 100 elements</span><br /> <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">u</span> <span class="token operator">=></span> u<span class="token punctuation">.</span>wealth <span class="token operator">||</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment">// + 100 elements</span><br /> <span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">acc<span class="token punctuation">,</span> wealth</span><span class="token punctuation">)</span> <span class="token operator">=></span> acc <span class="token operator">+</span> wealth<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// all to get a number!</span></code></pre>
<p>We've just allocated 300 elements across 3 arrays, while we only needed one numeric variable (what is it, around 8 bytes?).</p>
<h3>Less calls, larger functions</h3>
<p>Just because you can write every operation as a one-liner arrow, does not mean you should. In the above case, we could rewrite the chain into a non-chain, removing intermediate arrays:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> res <span class="token operator">=</span> arr<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">acc<span class="token punctuation">,</span> e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> wealth <span class="token operator">=</span> <span class="token punctuation">(</span>e <span class="token operator">&&</span> e<span class="token punctuation">.</span>user<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>user<span class="token punctuation">.</span>wealth <span class="token operator">||</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> acc <span class="token operator">+</span> wealth<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>As you can see in <a href="https://jsperf.com/array-chains">this jsperf</a>, this solution is several times faster than the excessively chained one.</p>
<p>Of course, this limits the reusability of individual transforms — but, honestly, when was the last time you used non-inline function in a map (<code>.map(mapper)</code>)? Programming is a way of tradeoffs.</p>
<h3><code>for</code> loops</h3>
<p>For especially hot functions, it makes sense to switch to good old <code>for</code> loops with index:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> total <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> arr<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> e <span class="token operator">=</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">&&</span> e<span class="token punctuation">.</span>user<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> total <span class="token operator">+=</span> e<span class="token punctuation">.</span>user<span class="token punctuation">.</span>wealth <span class="token operator">||</span> <span class="token number">0</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Iterator-based for..of loops may be the stylish and concise choice here, but, depending on your browser, they may be as fast as the indexed version, or the slowest option, or be not supported at all. When you transpile the code into ES5, for..of loops turn into plain loops and naturally run at the same speed, but you should not assume this would always be the case. Also, the spec requires iterators to produce a new object at any iteration, which complicates the GC job instead of easing it.</p>
<h2>Defensive Cloning</h2>
<p>This pattern is not as widespread in Redux community as it used to be in Flux / event bus times, but it's a case where a single statement can stall your program. I'm talking about <code>_.cloneDeep</code> and friends.</p>
<p>The premise of cloning is noble: you have no idea what the consumers will do to your object, but mutating them might break the other consumers' assumptions. <strong>example</strong> This is most prominent in middlewares and observables, because these patterns assume low coupling, and you have no idea where the object you create goes and what happens to it. If you hand each consumer a unique copy of the object, it can do no harm to others.</p>
<h3>Clone only what's necessary</h3>
<p>The basic redux pattern with object spreads — using <code>{ ...state, user }</code> to overwrite a single property — is already a good enough solution. Even if you overwrite the most deeply nested property, you generally allocate only <code>O(log N)</code> new memory for an N-sized object. That is, if you have a 3-nested object with 6 properties at each level, you only create 3 new object per clone instead of 6^3 = 216. Much better!</p>
<h3>Use Immutable.js</h3>
<p>Libraries such as Immutable.js give the consumers no way to mutate the original object. They also enable you to change the object in patches, with smaller memory pressure than the naive method. The drawback is that the syntax for working with Immutable objects is more verbose, especially if you're interfacing with plain JS objects, so this solution works best for apps developed from scratch. As an alternative, you could employ ES6 <code>Object.freeze</code>, but the browser support is not quite there yet.</p>
<p>Unfortunately, this option also imposes runtime performance cost for property access, which might not be the best thing to do out of pure cautiousness.</p>
<h3>Make up your mind about mutation</h3>
<p>My favorite option here is not technological. Impose a global rule over your codebase: <em>do not mutate the objects you did not create.</em> Beat yourself on the fingers with a ruler when you do. Enforce this in code reviews. Explain the problem you're solving to your colleagues. If you absolutely must mutate the object, clone it as soon as you receive it. That's 1 explicit clone per 100 calls, not the default case. Much better.</p>
<h2>Object arguments</h2>
<p>This one is primarily for designing hot-utility libraries. The guys often opt for ease of use and design APIs with <code>options</code> argument that accepts an object:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// Ugly and inflexible (or is it?)</span><br /><span class="token function">formatGreeting</span><span class="token punctuation">(</span><span class="token string">'Waldemar'</span><span class="token punctuation">)</span> <span class="token comment">// hello, Waldemar!</span><br /><span class="token comment">// You don't have to remember arg position! And we can add more options later.</span><br /><span class="token function">formatGreeting</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Waldemar'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// hello, Waldemar!</span></code></pre>
<p>It's all very nice and convenient until you realize that you have to create an object on every call, then throw it away. Here's <a href="https://jsperf.com/object-vs-positional-args">another jsperf</a> that shows just how big a hit this can be.</p>
<p>Make a rule of accepting required arguments positionally in hot utility functions. You can always reserve the last position for an optional argument object a-la python to allow your users to opt-in to extended functionality:</p>
<pre class="language-js"><code class="language-js"><span class="token function">formatGreeting</span><span class="token punctuation">(</span><span class="token string">'Waldemar'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">lang</span><span class="token operator">:</span> <span class="token string">'fr'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// Bonjour, Waldemar!</span></code></pre>
<hr />
<p>I know, I know, your favourite quote starts with "premature optimization" and ends with "evil". However, if you don't take these things into consideration when writing low-level code, you'll soon find your validations take 400ms per keyword stroke, your visualizations hang the browser, and your node server do GC pauses every other second. I'm not promoting the use of these techniques in all your code, but as soon as you recognize a code path is hot — go for it! At least you'll know what to look for. Good luck!</p>
Why you Might Want to Extend React Components2018-11-05T00:00:00Zhttps://thoughtspile.github.io/2018/11/05/react-extend-justified/<p>Do not <code>extend</code> components. If there is anything React community agrees upon, this is it. <a href="https://reactjs.org/docs/composition-vs-inheritance.html">Use HOCs</a>. Use state managers (and their connector HOCs). Use render props. <a href="https://stackoverflow.com/a/47032288/2699012">Do not inherit</a>. Remember, <a href="https://en.wikipedia.org/wiki/Composition_over_inheritance">composition over inheritance</a>! Obey your guru. Once upon a time, a developer extended his component, and a lightning stroke him.</p>
<p>This is a mantra. I like mantras just as much as <a href="https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose">Steven here</a> (I don't). Things other people say should not discourage you from thinking for yourself, doing your research and analyzing design choices pragmatically, not superstitiously.</p>
<p>For a motivational example: when the docs say <code>At Facebook, we use React in thousands of components, and we haven't found any use cases where we would recommend creating component inheritance hierarchies</code>, that's a bit tongue-in-cheek. They have indeed found at least one such case — sitting right in React core, there is <code>PureComponent</code>. With this in mind, let us embark on journey to stop being afraid and extend our components joyfully.</p>
<h2>Introducing Component Inheritance</h2>
<p>A bit of history for those who haven't been around in the wild days of React (circa 2014). We had no redux, no HOCs, and no idea how to write React apps that do not fall apart. We came up with all sorts of wild patterns. Coding was adventurous and imaginative, just probably not in a good way.</p>
<p>Our working example starts with an idea of making a reusable piece of logic that would give our components <code>toggle</code> functionality — provide a boolean state flag and a way to switch it. The guys with OOP background see an immediately apparent way to do it. We make a semi-component:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">Togglable</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">{</span><br /> <span class="token comment">// Basic React state stuff</span><br /> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">open</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>toggle <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">toggle</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token function">toggle</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">open</span><span class="token operator">:</span> <span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>open <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token comment">// Why semi-? No render, won't work by itself.</span><br /><span class="token punctuation">}</span></code></pre>
<p>And now we extend it with <code>render</code> definition of our choice:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">TogglerExt</span> <span class="token keyword">extends</span> <span class="token class-name">Togglable</span> <span class="token punctuation">{</span><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token operator"><</span>div onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>toggle<span class="token punctuation">}</span><span class="token operator">></span><br /> <span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>open <span class="token operator">?</span> <span class="token string">'open'</span> <span class="token operator">:</span> <span class="token string">'closed'</span><span class="token punctuation">}</span><br /> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>For completeness, here's a canonical way to do this in 2018 using a HOC:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">withToggle</span> <span class="token operator">=</span> <span class="token parameter">Cmp</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">class</span> <span class="token class-name">WithToggle</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">{</span><br /> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">open</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>toggle <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">toggle</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token function">toggle</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">open</span><span class="token operator">:</span> <span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>open <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token operator"><</span>Cmp open<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>open<span class="token punctuation">}</span> toggle<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>toggle<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">const</span> TogglerHoc <span class="token operator">=</span> <span class="token function">withToggle</span><span class="token punctuation">(</span><span class="token parameter">p</span> <span class="token operator">=></span> <span class="token punctuation">(</span><br /> <span class="token operator"><</span>div onClick<span class="token operator">=</span><span class="token punctuation">{</span> p<span class="token punctuation">.</span>toggle <span class="token punctuation">}</span><span class="token operator">></span><br /> <span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>open <span class="token operator">?</span> <span class="token string">'open'</span> <span class="token operator">:</span> <span class="token string">'closed'</span><span class="token punctuation">}</span><br /> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span><br /><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>We could also write this in three lines of <a href="https://github.com/acdlite/recompose">recompose</a> (<code>withState</code> + <code>withHandlers</code>), but that's beside the point. Also note how the code for both options is almost identical — we'll use this later to do something cool.</p>
<h2>The Business: Reuse Legacy Components</h2>
<p>You know these — components that are 300+ lines long and have a fair share of business logic in them. For some reason, they usually have JSX-returning methods, something like <code>renderAvatar</code>. The most infuriating thing about these? They may be old, obese and gross, but they've been around this project longer than you have, and are mostly working the way they're supposed to. Something like this:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">UserCard</span> <span class="token keyword">extends</span> <span class="token class-name">Component</span> <span class="token punctuation">{</span><br /> <span class="token function">renderMenu</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// some JSX here</span><br /> <span class="token punctuation">}</span><br /> <span class="token function">isOnline</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// and a lot of logic there</span><br /> <span class="token punctuation">}</span><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token operator"><</span>Card<span class="token operator">></span><br /> <span class="token operator"><</span>Avatar url<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>avatar<span class="token punctuation">}</span> isOnline<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">isOnline</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><br /> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">renderMenu</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><br /> <span class="token operator"><</span><span class="token operator">/</span>Card<span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>So, how would you approach making a similar component with the same business logic and similar layout parts? To be concrete, let's say we want to put our users into a table instead of a card list. Would you run and start decomposing, making <code>render*</code> methods proper components, extracting the business logic into a state manager? Do you have all the time in the world to do this task? Let's hack around and do some real work.</p>
<p><code>extend</code> to the rescue!</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">UserRow</span> <span class="token keyword">extends</span> <span class="token class-name">UserCard</span> <span class="token punctuation">{</span><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token operator"><</span>tr<span class="token operator">></span><br /> <span class="token operator"><</span>td<span class="token operator">></span><br /> <span class="token operator"><</span>Avatar url<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>avatar<span class="token punctuation">}</span> isOnline<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">isOnline</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><br /> <span class="token operator"><</span><span class="token operator">/</span>td<span class="token operator">></span><br /> <span class="token operator"><</span>td<span class="token operator">></span><span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">renderMenu</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token operator"><</span><span class="token operator">/</span>td<span class="token operator">></span><br /> <span class="token operator"><</span><span class="token operator">/</span>tr<span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Do not judge me! We've reused the old component without disrupting its function and with little to no duplication (just some in the <code>render</code> method). Let's try once again, with method overrides and super calls:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">SellerCard</span> <span class="token keyword">extends</span> <span class="token class-name">UserCard</span> <span class="token punctuation">{</span><br /> <span class="token function">renderMenu</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>isBlocked<br /> <span class="token operator">?</span> <span class="token operator"><</span>Menu warn<span class="token operator">=</span><span class="token string">"Blocked seller"</span> <span class="token operator">/</span><span class="token operator">></span><br /> <span class="token operator">:</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">renderMenu</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>There's a hundred reasons not to write components like this, and I wouldn't call it a clean solution I am proud of, but this is very effective in getting out of the <em>so worried about the state of your codebase you can't eat</em> block.</p>
<h2>The Technical: Performance Considerations</h2>
<p>Never mind the design, let's move into the land of extreme performance hacks. Common sense tells that with HOCs React has twice as much bookkeeping to do: set up both the parent and the child components, call their lifecycle hooks in the proper order, and so on. So, subclass components should be faster than HOCs.</p>
<p>Since common sense is not worth much when it comes to performance testing, I made an actual benchmark using the toggler example introduced earlier. It is a realistic use case, not some synthetic benchmark. Still, it shows updating the HOC to be slightly (3–10%) slower in webkits (Chrome and Safari), and 50% slower in Firefox. If you don't trust me, head over to a <a href="https://github.com/thoughtspile/hoc-vs-extend">special repo</a> and see for yourself.</p>
<p>This is probably not a deal-breaker for most real apps, but, again, something to keep in mind — layering HOCs has a real performance cost. This is also the reason why <code>PureComponent</code> works best as-is — having performance helpers be performant makes sense.</p>
<h2>Best of Both Worlds (and Generality)</h2>
<p>An especially powerful thing about the extend pattern is its generality. The parent component exposes all its methods and internal state for you to play with, so you can do whatever you please, even make a HOC out of it! This openness is not always good — with great power comes great responsibility, <em>etcetera, etcetera.</em> For what it's worth, here is the extend-toggler, wrapped in a HOC:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">withToggleExt</span> <span class="token operator">=</span> <span class="token parameter">Cmp</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">class</span> <span class="token class-name">WithToggle</span> <span class="token keyword">extends</span> <span class="token class-name">Togglable</span> <span class="token punctuation">{</span><br /> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token operator"><</span>Cmp open<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>open<span class="token punctuation">}</span> toggle<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>toggle<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>With a bit of trickery you could even make a generic <em>hocifier</em> that takes two components and plugs one into the other's render method.</p>
<h2>The Downsides</h2>
<p>Frankly, I do not extend components all the time either, and here is why:</p>
<ul>
<li>No clear-cut interface boundary. Well-written HOCs have decorator options and the props they inject into the decorated component. With extend, all the component code is the public interface. You can somewhat cover it with TypeScript, but it's duct tape over duct tape.</li>
<li>There is no way to inherit from several components. Using extend, you can't make your component both Pure and Togglable. HOCs, on the other hand, are easily composable, albeit with a runtime performance cost. Granted, JS is so dynamic you could write your own multiple inheritance / mixin engine, but this is also beyond the scope of this article.</li>
<li>You can't use functional components any more. Not for the base components, not for the derived ones. It's a pity, I love the brevity and ease of creation.</li>
<li>Lifecycle hooks in child components are verbose. If you define <code>componentDidMount</code> in the child component, it's now your responsibility to call the parent hook. Even worse, the parent hook may or may not exist, so you have to check before you call.</li>
</ul>
<p>Dual extend / HOC pattern works around these issues nicely, but nullifies the performance benefits.</p>
<hr />
<p>So, should switch to extending your components exclusively? As you might have guessed by the downside list, absolutely not! Just do not rule out this option, especially if you're developing a library. Since you can expose both options to the library users, there's no reason to reject. To repeat, the two cases where I find <code>extend</code> viable are:</p>
<ul>
<li>Making a slightly changed version of a legacy component without rewriting it completely.</li>
<li>Squeezing the last bit of performance out of your helper components, at the expense of convenience.</li>
</ul>
<p>If you like what you just read, encourage me in the comments and subscribe to my blog — I have a more exhaustive overview of React composition patterns in writing, with HOCs, render props, and maybe even <code>useState</code>. If you hate what you just read (interesting! why are you still reading it then?) — discourage me in the comments or write an angry email. Anyways, you're a bit smarter now. Have a nice week and enjoy yourself.</p>
OOP for FP lovers: Simplistic Dependency Injection2018-10-28T00:00:00Zhttps://thoughtspile.github.io/2018/10/28/simplistic-di/<p>With all the enthusiasm around functional design in javascript community, we've come to reject the concepts whose names remind us of object-orientation. We throw constructors, methods and classes out of the window because they seem to smell of bank cubicles, water coolers and ERP. I've been on that train, but now I'm free from the prejudice. Great, useful ideas are hidden inside the fancy OOP terming, and I'm here to expose their niceness. We shall begin with Dependency Injection.</p>
<h2>We Have a Problem</h2>
<p>I hear your teeth cringe as dusty IoC containers and AngularJS fly around your head. Take a deep breath and bear with me. Let's write a module, say, a User service, that <em>depends</em> on other modules — a requester and the config. Normally, we code it along the lines of (for some imaginary redux-like state manager):</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> getUserList <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./getUserList'</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> config <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">pageSize</span><span class="token operator">:</span> <span class="token number">10</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">host</span><span class="token operator">:</span> <span class="token string">'192.168.0.0'</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">initState</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">users</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">loadNextUsers</span><span class="token punctuation">(</span><span class="token parameter">state</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">getUserList</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">limit</span><span class="token operator">:</span> config<span class="token punctuation">.</span>pageSize<span class="token punctuation">,</span><br /> <span class="token literal-property property">offset</span><span class="token operator">:</span> state<span class="token punctuation">.</span>users<span class="token punctuation">.</span>length<span class="token punctuation">,</span><br /> <span class="token literal-property property">host</span><span class="token operator">:</span> config<span class="token punctuation">.</span>host<span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">nextUsers</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> state<span class="token punctuation">.</span>users<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token operator">...</span>nextUsers<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>The dependency structure of this app is set in stone, allowing no fiddling. It causes two classic problems. The module is not too reusable: we can't customize the page size or host when loading the users. Testablity could be better, too: while we could have a <code>getUserList.mock.js</code> lying around for testing, replacing <code>config</code> in module's internal scope is probably excessively tricky, even if possible.</p>
<h2>STEP! Enter DI.</h2>
<p>If we have <em>dependencies</em>, we can <em>inject</em> them! All it takes in our functional example is passing them to <code>loadNextUsers</code> as a parameter:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">initState</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">users</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">loadNextUsers</span><span class="token punctuation">(</span><span class="token parameter">state<span class="token punctuation">,</span> getUserList<span class="token punctuation">,</span> config</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">getUserList</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">limit</span><span class="token operator">:</span> config<span class="token punctuation">.</span>pageSize<span class="token punctuation">,</span><br /> <span class="token literal-property property">offset</span><span class="token operator">:</span> state<span class="token punctuation">.</span>users<span class="token punctuation">.</span>length<span class="token punctuation">,</span><br /> <span class="token literal-property property">host</span><span class="token operator">:</span> config<span class="token punctuation">.</span>host<span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">nextUsers</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> state<span class="token punctuation">.</span>users<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token operator">...</span>nextUsers<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>That's not a huge change: instead of explicitly specifying and importing the dependencies inside a module, we do it one level above. By the way, <code>state</code> was injectable from the beginnning, allowing us to play with it or create multiple instances.</p>
<p>At first, it might seem like added verbosity and bookkeeping. But hey, look at the upsides! Now we can override the config as much as we like when loading the users: vary page size and the host (frankly, the host setting is a bit silly, but I couldn't think of anything better — just pretend it's super useful).</p>
<p>Testing becomes a joy. Instead of using a magic loader that takes not-the-module-we-specify-but-the-one-with-<code>.mock</code>-added-if-there-is-one, we can just call our function directly with a mocked dependency, using <a href="https://www.node-tap.org/">node-tap</a> or even a bunch of <code>assert</code>s.</p>
<p>And the best thing is — we can use the explicit dependencies from the first example as default parameters, combining the best of both approaches.</p>
<p>This spirit of DI is what separates</p>
<ul>
<li><code>sum([1,2,3,4])</code> from <code>reduce([1,2,3,4], (x, y) => x + y, 0)</code> — we <em>inject</em> the reducer and the initial value.</li>
<li><code>mongoose</code> from <code>new Mongoose()</code> — this is brilliant API design, giving you ease of use for the 90% use case while providing an escape hatch to <em>inject</em> <a href="https://stackoverflow.com/questions/19474712/mongoose-and-multiple-database-in-single-node-js-project#19475270">multiple databases</a> if you need to.</li>
<li><code>alias: { 'react': 'preact-compat' }</code> in webpack from <code>@tappable({ h })</code> — yes, JSX-based helper libraries would have made us a favor by allowing to <em>inject</em> the JSX provider instead of relying on bundler trickery.</li>
</ul>
<h2>Are We Done Yet?</h2>
<p>Our homegrown DI is not perfect — it has problems of its own compared to real IoC containers, such as <a href="http://inversify.io/">InversifyJS</a>. The more stuff we inject, the more positional arguments we have to pass around, which becomes painful. Moreover, somewhere in our code we are still bound to the physical location of source modules, importing and repeating ourselver over and over. So yes, real, unhip, enterprise flavor of DI solves real problems.</p>
<hr />
<p>Please take some time to think it all over. DI is good. OOP is even better. They have non-hacky solutions to real challenges. Had it for over 20 years now, just sitting around. FP is good, too, for the same reason — not because of <em>elegance</em>, whatever that means. Meanwhile, I'll try to write more on the merits of loosely-understood OOP, so make sure to come back!</p>
Five Tricks for Debug-Logging in JavaScript2018-10-05T00:00:00Zhttps://thoughtspile.github.io/2018/10/05/js-debug-logging/<p>Cheer up, today is a quick tip day. No <a href="https://thoughtspile.github.io/2018/09/23/bad-software-week/">rants</a>, no <a href="https://thoughtspile.github.io/2018/09/23/bad-software-week/">motivation</a>, no existentialism — just a few simple tricks you can use right now.</p>
<p>We'll be talking about <code>console.log</code> and friends for debugging javascript, mostly in the browser. If you don't use devtools debugger — try it, but I'm not here to judge you (unless you use <code>alert</code>). There is at least one case where <code>console</code> is better: you have a method that gets called frequently, and want to inspect the internal state over multiple runs, then drill down on the interesting ones. Pausing on every hit would be extremely tedious.</p>
<h2>Logging in concise arrows</h2>
<p>If we want to log something inside a function written in concise arrow syntax (the one without curly braces), you might find yourself adding and removing the body and changing formatting — very boring and error-prone:</p>
<pre class="language-js"><code class="language-js">promise<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>age<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// what else was there in res?</span><br />promise<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// meh the braces... enter...</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// this is the thing... enter...</span><br /> <span class="token keyword">return</span> res<span class="token punctuation">;</span> <span class="token comment">// oh and the "return"</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>No need to suffer! Since <code>console.log</code> returns <code>undefined</code>, which is falsey, we can just write:</p>
<pre class="language-js"><code class="language-js">promise<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span> <span class="token operator">||</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>age<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h2>Beware of post-log object mutation</h2>
<p>My most painful session of console-debugging was related to this one. Oh, times. I must admit, this problem does not exist for debugger pauses. Yet, for what it's worth, let's say we have a <code>city</code> object, and something's wrong. Let's log it:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> city <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Vladivostok'</span><span class="token punctuation">,</span> <span class="token literal-property property">poulation</span><span class="token operator">:</span> <span class="token number">606589</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>city<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// { name: null, population: 606589 }</span></code></pre>
<p>What the hell? Where is the name? You try. You try again. You start suspecting V8 just shipped with multi-threading. You try again. Guess what? In a far away area of your code there's a</p>
<pre class="language-js"><code class="language-js">city<span class="token punctuation">.</span>name <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span></code></pre>
<p>The solution is simple: if you see the data look weird (or just want to double-check), dump a clone / cloneDeep (to triple-check) / stringify:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> city <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Vladivostok'</span><span class="token punctuation">,</span> <span class="token literal-property property">poulation</span><span class="token operator">:</span> <span class="token number">606589</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>city<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// "{ "name": "Vladivostok, "population": 606589 }"</span></code></pre>
<h2>Quickly filter logs by condition</h2>
<p>Another trick with boolean short-circuiting is conditional logging. Suppose we have an array of users, and there's a bug displaying users from Australia. But if you log all the users, there is a lot of eye-scanning to find the necessary ones. Booelan short-circuiting to the rescue again, with <code>condition && console.log(data)</code></p>
<p><img src="https://thoughtspile.github.io/images/conditional-logging.png?invert" alt="" /></p>
<p>Bad code style? Sure. But you'll remove it in a minute anyways.</p>
<h2>Make your logs stand out</h2>
<p>With a lot of logs running around your console, finding the ones you just added is no easy task.</p>
<p><img src="https://thoughtspile.github.io/images/painted-log.png?invert" alt="" /></p>
<p>If you use <code>console.error</code> or <code>warn</code>, your line will be brighter-colored and easier to find. At least until you write 20 <code>console.warn</code>s.</p>
<h2>Dump stack traces with <code>console.error</code></h2>
<p>The final tip will help you find <em>who</em> called the function, instead of <em>how</em>. <code>console.error</code> conveniently captures the stack trace and shows it in a nice collapsible way. You can quickly look around and find all the call sites — very neat.</p>
<hr />
<p>That's all for today! Hit the comments if you use know another neat logging trick, or want to blame everyone for not using real loggers, or for not using the debugger. Would love to make a teaser for the next post, but have not chosen yet. <em>Hasta la vista!</em></p>
Let's Make Software Better2018-09-29T00:00:00Zhttps://thoughtspile.github.io/2018/09/29/lets-make-software-better/<p>My <a href="https://thoughtspile.github.io/2018/09/23/bad-software-week/">previous post</a>, a classic rant, how-bad-software-is-these-days kind, attracted unexpected and probably even unreasonable attention. This time I'm in for something different — I'm going to preach. Behold, and open your eyes, and open your hearts, and open your minds, as I am about to tell how we should live our lives to bring about a lifetime of good software. This is an uphill battle, people will not come in droves to cheer you up on what a great job you did, but we must stay strong and fight. We shall begin now.</p>
<p><img src="https://thoughtspile.github.io/images/vienna.jpg" alt="" /></p>
<h2>Work well</h2>
<p>Whatever you do, do it well. Write code magnificently. Review relentlessly. Test tirelessly. This way is a hard way, but it is the way of the warrior.</p>
<p>Do not let the spaghetti of a code on your hands get into production just because you're sick of this particular feature and want to have fun. Or maybe have fun switching from technology X to technology Y, then come back and make your regular boring code good, too.</p>
<p>Fix bugs. This is even less fun, but it's your project, and it's your responsibility to make it work. Resort to hacks if you must — a working system is worth a thousand lines of clean code. Take pride in making things that work and make people happy.</p>
<h2>Understand the business</h2>
<p>The concept of evil businesses preventing the good-hearted developers from doing their best comes up in every argument on bad software sooner or later. They hire too few people, and the ones they hire are no good, and hired too late anyways, and the management slaps a tight deadline on top, so it's their fault. Is it, really?</p>
<p>The only thing that gives meaning to your work is how you make the lives of your users easier. That is a business concept, not a development one. Your code is nothing without the business it supports, but the business is still worth something without the code. So, business > software > code. Accept it.</p>
<p>Planning around people writing code is hard as it is. Without deadlines it is plain out impossible. Negotiate and adapt. Isn't it better to do something imperfect that already works and brings value than to spend eternity chasing the perfection?</p>
<p>And then there's this thing: if your company has no product, has no business strategy, has no sales, has none of all these things you call dirty and hypocritical — it also has no money, so how are they going to pay you? I bet you prefer being paid.</p>
<h2>Promote best practices</h2>
<p>You may have heard some practices make software better: code reviews, documentation, testing and refactoring. These things work. Build them into your workflow — adding even one, even a half, will make you stronger.</p>
<p>If your management does not approve of you spending time on this stupid geek stuff — convince them. If they will not get convinced — do a bit guerilla-style, see what the team thinks, and if it sticks.</p>
<h2>Be open to other professions</h2>
<p>The fact that you are a developer does not mean that you can, or should, solve all problems with code exclusively. Sometimes it's better to work your way around it, even if it means no code and no fun.</p>
<p>Have a particularly hard problem you want to slap machine learning / AI on? Leave it for the user to do manually, or do it yourself, or hire some poor students.</p>
<p>Have an extremely tricky front-end logic? Talk to your designer, and you might find a way to solve the problem with no code whatsoever. You just achieved the same result with no code — wonderful!</p>
<p>Designers, sales and project managers are working towards the same goal as you are. And yes, your users are crucial to a useful product. There is no reason not to respect these people as less technical, or less intelligent, or less anything than you — you're a just tool for the job, as are your colleagues. So respect each other and be open to their input informed by their experience.</p>
<h2>Be a part of open source</h2>
<p>Your codebase certainly has something with no specific business value that you have spent some time developing. It might be a migration engine for some exotic database, , a front-end snippet or an algorithm. Talk to your boss and sse if you can publish it. Making it public does no harm (it wasn't a secret know-how in the first place, just an implementation of something more or less trivial) and provides free marketing for your company. More globally: the more open source tools we have, the easier it is to make good software, fast.</p>
<p>On the other side, prefer open-source tools to the ones you wrote yourself. You won't be the only user, so some problems that did not even appear to you are already found and solved. If the existing libraries do not satisfy you as-is, pick the closest one, adapt it to your needs, than propose a PR or publish as an extension.</p>
<h2>Give feedback on software you use</h2>
<p>Sometimes you're using a website or an application and run into a bug. Report it to the team that made it. You are a tech person, and you will find your way around it, but others might not. If it's not a real bug, just some tricky UX workflow, let them know anyway.</p>
<p>Don't blame them too hard. Remember there's a reason you use their particular service, so they've already made a reasonably fine job. Small teams will appreciate your input more — they have less users, so your help is more helpful. And don't be too disappointed if they don't fix it right away: they have more context to set priorities than you do.</p>
<h2>Educate</h2>
<p>I bet you have learnt <em>something</em> useful in your life. Know how to make good user interfaces, or how to design scalable back-end infrastructure, or how to manage software projects? Well done, congratulations! Now go out there and share this precious light of knowledge with others.</p>
<p>Write a blog. Answer questions on stackoverflow and quora. Talk on conferences. <em>Get into education business a little bit.</em> Check with your local schools and universities, ask them if they have an instructor position for such a professional. Check bootcamps and programming courses. These jobs might not pay as well as your normal one, but they are satisfying as hell. And they usually come part-time, so you're likely not to harm your primary occupation.</p>
<hr />
<p>Go and use these commandments for the better of the world. We all think how wrong and unjust the world order is from time to time, and I do, too, probably even more so than anyone, but we have to cut down on this crap as there is no realistic way for us to change the way things are right away. So go, and be responsible for your actions and decisions, and, no matter what you do, think of how you can do it better. Become a brick in the beautiful world of good software; fight the fight day after day; little by little we will see the world of software change. And please, please let me know if I have encouraged you even a tiny little bit — that would make me feel so much happier.</p>
Another Week with Bad Software2018-09-23T00:00:00Zhttps://thoughtspile.github.io/2018/09/23/bad-software-week/<p>In the midst of my September job hop I headed to Kazan for the weekend. I don't know exactly why — probably because I could. I had a hotel booked via booking.com, but once I arrived there the receptionist told me it was the first time he's heard of my booking, and he told me that they had no more rooms, and he told me that I'd rather find another place to stay.</p>
<p>But why, I always see these things for what they are. Some sneaky caffeinated programmer kids at work with the booking system integrations, you should always double-check after us! After what came next, I started suspecting there's some particularly defective developer marketing himself as a <em>"Chief Hotel Booking Management System (HBMS) Professional with 10+ years experience in the field"</em>.</p>
<p><img src="https://thoughtspile.github.io/images/kazan-bridge.jpg" alt="" /></p>
<p>I booked the next hotel for the two nights, with a discount, and walked there. Guess what? The room I booked was occupied, but they had another one, but not for two night, just one. In the end it worked out all right because there was that woman using the responsive, fully interactive, enterprise-ready ERP system "sheet of paper and a pencil" that allows you to reschedule bookings at will, with no programmers involved.</p>
<p>The rest of my trip went well, probably because I had little interaction with software stuff. The pizza place charged me twice, because they thought the first payment didn't pass, but they gave the money back. Human problem, wasn't it? (Of course not completely, some usability failure out there, but let's leave it for what it is, shall we?)</p>
<h2>The Return</h2>
<p>I was riding the train from the airport, almost eager to get back to work writing things that mostly function. I was a bit worried they didn't give me a call — was I supposed to just come to the office and figure out what to do next? Anyways, I made some calls and found out that the information about me starting my work got lost somewhere along the course of a month it's been running around the infrastructure.</p>
<p>This got me a little worried: I haven't spent a week in Moscow without work in over three years, and, besides, I was running low on money. I wasn't angry with anyone in particular: who should I blame for the state of the industry? I'm just as guilty as anyone for letting it fall that low. it turned out not to be that bad after all: the weather was good, and I got a chance to walk the empty city — everyone here in Moscow is too busy working hard to be out of office in the middle of the day.</p>
<p>My accidental vacation started along the fanfare of Nikita Prokopov's <a href="http://tonsky.me/blog/disenchantment/">software disenchantment</a>. It's depressing but, and even more depressive for being true. Let me pick a quote for you:</p>
<blockquote>
<p>We cover shit with blankets just not to deal with it.</p>
</blockquote>
<p>I've been subscribed to this blog for several years, and never knew the man was Russian. I should have guessed — where else can a man that depressed and dissatisfied with the state-of-the-world come from? The part that caught my attention was the Russian apps on the screenshots. From there I got onto Nikita's <a href="https://tonsky.livejournal.com/">russian-language blog</a>.</p>
<h2>Why must it be like this?</h2>
<p>It's good the blog is in Russian — serves well to save the good people of the world from Moscow-grade sadness. Somewhere in the comments on the fourth page I found a neat explanation for why the things in software are the way they are. I can't find the particular comment any more, but I remember the gist and even spent some time elaborating on it.</p>
<p>Once a problem is solved by software at a minimum viable level, you don't need programmers (we are crazy expensive) to actually make it work. The solution gets canned and distributed in a bundle that usually works, as long as you're not getting too tricky:</p>
<ul>
<li>Edit a table of numbers? Excel!</li>
<li>Write a text? Word!</li>
<li>Send some data to another person? E-mail!</li>
<li>Make a website? Wordpress!</li>
<li>Edit an image? Photoshop!</li>
</ul>
<p>The programmers are there to stick them into the holes of the abstraction. If you're generating excel tables programatically from a database, or want your website to synchronize with a price list from a Googledoc, you need some programmers to duct-tape the systems together in unexpected ways (I'm working hard to stay away from shit-based metaphors, but feel free to make one yourself). These systems have a long train of backwards compatibility, they are a massive overkill for the job, but hey, they work just well enough.</p>
<p>I'm enraged by the lack of attention to static website generators. The idea is beautiful: pipe some markdowns through a template, get a set of static HTMLs and resources that can be host anywhere for, like, free! Why do people make their websites in Wordpress — have a MySQL database at work pushing the limits of storing static data, and PHP, rendering the pages at every request. Oh dear, the system was supposed to write blogs — how did they ever manage to bend it into e-commerce, landing pages and ERP front-ends?</p>
<p>No, I do know the answer to that — just hit a freelance farm to find a bunch of schoolchildren who can make you something that looks like a website in a week, for $50. I wouldn't consider disrupting a business with that low profit margin — and who would?</p>
<h2>And also</h2>
<p>On Thursday I was watching my TV when the electricity died. It spent the evening randomly switching it on and off, then got stuck loading forever. The philips tech support was very sympathetic. "There's some very high tech in there — a whole embedded system! How in the world did you expect it to survive the electricity shutdown?" they said. And yeah, sure, what did I expect from a system that had a piece of software in it?</p>
<figure>
<iframe style="margin: 0 auto; display: block; max-width: 100%" width="560" height="315" src="https://www.youtube.com/embed/-eREiQhBDIk?rel=0&start=230" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>
<div style="text-align: center; color: #666">Here's what I (and Nikita, and even Uncle Bob lately) think of software</div>
</figure>
<p>Really, I should assume I write code at least marginally better-than-average and get into programming education. There's a horrible shortage of material on learning actual programming (not "How to Use Technology X to Solve Problem Y"-style things) out there. I must to fight as well as I possibly can. You must, too (yes, you, the guy who actually read this all).</p>
<hr />
<p>You've just seen a weekly snapshot of my thoughts pile. While taking another look at this website's design to see if the the navigation is obscure enough to prevent the users from noticing the other posts (it probably is; I don't think i'm changing it just yet) I noticed something horrible.</p>
<p>The blog's address has <em>thoughts pile</em> in it. How is that name appropriate for the smart, boring and technical stuff I've come to post? I need to give the place some wildness it deserves. <em>Klyukovka, ay, igray Balalaechka!</em> Writing messy speculative posts is far more entertaining!</p>
Not Sucking at TypeScript: 3 Tips2018-09-22T00:00:00Zhttps://thoughtspile.github.io/2018/09/22/typescript-unsuck-guide/<p>I have spent three years developing in TypeScript, but sometimes it is owerwhelming. I'm sitting there with all those little "fuck-fuck-fucks" in my head, thinking of how I'd great it would be to burn the annotations, change the extensions to <code>.js</code> and get out of this nighmare already. But hey, if used properly, TS makes you happy, not depressed! Here are my top 3 tips to ease the pain.</p>
<h2>Let TS do its type inference</h2>
<p>Here's a sample typescript fragment from one of my projects:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">maxNumber</span><span class="token punctuation">(</span>arr<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> max<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">Infinity</span><span class="token punctuation">;</span><br /> arr<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span>x<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">></span> max<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> max <span class="token operator">=</span> x<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> max<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>But TS compiler is really good at deducing types, so you don't to be that explicit! TS deduces types from:</p>
<ul>
<li>initial values: <code>const x = 10</code> is enough, no need for <code>const x: number = 10;</code>. This also works on default values, as in <code>(x = false) => !x</code>;</li>
<li>array method types (any generic specifications, really): if <code>arr</code> is <code>number[]</code>, than x in <code>arr.forEach(x => ...)</code> is obviously a <code>number</code>;</li>
<li>return values: in our example, TS knows pretty well that <code>max</code> is a <code>number</code>, so the function returns a <code>number</code>.</li>
</ul>
<p>So, our examle only needs one type annotation:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">maxNumber</span><span class="token punctuation">(</span>arr<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> max <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">Infinity</span><span class="token punctuation">;</span><br /> arr<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">></span> max<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> max <span class="token operator">=</span> x<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> max<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>As a bonus, once we the annotations do not duplicate, we can easily change types without having to fix half a file. In our example, we changing <code>arr</code> to <code>string[]</code> immediately shows that we must also change the initial value of <code>max</code> to a string, <code>''</code>.</p>
<p>As a rule of thumb, we only need explicit type annotations for:</p>
<ul>
<li>Function parameters: <code>(x: number, y: number) => x + y;</code>. As we've seen, default values will also do: <code>(x = 0, y = 0) => x + y;</code>.</li>
<li>Empty containers: <code>private users: IUser[] = [];</code>. TS does not see an item, and can't know its type.</li>
<li>Values coming from outside the codebase. This one's trickier, but think af an API call: <code>get<IUser[]>('/users')</code>.</li>
<li>(Yes, there are other cases, you'll know it when you see one, don't get mad at me).</li>
</ul>
<p><img src="https://thoughtspile.github.io/images/ts-just-enough.png?invert" alt="" /></p>
<p>Generally, annotate as few types as you can, then check the IDE hints to see if TS got it right. If not, help him.</p>
<h2>Sometimes, just let types go</h2>
<p>I'm absolutely guilty of this one: I've spent a day once typing a tricky low-level canvas util. Always remember that TS is supposed to help you, not stand in your way.</p>
<p>If you find yourself describing an especialy tricky type — a generic generic, or a polymorphic variadic function — stop and think if you really need it. Maybe the logic is just too obscure, and the fancy typings are just a symptom. Maybe you only use that function in one place, and it already works, so what's the use?</p>
<p>With TS, you always have an easy way out — there's no shame in dropping an <code>any</code> if it saves you a day! An explicit <code>any</code> is better than implicit beacuse if you're feeling static on a Friday afternoon, you can grep your codebase for <code>/: any/</code> and see if you can fix a couple.</p>
<h2>Prevent compilation error buildup with global overrides</h2>
<p><img src="https://thoughtspile.github.io/images/ts-errors.png?invert" alt="" /></p>
<p>Accidentally you slip and ignore a TS error. The code still compiles, no harm done! But once your compilation log is several 10+ of bloody redness, it's lost as a source of information about global project correctness. The generic advice is "look if it's a real error, then either fix or re-type it", but yes, I do have something specific in mind. Global type overrides are your friends!</p>
<p>Sure your browser targets support <code><Array>.find</code>, or have a polyfill ready? Override the global <code>Array</code> type (courtesy of <a href="https://stackoverflow.com/questions/31455805/find-object-in-array-using-typescript">user75525 at SO</a>)!</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">interface</span> <span class="token class-name"><span class="token builtin">Array</span><span class="token operator"><</span><span class="token constant">T</span><span class="token operator">></span></span> <span class="token punctuation">{</span><br /> <span class="token function">find</span><span class="token punctuation">(</span><span class="token function-variable function">predicate</span><span class="token operator">:</span> <span class="token punctuation">(</span>search<span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">boolean</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Working on a legacy project with lodash loaded globally via a CDN? Throw it in:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> lodash <span class="token keyword">from</span> <span class="token string">'lodash'</span><span class="token punctuation">;</span><br /><span class="token keyword">declare</span> global <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> _<span class="token operator">:</span> <span class="token keyword">typeof</span> lodash<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Using an obscure jQuery plugin? Global type overrides got you covered:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">JQuery</span> <span class="token punctuation">{</span><br /> <span class="token function-variable function">webuiPopover</span><span class="token operator">:</span> <span class="token punctuation">(</span>o<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">)</span> <span class="token operator">=></span> JQuery<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h2>Love the types you're with</h2>
<p>Hope this tips will help you be less depressed and more productive when working with TypeScript.</p>
Simpifying AngularJS controllers with ES5 get / set2018-09-20T00:00:00Zhttps://thoughtspile.github.io/2018/09/20/angularjs-service-property-getter/<p>I've been developing an AngularJS application for the past year — and <em>voila!</em> here I am, alive and well. I'm not some crazy old fuck who thinks AngularJS is a promising new technology. Nor have I been waiting to publish this post for 3 years. It's just how things turned up for me. Since there's no shortage of AngularJS apps in the wild, I've decided to share some tips for taming Angular (the Terrible one) and staying sane (yes you can).</p>
<h2>The context — where am I? (Help)</h2>
<p>Some context first. I spent two years developing React front-ends. When offered a job on an AngularJS app, I was scared at first — we've all spent years making fun of it. The team lead was shaking a full Vue rewrite around not to scare the candidates off. The idea of playing around with Vue felt good (I'm a playful coder, don't judge me), but Joel Spolsky's <a href="https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/">spooky story</a> of Netscape's full rewrite had been growing on me for years. And well, there were <em>features</em> to be made, no time for the geek fun.</p>
<p>Now I'm gone from the project (no relation to the tech stack whatsoever), and the app is still moslty AngularJS. It's in a good shape, and has all the modern things: webpack, babel, a sprinkle of React here and there. I feel I've made a good job by focusing on the business stuff.</p>
<h2>The Problem — what's wrong?</h2>
<p>So, what was it I was gonna tell you kids about? We have a service that holds the list of users. Here it is, with all the ES6 exquisiteness:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">UserService</span> <span class="token punctuation">{</span><br /> <span class="token function">load</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/users'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now, the component. All basic, too, just shows a list of users:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">UserListController</span> <span class="token punctuation">{</span><br /> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">userService</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>userService <span class="token operator">=</span> userService<span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>userService<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">users</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>users <span class="token operator">=</span> users<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br />angular<span class="token punctuation">.</span><span class="token function">component</span><span class="token punctuation">(</span><span class="token string">'userList'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">template</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><user-card ng-repeat="user in $ctrl.users" user="user"></user-card></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span><br /> <span class="token literal-property property">controller</span><span class="token operator">:</span> UserListController<br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>But we can also add a user. Once the thing is done, we should update the list — it's surely changed. But — oh no! — we have no way of doing it, because the data is stuck in <code>UserListController</code>.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">UserService</span> <span class="token punctuation">{</span><br /> <span class="token function">load</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/users'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token function">addUser</span><span class="token punctuation">(</span><span class="token parameter">user</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'./users'</span><span class="token punctuation">,</span> user<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token comment">/* oops */</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<h2>The classic solution</h2>
<p>The classic, ES3-level <a href="https://www.justinobney.com/keeping-angular-service-list-data-in-sync-among-multiple-controllers/">solution put forward by Justin Obney</a> is to make <code>users</code> the property of <code>UserService</code> and never reassign it, only mutate (mute? mutilate?). The controller references the service property, and the angular view watch works, since <code>users</code> are shared by reference. Here's the code:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">UserService</span> <span class="token punctuation">{</span><br /> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>users <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token function">load</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/users'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">users</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> angular<span class="token punctuation">.</span><span class="token function">copy</span><span class="token punctuation">(</span>users<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>users<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token function">addUser</span><span class="token punctuation">(</span><span class="token parameter">user</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'./users'</span><span class="token punctuation">,</span> user<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">class</span> <span class="token class-name">UserListController</span> <span class="token punctuation">{</span><br /> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">userService</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>userService <span class="token operator">=</span> userService<span class="token punctuation">;</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>users <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>userService<span class="token punctuation">.</span>users<span class="token punctuation">;</span><br /> userService<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>There are three problems with this solution:</p>
<ol>
<li>It's fragile: if we accidentally reassign <code>users</code> either in the controller or the service, the whole scheme breaks down.</li>
<li>Instead of using normal javascript, you dance around the reference. The result of a well-behaved library function that does not mutate the data must be merged back into the original object.</li>
<li>The suggested way of caring for the reference, <code>angular.copy</code>, is angular-specific and makes a deep copy.</li>
</ol>
<p>We can work around the first issue using TypeScript's <code>readonly</code> properties, but the reference dance persists. Using TS2+ over AngularJS is a bit bipolar, too (exacly what I used on the project, but that's beside the point).</p>
<p>Luckily, we can do much better — let me show you how.</p>
<h2>The get / set solution</h2>
<p>My solution relies on ES5 getters. Compatibility analysis, if I please? ES5 is nothing hot, it's been around long enough to be considered the web standard. People who use IE9 are probably used to the web looking and working strange. Considering a modern framework — Vue or React? They require IE9+ anyways. So yes, we can use ES5 safely.</p>
<p>We do whatever we want to the service property, and declare a getter for it on the controller:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">UserService</span> <span class="token punctuation">{</span><br /> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>users <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token function">load</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/users'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">users</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>users <span class="token operator">=</span> users<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token function">addUser</span><span class="token punctuation">(</span><span class="token parameter">user</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'./users'</span><span class="token punctuation">,</span> user<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">class</span> <span class="token class-name">UserListController</span> <span class="token punctuation">{</span><br /> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">userService</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>userService <span class="token operator">=</span> userService<span class="token punctuation">;</span><br /> userService<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token keyword">get</span> <span class="token function">users</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>userService<span class="token punctuation">.</span>users<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Digest works normally. Mutate the <code>users</code> array in the service and the views update. Reassign in the service — the views still update. Mutate the array in a controller — the views update (a bug, not a feature? Maybe, but that's how it goes). We can't accidentally reassign the controller property because it only has a getter. And we have zero angular-specific code. The trick is backwards-compatible with the old one, so we needn't rewrite the service all at once. Nice!</p>
<h2>What good have we done?</h2>
<p>Is this the holy grail? Certainly not. It requires some boilerplate, a 4-line getter per controller. We're still stuck with the shaky shared ownersip: every controller can change the object. But this is an improvement over the old way.</p>
<p>For completeness, here are three other solutions off the top of my head:</p>
<ol>
<li>Bind to service from the template: <code><user-card ng-repeat="user in $ctrl.userService.users"></user-card></code>. Bad, because it breaks abstraction layering — the view should not touch the service.</li>
<li>Make the service an event bus, do <code>this.trigger('users.update', users);</code> on every users change. Vanilla implementation is fragile (never forget to call <code>trigger</code> on update), but this might work with some structure around (though at this point we might as well stick mobx into the service).</li>
<li><code>$scope.$watch(() => this.userService.users, users => this.users = users)</code>. The effect is the same as in my solution, but at the cost of an extra digest iteration. Fall back to this one for ES3 complicance.</li>
</ol>
<p>Never say never to AngularJS — who knows how it's gonna turn out. Drop a comment if the topic interests you! I still have a couple of AngularJS tricks down my sleeve to keep you safe. ES6 modules? String templates? CSS modules? Yes you can.</p>
Quick Tip: docx is a zip Archive2018-07-14T00:00:00Zhttps://thoughtspile.github.io/2018/07/14/docx-is-a-zip-archive/<p>Microsof Office's <code>docx</code> files are actually zip archives with a bunch of XMLs and all the attached media. Super useful, everyone should know it!</p>
<p>When I tell my colleagues, friends, or students about it, they don't take me seriously the first time. So, here we go again. If you have a docx (or xlsx, or pptx) file, you can unzip it with <code>unzip proj.docx -d proj</code> or any other unarchiver and get a folder with all the stuff that makes up the document:</p>
<p><img src="https://thoughtspile.github.io/images/unzipped-docx.png?invert" alt="" /></p>
<p>From here, you can:</p>
<ul>
<li>quickly grab all the media from <code>word/media</code></li>
<li>work with the document (<code>word/document</code>) via an XML parser (or grep / sed, but it's a secret)</li>
</ul>
<p>And do all the other marvellous stuff — no Office or even GUI needed. Now go and spread the light of this newfound knowledge and never complain about docx again!</p>
Advanced Promise Coordination: Rate Limiting2018-07-07T00:00:00Zhttps://thoughtspile.github.io/2018/07/07/rate-limit-promises/<p>In the <a href="https://thoughtspile.github.io/2018/06/20/serialize-promises/">previous post</a> we learnt to serialize
and concurrecy-limit promise-based operations in js. This time we dive further
and handle rate limiting.</p>
<h2>What Exactly to Rate Limit</h2>
<p>Let's get terminological matters out of the way first. Promises represent operations
that last a certain amount of time, while rate limiting is applied to discrete events.
Over its life, a promise starts and terminates (with a success or a failure, not
important now). It makes most sense to rate limit promise creations (starts).
Rate limiting promise resolutions can be done by appending a start-rate-limited
promise onto the end of the running promise. We could also limit the gap
between operations, but I have no idea how that would be useful.</p>
<h2>Rate vs concurrency limiting</h2>
<p>While both rate and concurrency limits are trying to prevent a client from
overloading the server by making too many calls too fast, they do not replace
one another, and are implemented differently.</p>
<p>Suppose an API is rate-limited to 1 request per second. Even 1-concurrent requests
break the rate limit if they complete in under 1s. On the other hand, if the
requests take 3 seconds to complete, we can only have 3 of them running at the same time:</p>
<pre><code>...
...
...
</code></pre>
<p>We could derive a bunch of formulae to connect the concurrency, rate and
running time of operations, but that's completely beside the point. The thing to
remember here is that without strict guarantees on operation duration you can
not replace concurrency limit with rate limit or vice versa.</p>
<h2>Rate limiting individual operations</h2>
<p>The simplest form of rate limiting is "1 operation per N seconds". This one is
straightforward, but first we need a building block — the promise counterpart
of <code>setTimeout</code>:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">resolveAfter</span> <span class="token operator">=</span> <span class="token parameter">ms</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token parameter">ok</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>ok<span class="token punctuation">,</span> ms<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><code>resolveAfter</code> is self-explanatory: it returns a promise that resolves after
the specified time has elapsed. Now, for the actual rate limiter:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">rateLimit1</span><span class="token punctuation">(</span><span class="token parameter">fn<span class="token punctuation">,</span> msPerOp</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> wait <span class="token operator">=</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>a</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// We use the queue tail in wait to start both the</span><br /> <span class="token comment">// next operation and the next delay</span><br /> <span class="token keyword">const</span> res <span class="token operator">=</span> wait<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">fn</span><span class="token punctuation">(</span><span class="token operator">...</span>a<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> wait <span class="token operator">=</span> wait<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">resolveAfter</span><span class="token punctuation">(</span>msPerOp<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> res<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now we can, as usual, wrap the promise and call with no worries, the operations
are magically delayed:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> slowFetch <span class="token operator">=</span> <span class="token function">rateLimit1</span><span class="token punctuation">(</span>fetch<span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>urls<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">u</span> <span class="token operator">=></span> <span class="token function">slowFetch</span><span class="token punctuation">(</span>u<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">raw</span> <span class="token operator">=></span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>raw<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">p</span> <span class="token operator">=></span> p<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">pages</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>pages<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The 1-rate-limiter can also be elegantly implemented on top of serializer
with the pitfall of unnecessarily delaying the first operation:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">rateLimit1</span><span class="token punctuation">(</span><span class="token parameter">fn<span class="token punctuation">,</span> msPerOp</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> wait <span class="token operator">=</span> <span class="token function">serializePromises</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">resolveAfter</span><span class="token punctuation">(</span>msPerOp<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>a</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">fn</span><span class="token punctuation">(</span><span class="token operator">...</span>a<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h2>Rate limiting multiple operations</h2>
<p>Many APIs feature soft rate limits instead: they allow <code>M request per N seconds</code>.
That is not equivalent to <code>1 request per N/M seconds</code>! Converting the multiple
rate limit into individual one does fulfil the rate limit, but is overly harsh
and non-optimal. Let's see this through examples.</p>
<h3>Difference from individual rate limit, by example</h3>
<p>Suppose you're flying a plane, and the airline allows 10 kg of luggage per
passenger. If you're travelling with a girl, and have one 16-kg bag with both
your things. At the check-in desk you're asked to take out half the stuff in
your bag to make two 8-kg items. While formally correct, it feels idiotic —
you still add the exact same weight to the plane! But now, why would you enforce
such a stupid restriction on your own operations if you can do better?</p>
<p>Closer to the topic, let's try 2-req-per-2-sec rate limit for operations
lasting 2 seconds. If you immediately fire 2 requests, you're done in 2 seconds:</p>
<pre><code>----| 2 seconds, all done!
----|
</code></pre>
<p>Converting this into 1-req-per-1-sec delays the second request by 1s, and
now the same 2 requests take 3 seconds! You just lost a second for no reason.</p>
<pre><code>---- | 3 seconds
----|
</code></pre>
<h3>Understanding</h3>
<p>To understand what we should do, let's have a closer look at the 1-rate-limit.
We essentially make a queue of promises that never resolve closer than <code>delay</code>
apart. We use the resolutions to start the next operations, and don't care
about its termination at all:</p>
<pre><code>*--*-- *--*--
</code></pre>
<p>This view extends to N-rate-limit: create N independent queues and put these
into a circular queue (yes, a queue of queues makes a good <em>in Soviet Russia</em>
joke):</p>
<pre><code>*--*-- *--*--
*-- *--*-- *--
*-- *-- *-- *--
</code></pre>
<p>The individual queues are unchanged, and never fire more than 1 operation per N
seconds. Thus, M queues can fire at most M operations during the window.</p>
<h3>Implementing</h3>
<p>With this plan in mind, we can generalize the implementation:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">rateLimit</span><span class="token punctuation">(</span><span class="token parameter">fn<span class="token punctuation">,</span> windowMs<span class="token punctuation">,</span> reqInWindow <span class="token operator">=</span> <span class="token number">1</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// A battery of 1-rate-limiters</span><br /> <span class="token keyword">const</span> queue <span class="token operator">=</span> _<span class="token punctuation">.</span><span class="token function">range</span><span class="token punctuation">(</span>reqInWindow<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">rateLimit1</span><span class="token punctuation">(</span>fn<span class="token punctuation">,</span> windowMs<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Circular queue cursor</span><br /> <span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>a</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// to enqueue, we move the cursor...</span><br /> i <span class="token operator">=</span> <span class="token punctuation">(</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">%</span> reqInWindow<span class="token punctuation">;</span><br /> <span class="token comment">// and return the rate-limited operation.</span><br /> <span class="token keyword">return</span> queue<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token operator">...</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h2>Preventing queue overflow</h2>
<p>Just as before, we run into problems if the operations are consistently inserted
into the queue faster than the rate limit. The solution is the same: once the
queue exceeds the specified number of items, we immediately reject the incoming
operations.</p>
<h2>Combining with concurrency limiting</h2>
<p>Now that we know how to limit both the rate and the number of simultaneously
running operations, and since neither is a substitute for another, we want a
way to combine the two limits. But can we build the joint rate/concurrency
limiter by composing the primitive limiters? Turns out we can, but should carefully
choose the order.</p>
<p><code>rateLimit(concurrencyLimit(fetch, N), ms)</code>, limits the rate at which the
operations enter the concurrency-limit queue. Serialized (1-concurrent) promises
rate-limited to 1 second break this combination. Suppose the first operation runs for
2 seconds, and during that time we throw 2 fast operations, O_2 and O_3 (say,
10 ms each) into the serializer. Instead of waiting for 1 second, the O_3 starts
right after O_2 completes, or 10ms after it starts, breaking the rate limit.</p>
<p><code>concurrencyLimit(rateLimit(fetch, ms), N)</code> limits the number of operations in
the rate-limit queue. Since the rate limiter only sees N operations at a time,
it has no chance to fire more than N, which is exactly what we want.
Hence, <strong>Chaining Rule 1: limit concurrency before rate.</strong></p>
<h2>Use cases</h2>
<p>The classic and most appropriate rate-limiting use case is for API requests.
But now that you know the pattern, you will see it in your own tasks and,
hopefully, use it ;-)</p>
<p>Promise-based rate limiting is a great way to quickly hack together a safe API
wrapper without depending on the underlying HTTP / TCP / WebSocket client.</p>
<p>Frankly, other use cases I can come up with off the top of my head (render
throttling and preventing too many e-mail notifications) are better served by
batching. Maybe, you'll have better luck.</p>
<h2>Summary</h2>
<p>We've learnt to rate-limit promise-based APIs, both for the simple
"1-action-per-N-seconds" and the more general M-actions case. Together with the
previously discussed concurrency limiter, these patterns allow us to build robust
service gateways with node.js, safely call external APIs and do all the other
things you come up with.</p>
<p>Planning note: I've decided to throw away the excessively tricky part on load
balancing and go with super fun and useful posts on <em>batching</em> and <em>handling failure</em>.
I have RSS now, so be sure to stay tuned!</p>
<p><strong>Advanced Promise Coordination Series</strong></p>
<ul>
<li><a href="https://thoughtspile.github.io/2018/06/20/serialize-promises/">Serialization and Concurrency Limiting</a></li>
<li><a href="https://thoughtspile.github.io/2018/07/07/rate-limit-promises/">Rate Limiting</a></li>
</ul>
Advanced Promises Coordination: Serialization and Concurrency Limiting2018-06-20T00:00:00Zhttps://thoughtspile.github.io/2018/06/20/serialize-promises/<p>I'm sure you can chain promises with <code>doBefore().then(() => doAfter())</code> and even
run multiple promises in parallel using <code>Promise.any</code>. However, chaining an
unknown count of homogenous promises is trickier. Let me teach you to serialze
promises like a pro!</p>
<p>Suppose we want a list of all the cafes in a mid-sized european country.However,
the API only lets you query the cafes by city. No problem — we have a list of
all the cities, and will send a request for each one, then assemble the results.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> cities <span class="token operator">=</span> <span class="token punctuation">[</span><br /> <span class="token string">"Abertamy"</span><span class="token punctuation">,</span><br /> <span class="token string">"Adamov (Blansko District)"</span><span class="token punctuation">,</span><br /> <span class="token string">"Aš"</span><span class="token punctuation">,</span><br /> <span class="token string">"Bakov nad Jizerou"</span><span class="token punctuation">,</span><br /> <span class="token string">"Bavorov"</span><span class="token punctuation">,</span><br /> <span class="token string">"Bechyně"</span><span class="token punctuation">,</span><br /> <span class="token string">"Bečov nad Teplou"</span><span class="token punctuation">,</span><br /> <span class="token string">"Bělá nad Radbuzou"</span><span class="token punctuation">,</span><br /> <span class="token string">"Bělá pod Bezdězem"</span><span class="token punctuation">,</span><br /> <span class="token comment">// ... and 200 more</span><br /><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token function-variable function">loadCafes</span> <span class="token operator">=</span> <span class="token parameter">city</span> <span class="token operator">=></span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">api.fivecircle.com/city/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>city<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h2>How Not to Chain Promises</h2>
<p>The first naive attempts are no good:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// All gone in a glimpse of eye:</span><br />Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>areas<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>loadCafes<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">cafes</span> <span class="token operator">=></span> db<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>_<span class="token punctuation">.</span><span class="token function">flatten</span><span class="token punctuation">(</span>cafes<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// Still not good</span><br />areas<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">area</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">loadCafes</span><span class="token punctuation">(</span>area<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>storeData<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// More of the same</span><br /><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> area <span class="token keyword">in</span> areas<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">loadCafes</span><span class="token punctuation">(</span>area<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>storeData<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Since promises start executing once created, each of these options fires all
the requests at once. With sane rate limiting restrictions, it will fail.
A less elaborate server could even crash.</p>
<p>We could, of course, use <code>await</code>:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> cafes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> area <span class="token keyword">of</span> areas<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> cafes <span class="token operator">=</span> cafes<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span><span class="token keyword">await</span> <span class="token function">loadCafes</span><span class="token punctuation">(</span>area<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token function">storeData</span><span class="token punctuation">(</span>cafes<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>But I'm not a fan of this syntax — the code is now arguably C-like. I also
find error handling in promises cleaner. And now we have more preprocessing to do
for the code to work, which is nothing to be proud of. So let's go on and write this
in pure promises instead.</p>
<h2>Explicit Serialization</h2>
<p>The best-known trick from this bunch is explicitly chaining an array of promises with
<code><Array>.reduce</code>. It works best for fire-and-forget promises, like redux actions:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">return</span> actions<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><br /> <span class="token punctuation">(</span><span class="token parameter">pre<span class="token punctuation">,</span> action</span><span class="token punctuation">)</span> <span class="token operator">=></span> before<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">action</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>However, assembling return values is a bit awkward:</p>
<pre class="language-js"><code class="language-js">areas<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">before<span class="token punctuation">,</span> area</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> before<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">acc</span> <span class="token operator">=></span> <span class="token function">loadCafes</span><span class="token punctuation">(</span>area<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">cafes</span> <span class="token operator">=></span> acc<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>cafes<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">cafes</span> <span class="token operator">=></span> db<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>cafes<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Overall, this is good enough when you have an array of data you want to run the
actions on beforehand. But what if you don't?</p>
<h2>Implicit Serialization</h2>
<p>We can actually write a wrapper for arbitrary promise-returning
functions that makes any call wait for the previous ones to finish. This wrapper
is completely transparent, leaving the function's interface intact — good for
composability. Here it is:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">serializePromises</span><span class="token punctuation">(</span><span class="token parameter">immediate</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// This works as our promise queue</span><br /> <span class="token keyword">let</span> last <span class="token operator">=</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>a</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// Catch is necessary here — otherwise a rejection in a promise will</span><br /> <span class="token comment">// break the serializer forever</span><br /> last <span class="token operator">=</span> last<span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">immediate</span><span class="token punctuation">(</span><span class="token operator">...</span>a<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> last<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now we can just wrap our function and never have to worry about flooding the API again:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> loadCafesSafe <span class="token operator">=</span> <span class="token function">serializePromises</span><span class="token punctuation">(</span>loadCafes<span class="token punctuation">)</span><span class="token punctuation">;</span><br />Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>areas<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">a</span> <span class="token operator">=></span> <span class="token function">loadCafesSafe</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>It's so easy it doesn't warrant a library — just five lines of code. And we can
take this idea further with...</p>
<h2>Concurrency Limiting</h2>
<p>Serialization effectively forces our promises to run in one thread. To make them
go faster, we can generalize the serializer to allow not one, but at most N
promises to run simultaneously:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">limitConcurrency</span><span class="token punctuation">(</span><span class="token parameter">immediate<span class="token punctuation">,</span> maxConcurrent</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// Each element holds its index, or a promise resolving with the index</span><br /> <span class="token keyword">const</span> workers <span class="token operator">=</span> _<span class="token punctuation">.</span><span class="token function">range</span><span class="token punctuation">(</span>maxConcurrent<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Without this serialization, Promise.race would resolve with the same</span><br /> <span class="token comment">// worker whenever a concurrency-limited function was synchronously called</span><br /> <span class="token comment">// multiple times.</span><br /> <span class="token keyword">const</span> findWorker <span class="token operator">=</span> <span class="token function">serializePromises</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> Promise<span class="token punctuation">.</span><span class="token function">race</span><span class="token punctuation">(</span>workers<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>a</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// race resolves with the first free worker</span><br /> <span class="token keyword">return</span> <span class="token function">findWorker</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">i</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// and here we start the action and update the worker correspondingly:</span><br /> <span class="token keyword">const</span> promise <span class="token operator">=</span> <span class="token function">immediate</span><span class="token punctuation">(</span><span class="token operator">...</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> workers<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> promise<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> i<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> i<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> promise<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>The idea is the same, but we replaced the single <code>last</code> promise with an array of
N workers and added some bookkeeping. This code packs promises into threads as
tightly as possible, with no idle time.</p>
<p>Also note that <code>serializePromises</code> is the same as <code>a => limitConcurrency(a, 1)</code>.</p>
<p>If you want to impose joint limiting on several arbitrary functions, you can tweak the
code — I leave this to you as an exercise ;-)</p>
<h2>Propagating Rate Errors</h2>
<p>Now that our code manages a promise queue, we can see a potential problem in it.
The system can smooth activity spikes without propagating these upstream.
However, if the request rate is higher than what the upstream can handle for an
extended period of time, our queue can overfill and blow up the memory limit.</p>
<p>The problem still existed before we added the limiter, but would occurred
upstream instead. No wrapper can magically improve service throughput.</p>
<p>To handle these errors without crashing our process, we can put a hard limit on
queue size. Here's how it can be done for the generic <code>limitConcurrency</code>:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">limitConcurrency</span><span class="token punctuation">(</span><span class="token parameter">immediate<span class="token punctuation">,</span> maxConcurrent<span class="token punctuation">,</span> maxQueue</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// this is our queue counter</span><br /> <span class="token keyword">let</span> queued <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> workers <span class="token operator">=</span> _<span class="token punctuation">.</span><span class="token function">range</span><span class="token punctuation">(</span>maxConcurrent<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> findWorker <span class="token operator">=</span> <span class="token function">serializePromises</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> Promise<span class="token punctuation">.</span><span class="token function">race</span><span class="token punctuation">(</span>workers<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>a</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>queued <span class="token operator">>=</span> maxQueue<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> Promise<span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Max queue size reached'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> queued <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token function">findWorker</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">i</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> queued <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> promise <span class="token operator">=</span> <span class="token function">immediate</span><span class="token punctuation">(</span><span class="token operator">...</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> workers<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> promise<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> i<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> i<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> promise<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now, instead of uncontrollably enqueueing, the coordinator rejects when there's
already too much work ahead. The consumers can handle these errors and retry later.</p>
<h2>Use Cases</h2>
<p>So far we've been discussing an example with API requests, and you might argue
that concurrency limiting functionality should be provided bt the HTTP client
library. That's true, but the power of our promise-based strategy is its generality.
Here are some unorthodox use cases:</p>
<h3>"Sloppy Transactions" with Serialization</h3>
<p>Suppose an action involves reading an external data source, computing on the
response and issuing an update. If the source changes between the read and the
update, you've corrupted your data beyond repair. You can instead wrap the action
with our "promise serializer". Of course, this assumes that the relevant data is only
accessed by your wrapper, and only by a single process. You can even build a
simple file-based database.</p>
<h3>Prevent Notification Flood with Concurrency Limiting</h3>
<p>A front-end idea. You probably have a notification area somewhere on
the screen. However, if a large batch of notifications just arrived, the users are
likely to miss some of those. But now you can treat the currently visible
notifications as the running threads and apply <code>limitConcurrecny</code>!</p>
<p>A similar use case for modal windows uses serialized promises — you can't
show multiple modals at the same time, but now you can enqueue the next one
instead.</p>
<h3>Web Worker Thread Pool</h3>
<p>Finally, time for some cutting-edge tech. If your web app heavily uses web
workers for background processing, you can wrap web worker access with a
promise-based API, then use our wrapper to limit the number of simultaneously
active workers. With several kinds of specialized workers, you might choose to
use a multi-factory flavour of our <code>limitConcurrecny</code> instead. I'll delve
deeper into this this case with an upcoming article on load balancing.</p>
<h2>Summary</h2>
<p>We've learnt how to force promises to run consecutively and even to limit the
number of pending promises to a specified number. This technique can be used
for safer back-end access, and its generality allows to use it for any
promise-based API.</p>
<p>I'm not too good at writing: the topics kept expanding in my head, and I have
had a hard time finishing this article. I have two other interesting
promise coordination patterns to handle in future articles of this series:</p>
<ul>
<li>Rate Limiting</li>
<li>Load Balancing</li>
</ul>
<p>Wish me luck writing these! If you have some tips or want to argue, drop me an
e-mail.</p>
<p><strong>Advanced Promise Coordination Series</strong></p>
<ul>
<li><a href="https://thoughtspile.github.io/2018/06/20/serialize-promises/">Serialization and Concurrency Limiting</a></li>
<li><a href="https://thoughtspile.github.io/2018/07/07/rate-limit-promises/">Rate Limiting</a></li>
</ul>