Prateek's BlogPrateek is a Frontend Engineer currently building Devfolio. Check out the stuff he's been writing lately2023-09-29T00:00:00Zhttps://prateeksurana.me/blog/Prateek Suranahello@prateeksurana.meHow to build a React library using TypeScriptA step by step guide to setup a React Library from scratch using TypeScript, and publish it to NPM.2020-10-18T00:00:00Z2021-08-16T00:00:00Zhttps://prateeksurana.me/blog/react-library-with-typescript/<p>Code reusability and modular design are what have made React one of the best JavaScript frameworks out there. Also, thanks to NPM, publishing a new JavaScript module has never been easier. All you need to do is point the main JavaScript file in your <code>package.json</code> and run <code>npm publish</code>. Although, you need to take care of a few more things when it comes to publishing a TypeScript package, and that's what we'll discuss in this guide.</p>
<h2 class="relative">
<a id="why-typescript" href="https://prateeksurana.me/blog/react-library-with-typescript/#why-typescript" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Why TypeScript?
</a>
</h2>
<p><a href="https://www.typescriptlang.org/">TypeScript is a strict syntactical superset of JavaScript</a> and adds optional static typing to the language, giving you the ability to write code free from the JavaScript type errors.</p>
<p>You don't necessarily need to use TypeScript to create a React module, but adding TypeScript results in a much better DX in editors like VSCode, where users get <a href="https://code.visualstudio.com/docs/languages/typescript#_intellisense">better IntelliSense</a> via the type definitions.</p>
<p>Also if your library becomes popular you will need to add type-definitions sometime, and writing and maintaining those manually is error-prone and wastes a lot of time.</p>
<p>And I think this goes without saying that TypeScript makes your code more robust and less prone to errors.</p>
<h2>Let's Begin</h2>
<p>We'll be building a small library with a <code>create-react-app</code> demo to help you understand the basic boilerplate setup for building and publishing a TypeScript React library, with the following steps -</p>
<ol>
<li><a href="https://prateeksurana.me/blog/react-library-with-typescript/#initializing-a-project-and-adding-typescript">Initializing a project and adding TypeScript</a></li>
<li><a href="https://prateeksurana.me/blog/react-library-with-typescript/#setting-up-typescript-config">Setting up TypeScript config</a></li>
<li><a href="https://prateeksurana.me/blog/react-library-with-typescript/#compiling-typescript">Compiling TypeScript</a></li>
<li><a href="https://prateeksurana.me/blog/react-library-with-typescript/#adding-react">Adding React</a></li>
<li><a href="https://prateeksurana.me/blog/react-library-with-typescript/#setting-up-the-demo">Setting up the demo</a></li>
<li><a href="https://prateeksurana.me/blog/react-library-with-typescript/#getting-ready-for-publishing">Getting ready for Publishing</a></li>
</ol>
<h2 class="relative">
<a id="initializing-a-project-and-adding-typescript" href="https://prateeksurana.me/blog/react-library-with-typescript/#initializing-a-project-and-adding-typescript" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Initializing a project and adding TypeScript
</a>
</h2>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>A small heads up that I'll be using <a href="https://yarnpkg.com/">yarn</a> as the package manager throughout this guide since it is <a href="https://www.cubui.com/blog/javascript/why-yarn-is-better-than-npm/">much better than npm.</a></p>
</aside>
<p>We'll start by going through the setup of a plain JavaScript module using <code>yarn init</code> in a fresh folder and answering the prompts accordingly</p>
<pre class="language-bash"><code class="language-bash">$ <span class="token function">yarn</span> init<br /><span class="token function">yarn</span> init v1.22.4<br />question name <span class="token punctuation">(</span>typescript-react-test<span class="token punctuation">)</span>:<br />question version <span class="token punctuation">(</span><span class="token number">1.0</span>.0<span class="token punctuation">)</span>: <br />question description: Learning how to create React modules using TypeScript<span class="token operator">!</span><br />question entry point <span class="token punctuation">(</span>index.js<span class="token punctuation">)</span>: <br />question repository url: <br />question author: <br />question license <span class="token punctuation">(</span>MIT<span class="token punctuation">)</span>: <br />question private: <br />success Saved package.json</code></pre>
<p>Since we want to write code in TypeScript, we need to set up the build step that will transpile our TypeScript code to JavaScript.</p>
<p>Install TypeScript as a dev dependency by running:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">yarn</span> <span class="token function">add</span> --dev typescript</code></pre>
<p>While you're at it, don't forget to set up git in your folder and regularly commit; if you’re a beginner to git, I would recommend going through this <a href="https://product.hubspot.com/blog/git-and-github-tutorial-for-beginners">git tutorial for beginners</a>.</p>
<h2 class="relative">
<a id="setting-up-typescript-config" href="https://prateeksurana.me/blog/react-library-with-typescript/#setting-up-typescript-config" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Setting up TypeScript config
</a>
</h2>
<p>To compile TypeScript and enforce/ignore certain rules, we will need to create a config file called <code>tsconfig.json</code> in the root directory. At the time of writing this tutorial, I'm using <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html">TypeScript version 4.0.2</a>. So if something is not working for you, it might be because some keys might have been updated or deprecated. I would recommend you to consult the <a href="https://www.typescriptlang.org/docs/handbook/release-notes/overview.html">release notes</a> for that.</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> <span class="token property">"compilerOptions"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"outDir"</span><span class="token operator">:</span> <span class="token string">"lib/esm"</span><span class="token punctuation">,</span><br /> <span class="token property">"module"</span><span class="token operator">:</span> <span class="token string">"esnext"</span><span class="token punctuation">,</span><br /> <span class="token property">"target"</span><span class="token operator">:</span> <span class="token string">"es5"</span><span class="token punctuation">,</span><br /> <span class="token property">"lib"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"es6"</span><span class="token punctuation">,</span> <span class="token string">"dom"</span><span class="token punctuation">,</span> <span class="token string">"es2016"</span><span class="token punctuation">,</span> <span class="token string">"es2017"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token property">"jsx"</span><span class="token operator">:</span> <span class="token string">"react"</span><span class="token punctuation">,</span><br /> <span class="token property">"declaration"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"moduleResolution"</span><span class="token operator">:</span> <span class="token string">"node"</span><span class="token punctuation">,</span><br /> <span class="token property">"noUnusedLocals"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"noUnusedParameters"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"esModuleInterop"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"noImplicitReturns"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"noImplicitThis"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"noImplicitAny"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"strictNullChecks"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"suppressImplicitAnyIndexErrors"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"allowSyntheticDefaultImports"</span><span class="token operator">:</span> <span class="token boolean">true</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token property">"include"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"src"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token property">"exclude"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"node_modules"</span><span class="token punctuation">,</span> <span class="token string">"lib"</span><span class="token punctuation">]</span><br /><span class="token punctuation">}</span></code></pre>
<p>Some of the important parameters we are using here are -</p>
<ul>
<li><strong><a href="https://www.typescriptlang.org/tsconfig#outDir">outDir</a></strong> - Specifies the output build directory for our compiled code. Since this would create an ESM build, we would output it to a folder called <code>lib/esm</code>. I'll explain why we did this later in this post.</li>
<li><strong><a href="https://www.typescriptlang.org/tsconfig#module">module</a></strong> - Sets the module system that your code will be compiled to. TypeScript supports a <a href="https://www.typescriptlang.org/tsconfig#module">variety of module types</a>.</li>
<li><strong><a href="https://www.typescriptlang.org/tsconfig#target">target</a></strong> - The ECMAScript version target we want TypeScript to compile to.</li>
<li><strong><a href="https://www.typescriptlang.org/tsconfig#lib">lib</a></strong> - Specified the built-in JS libraries that you want to support.</li>
<li><strong><a href="https://www.typescriptlang.org/tsconfig#include">include</a> - F</strong>ilename patterns that the compiler should scan.</li>
<li><strong><a href="https://www.typescriptlang.org/tsconfig#exclude">exclude</a> -</strong> Filename patterns that the compiler should ignore.</li>
</ul>
<p>TypeScript offers <a href="https://www.typescriptlang.org/tsconfig">a lot of flags for type checking and compiling</a>.</p>
<p>It's time to test our setup now, create a file <code>src/index.ts</code> and add the following snippet to it -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">sayHello</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">name</span><span class="token operator">:</span> string</span><span class="token punctuation">)</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 string">Hey </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">, say hello to TypeScript.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Next, add the following build script to your <code>package.json</code> -</p>
<pre class="language-json"><code class="language-json"><span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"build"</span><span class="token operator">:</span> <span class="token string">"tsc"</span><br /> <span class="token punctuation">}</span></code></pre>
<p>Now run <code>yarn build</code> inside your root folder, and you should see a folder called <code>lib/esm</code> with the following files -</p>
<ul>
<li><code>index.js</code> - The compiled output file.</li>
<li><code>index.d.ts</code> - The <a href="https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-d-ts.html">type definitions</a> for your code.</li>
</ul>
<h2 class="relative">
<a id="compiling-typescript" href="https://prateeksurana.me/blog/react-library-with-typescript/#compiling-typescript" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Compiling TypeScript
</a>
</h2>
<p>Although we completed the setup for TypeScript and added a build script in the last step, we're not quite ready yet. To be able to publish our package to NPM, you need to be aware of the different types of modules available in the JavaScript ecosystem -</p>
<ul>
<li><a href="https://nodejs.org/docs/latest/api/modules.html#modules_modules_commonjs_modules">CommonJS</a> - This module format is most commonly used with Node using the <code>require</code> function. Even though we are publishing a React module (which will be consumed by an application generally written in ESM format, then bundled and compiled by tools like webpack), we need to consider that it might also be used within a Server side rendering environment, which generally uses Node and hence might require a CJS counterpart of the library (ESM modules are supported in Node environment as of <a href="https://nodejs.org/dist./v10.22.0/docs/api/esm.html">v10 behind an experimental flag</a>).</li>
<li><a href="https://nodejs.org/api/esm.html#esm_modules_ecmascript_modules">ESM</a> - This is the modern module format that we normally use in our React applications in which modules are defined using a variety of import and export statements. The main benefit of shipping ES modules is that it <a href="https://bitsofco.de/what-is-tree-shaking/">makes your library tree-shakable</a>. This is supported by tools like Rollup and webpack 2+.</li>
<li><a href="https://riptutorial.com/javascript/example/16339/universal-module-definition">UMD</a> - This module format is not as popular these days. It is required when the user requires our module using a script tag.</li>
</ul>
<p>So for our package, we will add support for both ESM and CommonJS modules. Currently, if you open the <code>index.js</code> file in the <code>lib/esm</code> folder, you will find that it's using the ESM format because we specified the module type to <code>esnext</code> in the <code>tsconfig</code> compiler options. That's the reason why I asked you to send the output to the <code>lib/esm</code> folder when setting up TypeScript.</p>
<p>To create a CommonJS module, add the following script to the scripts key in your <code>package.json</code></p>
<pre class="language-json"><code class="language-json"><span class="token property">"build:cjs"</span><span class="token operator">:</span> <span class="token string">"tsc --module commonjs --outDir lib/cjs"</span><span class="token punctuation">,</span></code></pre>
<p>The above script would use the configuration that we defined in our <code>tsconfig</code> except with the module key being changed to <code>commonjs</code> and the output directory to <code>lib/cjs</code></p>
<p>Also, update the <code>build</code> script and add a <code>build:esm</code> script as well, making the final scripts looking like this -</p>
<pre class="language-json"><code class="language-json"><span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"build"</span><span class="token operator">:</span> <span class="token string">"yarn build:esm && yarn build:cjs"</span><span class="token punctuation">,</span><br /> <span class="token property">"build:esm"</span><span class="token operator">:</span> <span class="token string">"tsc"</span><span class="token punctuation">,</span><br /> <span class="token property">"build:cjs"</span><span class="token operator">:</span> <span class="token string">"tsc --module commonjs --outDir lib/cjs"</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre>
<p>You would see two folders in your lib directory when you run <code>yarn build</code>, one for both ESM and CommonJS builds.</p>
<p>As one last step, we need to specify the entry points for different types of modules in <code>package.json</code>, so that module bundlers/tools can pick up the appropriate format for them.</p>
<pre class="language-json"><code class="language-json"><span class="token property">"main"</span><span class="token operator">:</span> <span class="token string">"./lib/cjs/index.js"</span><span class="token punctuation">,</span><br /><span class="token property">"module"</span><span class="token operator">:</span> <span class="token string">"./lib/esm/index.js"</span><span class="token punctuation">,</span><br /><span class="token property">"types"</span><span class="token operator">:</span> <span class="token string">"./lib/esm/index.d.ts"</span><span class="token punctuation">,</span></code></pre>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>If your library has some external dependencies or some other assets, I would recommend using module bundlers like <a href="https://prateeksurana.me/blog/react-component-library-using-storybook-6/#compiling-the-library-using-rollup">Rollup to compile your library</a>.</p>
</aside>
<h2 class="relative">
<a id="adding-react" href="https://prateeksurana.me/blog/react-library-with-typescript/#adding-react" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Adding React
</a>
</h2>
<p>We have the TypeScript setup up and running, now its time to add React.</p>
<p>Since <code>react</code> requires that we need to have a single copy of <code>react-dom</code> that is also used by the person installing it. We will be adding it as a <a href="https://flaviocopes.com/npm-peer-dependencies/"><code>peerDependency</code></a>. Add the following snippet to your <code>package.json</code>.</p>
<pre class="language-json"><code class="language-json"><span class="token property">"peerDependencies"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"react"</span><span class="token operator">:</span> <span class="token string">"^16.8.0"</span><span class="token punctuation">,</span><br /> <span class="token property">"react-dom"</span><span class="token operator">:</span> <span class="token string">"^16.8.0"</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre>
<p>But we also need React in our module, so we need to add it as a dev dependency as well -</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">yarn</span> <span class="token function">add</span> --dev react-dom react @types/react-dom @types/react</code></pre>
<p>Now let's create our React component, which will be consumed by our library users, replace the code in <code>src/index.tsx</code> with this -</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> SayHello <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> name <span class="token punctuation">}</span><span class="token operator">:</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 punctuation">)</span><span class="token operator">:</span> <span class="token constant">JSX</span><span class="token punctuation">.</span>Element <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 punctuation">></span></span><span class="token plain-text">Hey </span><span class="token punctuation">{</span>name<span class="token punctuation">}</span><span class="token plain-text">, say hello to TypeScript.</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 /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> SayHello<span class="token punctuation">;</span></code></pre>
<h2 class="relative">
<a id="setting-up-the-demo" href="https://prateeksurana.me/blog/react-library-with-typescript/#setting-up-the-demo" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Setting up the demo
</a>
</h2>
<p>First let's build our library so that it can be consumed. Run the build command that we created in the last step -</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">yarn</span> build</code></pre>
<p>To be able to use and test our library, we would need an example project. We will be using <a href="https://create-react-app.dev/docs/adding-typescript/"><code>create-react-app</code> with the TypeScript template</a> for creating an example app to test our library. Run the following in the root folder -</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">yarn</span> create react-app example --template typescript</code></pre>
<p>The above command would create a new TypeScript React application in a folder called example.</p>
<p>Also since we would be using the <code>.gitignore</code> file in our root folder, so delete the git related files from the example folder.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">rm</span> -rf example/.gitignore example/.git</code></pre>
<p>Now add the following to your root project's <code>.gitignore</code> file to exclude the <code>build</code> and <code>node_modules</code> folder from git.</p>
<pre class="language-bash"><code class="language-bash">/example/node_modules<br />/example/build</code></pre>
<p>To use our library in the example project, just add the following to your <code>package.json</code> dependencies, and run <code>yarn</code> <strong>in the example directory.</strong></p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> ...<br /> dependencies<span class="token operator">:</span> <span class="token punctuation">{</span><br /> ...<br /> <span class="token property">"typescript-react-test"</span><span class="token operator">:</span> <span class="token string">"link:.."</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>This would install the package as a local dependency for our example project. If you're curious and want to know how it works and you're using VSCode, you can expand the <code>node_modules</code> folder in your example project, and you should be able to see the package with a ↪ symbol next to it.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/vs-code-node-modules-316.webp 316w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/vs-code-node-modules-316.jpeg 316w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/vs-code-node-modules-316.jpeg" width="316" height="189" alt="VSCode node_modules for our package" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>You can now add your component to the <code>App.tsx</code> to try it out.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> SayHello <span class="token keyword">from</span> <span class="token string">'typescript-react-test'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token string">'./App.css'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">App</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">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>App<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">SayHello</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>Prateek<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>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><br /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> App<span class="token punctuation">;</span></code></pre>
<p>Run yarn start in <strong>the example folder</strong>, and you should be able to see your component in the browser. To update the lib</p>
<h2 class="relative">
<a id="getting-ready-for-publishing" href="https://prateeksurana.me/blog/react-library-with-typescript/#getting-ready-for-publishing" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Getting ready for publishing
</a>
</h2>
<p>So now that we have added the demo and tested our module locally its now time to publish it to NPM so others can use it as well.</p>
<p>Before we get to publishing, we need to tell NPM the files that need to be included when your package is installed as a dependency. There are two ways to do that -</p>
<ul>
<li>Create a <a href="http://npm.github.io/publishing-pkgs-docs/publishing/the-npmignore-file.html"><code>.npmignore</code></a> file, which is similar to <code>.gitignore</code> in which you blacklist the files that you don't want your package to include.</li>
<li>Use the <a href="https://docs.npmjs.com/files/package.json#files"><code>files</code> key</a> in your <code>package.json</code> to whitelist the files you want to include in your final package.</li>
</ul>
<p>I actually prefer the latter because, as you add more types of files such as ESLint configs, environment files, you don't need to remember to keep adding them to <code>.npmignore</code> instead, you can specify the files to be included in your <code>package.json</code></p>
<p>Although you can use a combination of the files key and <code>.npmignore</code> for certain situations like if you have <code>__tests__</code> in your lib folder and you want to exclude the tests but include the <code>lib</code> folder, in that case, you can specify <code>/lib</code> in the files key of <code>package.json</code> and add <code>__test__</code> in <code>.npmignore</code> so that the tests are not included, but the rest of the stuff is.</p>
<p>Remember that files like <code>README</code>, <code>package.json</code>, and <code>LICENSE</code> are automatically included in your package and cannot be excluded.</p>
<p>For our case, we only want to add the <code>lib</code> folder to the <code>package.json</code> because that's where all our compiled JavaScript files are. Add the following to your <code>package.json</code> -</p>
<pre class="language-json"><code class="language-json"><span class="token property">"files"</span><span class="token operator">:</span> <span class="token punctuation">[</span><br /> <span class="token string">"/lib"</span><br /> <span class="token punctuation">]</span></code></pre>
<p>Time to publish our library!</p>
<p>If this is the first time you are publishing a package you would need to create an account on <a href="https://www.npmjs.com/">https://www.npmjs.com/</a> and then you need to log in from the command line using <code>npm login</code>.</p>
<p>After that, just run <code>npm publish</code> in your root directory, and voila, you've successfully created and published a TypeScript React NPM package.</p>
<p>You can find the code of the library and the demo we created <a href="https://github.com/prateek3255/typescript-react-demo-library">on GitHub</a>.</p>
<h2>Next Steps</h2>
<p>There still many things that you can and should do that I couldn't cover in the above guide, like -</p>
<ul>
<li>You can deploy the demo to something like <a href="https://www.netlify.com/blog/2016/09/29/a-step-by-step-guide-deploying-on-netlify/">Netlify</a> or <a href="https://github.com/marketplace/actions/deploy-to-github-pages">GitHub Pages</a> and automate the process to deploy changes when you merge PRs automatically.</li>
<li>Add test cases using <a href="https://www.pluralsight.com/guides/how-to-test-react-components-in-typescript">Jest and React Testing Library</a>.</li>
<li>Setup <a href="https://www.orangejellyfish.com/blog/code-consistency-with-eslint-and-husky/">Husky pre-commit hook with ESLint</a> for enforcing linting in your code.</li>
</ul>
Using environment variables with WebpackA guide for setting up and using environment variables with Webpack and handling different values for Production and Development environments.2020-11-09T00:00:00Z2021-07-04T00:00:00Zhttps://prateeksurana.me/blog/using-environment-variables-with-webpack/<p>When building for the web, we often have to deal with some sensitive data (like API keys), which cannot be pushed to source control or some things that are supposed to be different for development and production environments (like sending error logs to sentry only in a production environment). That's where environment variables come in as they let us store this kind of data with a breeze.</p>
<p>Now there are two ways of using environment variables -</p>
<ul>
<li>Storing those variables in the system environment, via the <a href="https://medium.com/@youngstone89/setting-up-environment-variables-in-mac-os-28e5941c771c">Terminal in Mac/Linux</a> or using the <a href="https://docs.oracle.com/en/database/oracle/r-enterprise/1.5.1/oread/creating-and-modifying-environment-variables-on-windows.html">GUI in Windows</a>.</li>
<li>Storing and accessing those variables from a <code>.env</code> file in the project's root folder, which is ignored from version control.</li>
</ul>
<p>This post will cover the second method and show you how to inject environment variables from a <code>.env</code> file to your application via Webpack's <code>dotenv-webpack</code> plugin via discussing the following steps -</p>
<ol>
<li><a href="https://prateeksurana.me/blog/using-environment-variables-with-webpack/#the-env-file">The .env file</a></li>
<li><a href="https://prateeksurana.me/blog/using-environment-variables-with-webpack/#accessing-environment-variables-via-webpack">Accessing environment variables via Webpack</a></li>
<li><a href="https://prateeksurana.me/blog/using-environment-variables-with-webpack/#using-different-env-files-for-production-and-development-environments">Using different .env files for production and development environments</a></li>
</ol>
<h2 class="relative">
<a id="the-env-file" href="https://prateeksurana.me/blog/using-environment-variables-with-webpack/#the-env-file" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
The .env file
</a>
</h2>
<p>I prefer creating a <code>.env</code> file with all the environment variables in the project root and <a href="https://www.atlassian.com/git/tutorials/saving-changes/gitignore">adding a line that references this file to your <code>.gitignore</code></a> so that they are not pushed to source control. This way, whenever someone new is cloning your repository, they wouldn't need to add these variables to their system environment (which can be really painful in operating systems like Windows).</p>
<p>Since we are not adding the <code>.env</code> file to the source control, it is good practice to create a <code>.env.example</code> file, which is added to source control and contains the value of the variables for the development environment or contains comments on how to obtain them.</p>
<p>For instance, the <code>.env</code> file for your production environment could be like -</p>
<pre class="language-bash"><code class="language-bash"><span class="token assign-left variable">API_ROOT</span><span class="token operator">=</span>https://myawesomeAPIRoot.com/<br /><span class="token assign-left variable">NODE_ENV</span><span class="token operator">=</span>production<br /><span class="token assign-left variable">SOME_IMPORTANT_API_KEY</span><span class="token operator">=</span>th1s181mpo97@n7</code></pre>
<p>And the corresponding <code>.env.example</code> will be -</p>
<pre class="language-bash"><code class="language-bash"><span class="token comment"># The base API endpoint to which requests are made</span><br /><span class="token assign-left variable">API_ROOT</span><span class="token operator">=</span>http://localhost:5000/<br /><br /><span class="token comment"># Whether we are using a production or development environment</span><br /><span class="token assign-left variable">NODE_ENV</span><span class="token operator">=</span>development<br /><br /><span class="token comment"># API key fetching some important things, get the key for your </span><br /><span class="token comment"># development environment at https://somerandomthings.com/important-keys </span><br /><span class="token assign-left variable">SOME_IMPORTANT_API_KEY</span><span class="token operator">=</span></code></pre>
<p>This way, someone new cloning your repo can create <code>.env</code> and add all the required variables accordingly.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>If you, like me, don't want the mental burden of keeping the <code>.env</code> and <code>.env.example</code> files in sync you can use a tool like <a href="https://github.com/codeshifu/sync-dotenv#examples">sync-dotenv</a> which syncs the envs with a simple command, that you can run before every commit automatically using <a href="https://github.com/typicode/husky">husky</a>.</p>
</aside>
<h2 class="relative">
<a id="accessing-environment-variables-via-webpack" href="https://prateeksurana.me/blog/using-environment-variables-with-webpack/#accessing-environment-variables-via-webpack" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Accessing environment variables via Webpack
</a>
</h2>
<p>Now let's read these variables in our code. To do that, we would be using the <a href="https://www.npmjs.com/package/dotenv-webpack"><code>dotenv-webpack</code> plugin</a>. Install this plugin as a dev dependency -</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">yarn</span> <span class="token function">install</span> dotenv-webpack --dev<br /><br />OR<br /><br /><span class="token function">npm</span> <span class="token function">install</span> dotenv-webpack --save-dev</code></pre>
<p>This plugin is to read the environment variables from the <code>.env</code> file <strong>securely by only exposing the variables used in the code.</strong></p>
<p>Add it to the plugins in your webpack config file -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// webpack.config.js</span><br /><span class="token keyword">const</span> Dotenv <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'dotenv-webpack'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br />module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span><br /> <span class="token literal-property property">plugins</span><span class="token operator">:</span> <span class="token punctuation">[</span><br /> <span class="token keyword">new</span> <span class="token class-name">Dotenv</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token operator">...</span><br /> <span class="token punctuation">]</span><br /> <span class="token operator">...</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Now you can use the variables in your code using the <code>process.env</code> syntax anywhere in your code and then webpack will replace them with the corresponding values in the <code>.env</code> file.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>Use <a href="https://www.npmjs.com/package/dotenv-webpack#properties"><code>systemVars: true</code> flag</a> in the Dotenv plugin to load the system variables as well, which comes in handy when deploying to Netlify or any other CI where you can't add the <code>.env</code> file due to source control restrictions.</p>
</aside>
<p>For example -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">requestMyAwesomeService</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> data <span class="token operator">=</span> <span class="token keyword">await</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 interpolation"><span class="token interpolation-punctuation punctuation">${</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">API_ROOT</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">awesome</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><br /> <span class="token operator">...</span><br /><span class="token punctuation">}</span></code></pre>
<p>On a sidenote a slightly cleaner approach would be store these variables in a separate file and export them from that file.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// constants/envrionment.js</span><br /><br /><span class="token keyword">const</span> <span class="token constant">API_ROOT</span> <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">API_ROOT</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token constant">NODE_ENV</span> <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> <span class="token constant">SOME_IMPORTANT_API_KEY</span> <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">SOME_IMPORTANT_API_KEY</span><br /><br /><span class="token keyword">export</span> <span class="token punctuation">{</span><br /> <span class="token constant">API_ROOT</span><span class="token punctuation">,</span><br /> <span class="token constant">NODE_ENV</span><span class="token punctuation">,</span><br /> <span class="token constant">SOME_IMPORTANT_API_KEY</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>And if you are using <a href="https://webpack.js.org/configuration/resolve/">webpack aliases</a> for your folders, you could consume these constants in any file like this -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token constant">API_ROOT</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'constants/environment'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> <span class="token function-variable function">requestMyAwesomeService</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> data <span class="token operator">=</span> <span class="token keyword">await</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 interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">API_ROOT</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">awesome_route</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><br /> <span class="token operator">...</span><br /><span class="token punctuation">}</span></code></pre>
<p>This way, linters like <a href="https://eslint.org/">ESLint</a> or compilers like <a href="https://www.typescriptlang.org/">TypeScript</a> would prevent you from spelling mistakes, and you would get <a href="https://code.visualstudio.com/docs/editor/intellisense">nifty auto-complete while importing these variables with editors like VSCode</a>.</p>
<h2 class="relative">
<a id="using-different-env-files-for-production-and-development-environments" href="https://prateeksurana.me/blog/using-environment-variables-with-webpack/#using-different-env-files-for-production-and-development-environments" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Using different .env files for production and development environments
</a>
</h2>
<p>When using environment variables, you might need to use different values for some keys depending on whether you are in a development or production environment.</p>
<p>For example, <code>SOME_IMPORTANT_API_KEY</code> could have some domain restrictions for the production environment, which would not work on the development <code>localhost</code> domains, so we need a way to have separate values for our environment variables in development and production environments.</p>
<p>So we would need to create two files here, namely <code>.env.production</code> and <code>.env.development</code>, which would contain the variables for the production and development environments, respectively.</p>
<p>To read these <code>.env</code> files, we would need to pass <a href="https://webpack.js.org/guides/environment-variables/">environment variables via the CLI</a> to our scripts in the <code>package.json</code> to read them in our <code>webpack.config.js</code> file. Assuming you are using <a href="https://webpack.js.org/configuration/dev-server/"><code>webpack-dev-server</code></a> for your development environment, your scripts would look something like this -</p>
<pre class="language-json"><code class="language-json"><span class="token comment">// package.json</span><br /><br /><span class="token punctuation">{</span><br /> ...<br /> scripts<span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"build"</span><span class="token operator">:</span> <span class="token string">"webpack --env production --mode production"</span><span class="token punctuation">,</span><br /> <span class="token property">"dev"</span><span class="token operator">:</span> <span class="token string">"webpack-dev-server --env development --mode development"</span><span class="token punctuation">,</span><br /> ...<br /> <span class="token punctuation">}</span><br /> ...<br /><span class="token punctuation">}</span></code></pre>
<p>Now you can read the value of environment variables you just passed in through your CLI in your webpack config, and load the appropriate <code>.env</code> file using the <a href="https://github.com/mrsteele/dotenv-webpack#properties"><code>dotenv-webpack</code> 's path</a> property.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// webpack.config.js</span><br /><span class="token operator">...</span><br /><br />module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token parameter">env</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token operator">...</span><br /> <span class="token literal-property property">plugins</span><span class="token operator">:</span> <span class="token punctuation">[</span><br /> <span class="token keyword">new</span> <span class="token class-name">Dotenv</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">path</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">./.env.</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>env<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token operator">...</span><br /> <span class="token punctuation">]</span><br /> <span class="token operator">...</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>And that's it. With this setup you can securely access multiple environment values for separate values for production and development environments.</p>
Creating a React component library using Storybook 7Learn how to build a React component library using Storybook 7 and TypeScript, compile it with Rollup and publish it.2020-12-20T00:00:00Z2023-05-14T00:00:00Zhttps://prateeksurana.me/blog/react-component-library-using-storybook-7/<p>If you have multiple projects that are using the same design system (inputs, buttons, other reusable components, etc.), then you probably have a good enough use case to create a shared component library that can be published and consumed directly by all your projects.</p>
<p>Another benefit is that you can develop UI components easily in isolation and render their different states directly, without needing to mess with the business logic in your dev stack, with the help of <a href="https://storybook.js.org/">Storybook</a>.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/storybook-working-480.webp 480w, https://prateeksurana.me/img/storybook-working-768.webp 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/storybook-working-480.jpeg 480w, https://prateeksurana.me/img/storybook-working-768.jpeg 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/storybook-working-768.jpeg" width="768" height="204" alt="Storybook working" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>In this tutorial, I would be covering the steps for creating and publishing a React component library (<a href="https://storybook.js.org/">Storybook supports countless other frontend frameworks</a>), with the following steps.</p>
<ol>
<li><a href="https://prateeksurana.me/blog/react-component-library-using-storybook-7/#setting-up-the-project">Setting up the project</a></li>
<li><a href="https://prateeksurana.me/blog/react-component-library-using-storybook-7/#installing-storybook">Installing Storybook</a></li>
<li><a href="https://prateeksurana.me/blog/react-component-library-using-storybook-7/#adding-stories-and-setting-up-the-file-structure">Adding stories and setting up the file structure</a></li>
<li><a href="https://prateeksurana.me/blog/react-component-library-using-storybook-7/#compiling-the-library-using-rollup">Compiling the Library using Rollup</a></li>
<li><a href="https://prateeksurana.me/blog/react-component-library-using-storybook-7/#publishing-and-consuming-the-library">Publishing and consuming the library</a></li>
</ol>
<p>You can find all the code that we will be writing in this tutorial on <a href="https://github.com/prateek3255/my-awesome-component-library">GitHub</a>.</p>
<h2 class="relative">
<a id="setting-up-the-project" href="https://prateeksurana.me/blog/react-component-library-using-storybook-7/#setting-up-the-project" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Setting up the project
</a>
</h2>
<p>Since we are building a component library that would be published to a package manager we would be creating a new package from scratch.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>If you have a component library using React already setup, then you can directly move forward to the next step. We just need a basic React setup before we can install Storybook.</p>
</aside>
<p>For that, create a new folder with whatever name you want for your component library. I would be calling mine my-awesome-component-library.</p>
<p>Then run <a href="https://classic.yarnpkg.com/en/docs/cli/init/"><code>yarn init</code></a> and <a href="https://github.com/git-guides/git-init"><code>git init</code></a>, respectively, in that folder providing appropriate values for the fields asked. This would initialize an empty NPM project with git. Also, <a href="https://docs.github.com/en/free-pro-team@latest/github/using-git/ignoring-files">set up a gitignore file</a>.</p>
<p>We are building a React component library, so we would need to React to build our components. Also, we are going to use <a href="https://prateeksurana.me/blog/react-library-with-typescript/#why-typescript">TypeScript to build our library</a>. Let's add that too.</p>
<pre class="language-jsx"><code class="language-jsx">yarn add <span class="token operator">--</span>dev react react<span class="token operator">-</span>dom @types<span class="token operator">/</span>react typescript</code></pre>
<p>Since <code>react</code> requires that we need to have a single copy of <code>react-dom</code>, we will be adding it as a <a href="https://flaviocopes.com/npm-peer-dependencies/">peerDependency</a> so that our package always uses the installing client's version. Add the following snippet to your package.json.</p>
<pre class="language-json"><code class="language-json">...<br /><span class="token property">"peerDependencies"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"react"</span><span class="token operator">:</span> <span class="token string">"^18.2.0"</span><span class="token punctuation">,</span><br /> <span class="token property">"react-dom"</span><span class="token operator">:</span> <span class="token string">"^18.2.0"</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span><br />...</code></pre>
<p>As one last step for setting up the project, let's also add a <code>tsconfig</code> for compiling our TypeScript. Create a file called <code>tsconfig.json</code> in the root and add the following to it.</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> <span class="token property">"compilerOptions"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"target"</span><span class="token operator">:</span> <span class="token string">"es5"</span><span class="token punctuation">,</span><br /> <span class="token property">"outDir"</span><span class="token operator">:</span> <span class="token string">"lib"</span><span class="token punctuation">,</span><br /> <span class="token property">"lib"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"dom"</span><span class="token punctuation">,</span> <span class="token string">"dom.iterable"</span><span class="token punctuation">,</span> <span class="token string">"esnext"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token property">"declaration"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"declarationDir"</span><span class="token operator">:</span> <span class="token string">"lib"</span><span class="token punctuation">,</span><br /> <span class="token property">"allowJs"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"skipLibCheck"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"esModuleInterop"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"allowSyntheticDefaultImports"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"strict"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"forceConsistentCasingInFileNames"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"module"</span><span class="token operator">:</span> <span class="token string">"esnext"</span><span class="token punctuation">,</span><br /> <span class="token property">"moduleResolution"</span><span class="token operator">:</span> <span class="token string">"node"</span><span class="token punctuation">,</span><br /> <span class="token property">"resolveJsonModule"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"isolatedModules"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"noEmit"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"jsx"</span><span class="token operator">:</span> <span class="token string">"react"</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token property">"include"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"src"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token property">"exclude"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"node_modules"</span><span class="token punctuation">,</span> <span class="token string">"lib"</span><span class="token punctuation">]</span><br /><span class="token punctuation">}</span></code></pre>
<p>These options help TypeScript to <a href="https://prateeksurana.me/blog/react-library-with-typescript/#setting-up-typescript-config">ignore and enforce certain rules while compiling our code</a>. You can <a href="https://www.typescriptlang.org/tsconfig">check out all the flags available in the docs</a>.</p>
<h2 class="relative">
<a id="installing-storybook" href="https://prateeksurana.me/blog/react-component-library-using-storybook-7/#installing-storybook" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Installing Storybook
</a>
</h2>
<p>Now that we have the React boilerplate ready we can now install Storybook, run the following command in the root folder to add Storybook to your project</p>
<pre class="language-json"><code class="language-json">npx sb init</code></pre>
<p>This command will install all the core <code>devDependencies</code>, add scripts, setup some configuration files, and create example stories for you to get <a href="https://storybook.js.org/docs/react/get-started/install">you up and running with Storybook</a>. The Storybook version has been updated to 7.0.11 since this article was first published.</p>
<p>You can now run <code>yarn storybook</code> and that should boot up Storybook for you with the examples they created for you.</p>
<p>Once you are done playing with the example, you can go ahead and safely delete the stories folder.</p>
<p>Now open the <code>.storybook/main.js</code> file. This file controls the behavior of your Storybook server by specifying the <a href="https://storybook.js.org/docs/react/configure/overview#configure-your-storybook-project">configuration for your stories</a>.</p>
<p>Update the stories key in the file to this -</p>
<pre class="language-json"><code class="language-json">...<br /><span class="token property">"stories"</span><span class="token operator">:</span> <span class="token punctuation">[</span><br /> <span class="token string">"../src/**/*.stories.tsx"</span><br /> <span class="token punctuation">]</span><span class="token punctuation">,</span><br />...</code></pre>
<p>This config would run TypeScript stories defined in the <code>src</code> folder, which we would be creating in the next step.</p>
<h2 class="relative">
<a id="adding-stories-and-setting-up-the-file-structure" href="https://prateeksurana.me/blog/react-component-library-using-storybook-7/#adding-stories-and-setting-up-the-file-structure" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Adding stories and setting up the file structure
</a>
</h2>
<p>Now that we have the Storybook setup, we can start creating our components and writing stories for them.</p>
<p>But first of all what are stories anyways?</p>
<p>Glad you asked, <a href="https://storybook.js.org/docs/react/get-started/whats-a-story">from the docs</a> -</p>
<blockquote>
<p>A story captures the rendered state of a UI component. Developers write multiple stories per component that describe all the “interesting” states a component can support.</p>
</blockquote>
<p>In short, Stories let you render the different states of your UI component and lets you play with the different states with something called <a href="https://storybook.js.org/docs/react/essentials/controls">Storybook Controls</a>, which we will get to in a minute. These are development only files and hence won't be included in our final library bundle.</p>
<p>Let's create a demo component to check out how stories work and how you can make the most out of it.</p>
<p>Our file structure would look something like this -</p>
<pre class="language-bash"><code class="language-bash">.storybook/<br /> main.js<br /> preview.js<br />.gitignore<br />package.json<br />rollup.config.mjs<br />tsconfig.json<br />src/<br /> components/<br /> MyAwesomeComponent/<br /> MyAwesomeComponent.tsx<br /> MyAwesomeComponent.css<br /> MyAwesomeComponent.stories.tsx<br /> index.ts<br /> index.ts</code></pre>
<p>We will be using the same button component that Storybook gave us with the demo earlier for demonstrating.</p>
<p>Create a folder <code>src/components/Button</code> and paste the <a href="https://github.com/prateek3255/my-awesome-component-library/blob/master/src/components/Button/Button.tsx">Button.tsx,</a> <a href="https://github.com/prateek3255/my-awesome-component-library/blob/master/src/components/Button/button.css">button.css</a>, and <a href="https://github.com/prateek3255/my-awesome-component-library/blob/master/src/components/Button/index.ts">index.ts</a> files in it.</p>
<p>Lets add some stories ✨</p>
<p>Create <code>src/components/Button/Button.stories.tsx</code></p>
<p>Now add the following default export to it -</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> Meta<span class="token punctuation">,</span> StoryObj <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@storybook/react"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> Button <span class="token keyword">from</span> <span class="token string">"./Button"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> meta<span class="token operator">:</span> Meta<span class="token operator"><</span><span class="token keyword">typeof</span> Button<span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span><br /> title<span class="token operator">:</span> <span class="token string">"Components/Button"</span><span class="token punctuation">,</span><br /> component<span class="token operator">:</span> Button<span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Storybook uses the <a href="https://github.com/ComponentDriven/csf">Component Story Format (CSF)</a> which is an open standard for UI component examples based on JavaScript ES6 modules.</p>
<p>The <a href="https://storybook.js.org/docs/react/writing-stories/introduction#default-export">default export in a story</a> defines the metadata that controls how Storybook lists your stories. It mainly includes the title, the component that the story will render and other options that can be used to further customize the story.</p>
<p>To define a Story you <a href="https://storybook.js.org/docs/react/writing-stories/introduction#defining-stories">need to create named exports</a> in the file. These named exports are objects that represent a single story of your component and can override certain metadata defined in the default export. For example, we can create a story for the Primary state of button by adding the following code to the <code>Button.stories.tsx</code> file:</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token keyword">type</span> <span class="token class-name">Story</span> <span class="token operator">=</span> StoryObj<span class="token operator"><</span><span class="token keyword">typeof</span> Button<span class="token operator">></span><span class="token punctuation">;</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">const</span> Primary<span class="token operator">:</span> Story <span class="token operator">=</span> <span class="token punctuation">{</span><br /> args<span class="token operator">:</span> <span class="token punctuation">{</span><br /> label<span class="token operator">:</span> <span class="token string">"Primary 😃"</span><span class="token punctuation">,</span><br /> size<span class="token operator">:</span> <span class="token string">"large"</span><span class="token punctuation">,</span><br /> type<span class="token operator">:</span> <span class="token string">"primary"</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></code></pre>
<p>Here the <code>args</code> object represent the default props that will be passed to our <code>Button</code> component when the story is rendered, although these props are also configurable by Storybook controls which we will get to in a minute.</p>
<p>Similarly you can create a story for the secondary state for the <code>Button</code> component that will have secondary variant as the default when the story is rendered. Add the following code to the <code>Button.stories.tsx</code> file to test it out yourself:</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token keyword">export</span> <span class="token keyword">const</span> Secondary<span class="token operator">:</span> Story <span class="token operator">=</span> <span class="token punctuation">{</span><br /> args<span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span>Primary<span class="token punctuation">.</span>args<span class="token punctuation">,</span><br /> type<span class="token operator">:</span> <span class="token string">"secondary"</span><span class="token punctuation">,</span><br /> label<span class="token operator">:</span> <span class="token string">"Secondary 😇"</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></code></pre>
<p>If you haven't already, you can restart the Storybook server by rerunning <code>yarn storybook</code>, and you should see the following.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/initial-story-setup-480.webp 480w, https://prateeksurana.me/img/initial-story-setup-768.webp 768w, https://prateeksurana.me/img/initial-story-setup-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/initial-story-setup-480.jpeg 480w, https://prateeksurana.me/img/initial-story-setup-768.jpeg 768w, https://prateeksurana.me/img/initial-story-setup-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/initial-story-setup-768.jpeg" width="768" height="408" alt="Initial Story Setup" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Notice that Storybook automatically generated the controls, according to the component props, for us. This is thanks to <a href="https://storybook.js.org/docs/react/api/argtypes#automatic-argtype-inference">react-docgen-typescript, which is used by Storybook to infer the argTypes for a component</a>. One more reason to use TypeScript.</p>
<p>Apart from using auto-generated controls, you can also define <a href="https://storybook.js.org/docs/react/essentials/controls#configuration">custom controls</a> for some or all props using the <code>argTypes</code> key. For example, let's define a custom color picker for the <code>textColor</code> prop. Add the <code>argTypes</code> key to the default metadata export in the <code>Button.stories.tsx</code> file:</p>
<pre class="language-tsx"><code class="language-tsx"><span class="highlight-line"><span class="token keyword">const</span> meta<span class="token operator">:</span> Meta<span class="token operator"><</span><span class="token keyword">typeof</span> Button<span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span></span><br /><span class="highlight-line"> title<span class="token operator">:</span> <span class="token string">"Components/Button"</span><span class="token punctuation">,</span></span><br /><span class="highlight-line"> component<span class="token operator">:</span> Button<span class="token punctuation">,</span></span><br /><mark class="highlight-line highlight-line-active"> argTypes<span class="token operator">:</span> <span class="token punctuation">{</span></mark><br /><mark class="highlight-line highlight-line-active"> textColor<span class="token operator">:</span> <span class="token punctuation">{</span> control<span class="token operator">:</span> <span class="token string">"color"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token punctuation">}</span><span class="token punctuation">,</span></mark><br /><span class="highlight-line"><span class="token punctuation">}</span><span class="token punctuation">;</span></span></code></pre>
<p>The current story preview also looks a bit weird with the button in one corner of the preview. As one last step, <a href="https://storybook.js.org/docs/react/configure/story-layout">add the <code>layout: 'centered'</code> key</a> to the <code>.storybook/preview.js</code> file to center the preview. This file lets you control how your <a href="https://storybook.js.org/docs/react/configure/overview#configure-story-rendering">story is rendered in the Storybook.</a></p>
<p>If you followed the above steps, your final story preview would look something like this -</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/final-story-setup-480.webp 480w, https://prateeksurana.me/img/final-story-setup-768.webp 768w, https://prateeksurana.me/img/final-story-setup-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/final-story-setup-480.jpeg 480w, https://prateeksurana.me/img/final-story-setup-768.jpeg 768w, https://prateeksurana.me/img/final-story-setup-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/final-story-setup-768.jpeg" width="768" height="408" alt="Final story setup" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>There's a lot more configuration you can do with your stories, like customizing <a href="https://storybook.js.org/docs/react/writing-stories/parameters">story parameters</a>, <a href="https://storybook.js.org/docs/react/writing-stories/decorators">using decorators</a>, <a href="https://storybook.js.org/docs/react/essentials/introduction">extend your Storbook UI behaviour with addons</a>, etc. But that would be out of scope of this tutorial and is covered in much more detail in the <a href="https://storybook.js.org/docs/react/writing-stories/introduction">official Storybook documentation</a>.</p>
<h2 class="relative">
<a id="compiling-the-library-using-rollup" href="https://prateeksurana.me/blog/react-component-library-using-storybook-7/#compiling-the-library-using-rollup" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Compiling the Library using Rollup
</a>
</h2>
<p>Now that you know how to build components with Storybook, it's time to move to the next step, which is compiling and bundling our library so that our end applications can consume it.</p>
<p>We will be using Rollup to bundle the library. If you're not familiar with Rollup I would recommend checking out this article by Rich on <a href="https://medium.com/webpack/webpack-and-rollup-the-same-but-different-a41ad427058c">why its better to use Rollup for libraries and Webpack for apps</a>.</p>
<p>First, we would need to create an entry file that would export all the components for our component library. Create <code>src/index.ts</code>, and since our component library only has one component right now, it would look something like this -</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token keyword">import</span> Button <span class="token keyword">from</span> <span class="token string">"./components/Button"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">export</span> <span class="token punctuation">{</span> Button <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Let's add rollup, run the following to install Rollup and its plugins that we'll be using to bundle the library -</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">yarn</span> <span class="token function">add</span> --dev rollup @rollup/plugin-typescript @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup-plugin-peer-deps-external rollup-plugin-postcss rollup-plugin-dts postcss</code></pre>
<p>Now before we add the rollup config, there are a few types of JavaScript modules that you should be aware of -</p>
<ul>
<li><a href="https://nodejs.org/docs/latest/api/modules.html#modules_modules_commonjs_modules">CommonJS</a> - This module format is most commonly used with Node using the <code>require</code> function. Even though we are publishing a React module (which will be consumed by an application generally written in ESM format, then bundled and compiled by tools like webpack), we need to consider that it might also be used within a Server side rendering environment, which generally uses Node and hence might require a CJS counterpart of the library (ESM modules are supported in Node environment as of <a href="https://nodejs.org/dist./v10.22.0/docs/api/esm.html">v10 behind an experimental flag</a>).</li>
<li><a href="https://nodejs.org/api/esm.html#esm_modules_ecmascript_modules">ESM</a> - This is the modern module format that we normally use in our React applications in which modules are defined using a variety of import and export statements. The main benefit of shipping ES modules is that it <a href="https://bitsofco.de/what-is-tree-shaking/">makes your library tree-shakable</a>. This is supported by tools like Rollup and webpack 2+.</li>
<li><a href="https://riptutorial.com/javascript/example/16339/universal-module-definition">UMD</a> - This module format is not as popular these days. It is required when the user requires our module using a script tag.</li>
</ul>
<p>So we would want to support both ESM and CommonJS modules for our component library so that all kinds of support tools can use it in the end application that relies on either of the module types.</p>
<p>To do that, <code>package.json</code> allows adding the entry points for both ESM and CommonJS modules via the module and main key, respectively. So add the following to keys to your <code>package.json</code> -</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> ...<br /> <span class="token property">"main"</span><span class="token operator">:</span> <span class="token string">"lib/index.js"</span><span class="token punctuation">,</span><br /> <span class="token property">"module"</span><span class="token operator">:</span> <span class="token string">"lib/index.esm.js"</span><span class="token punctuation">,</span><br /> <span class="token property">"types"</span><span class="token operator">:</span> <span class="token string">"lib/index.d.ts"</span><span class="token punctuation">,</span><br /> ...<br /><span class="token punctuation">}</span></code></pre>
<p>The types key would point to the static types generated for your library via Rollup, which would help with <a href="https://code.visualstudio.com/docs/languages/typescript#_intellisense">IntelliSense in code editors like VSCode.</a></p>
<p>Its time to add the Rollup config file now, create a file called <code>rollup.config.mjs</code> in the root folder and add the following to it -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> peerDepsExternal <span class="token keyword">from</span> <span class="token string">"rollup-plugin-peer-deps-external"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> resolve <span class="token keyword">from</span> <span class="token string">"@rollup/plugin-node-resolve"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> commonjs <span class="token keyword">from</span> <span class="token string">"@rollup/plugin-commonjs"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> typescript <span class="token keyword">from</span> <span class="token string">"@rollup/plugin-typescript"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> postcss <span class="token keyword">from</span> <span class="token string">"rollup-plugin-postcss"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> dts <span class="token keyword">from</span> <span class="token string">"rollup-plugin-dts"</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// This is required to read package.json file when</span><br /><span class="token comment">// using Native ES modules in Node.js</span><br /><span class="token comment">// https://rollupjs.org/command-line-interface/#importing-package-json</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> createRequire <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'node:module'</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> requireFile <span class="token operator">=</span> <span class="token function">createRequire</span><span class="token punctuation">(</span><span class="token keyword">import</span><span class="token punctuation">.</span>meta<span class="token punctuation">.</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> packageJson <span class="token operator">=</span> <span class="token function">requireFile</span><span class="token punctuation">(</span><span class="token string">'./package.json'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">[</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">input</span><span class="token operator">:</span> <span class="token string">"src/index.ts"</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">output</span><span class="token operator">:</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">{</span><br /> <span class="token literal-property property">file</span><span class="token operator">:</span> packageJson<span class="token punctuation">.</span>main<span class="token punctuation">,</span><br /> <span class="token literal-property property">format</span><span class="token operator">:</span> <span class="token string">"cjs"</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">sourcemap</span><span class="token operator">:</span> <span class="token boolean">true</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token punctuation">{</span><br /> <span class="token literal-property property">file</span><span class="token operator">:</span> packageJson<span class="token punctuation">.</span>module<span class="token punctuation">,</span><br /> <span class="token literal-property property">format</span><span class="token operator">:</span> <span class="token string">"esm"</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">sourcemap</span><span class="token operator">:</span> <span class="token boolean">true</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">plugins</span><span class="token operator">:</span> <span class="token punctuation">[</span><br /> <span class="token function">peerDepsExternal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <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 function">commonjs</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token function">typescript</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token function">postcss</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">extensions</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'.css'</span><span class="token punctuation">]</span><br /> <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><br /> <span class="token literal-property property">input</span><span class="token operator">:</span> <span class="token string">'lib/index.d.ts'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">output</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">{</span> <span class="token literal-property property">file</span><span class="token operator">:</span> <span class="token string">'lib/index.d.ts'</span><span class="token punctuation">,</span> <span class="token literal-property property">format</span><span class="token operator">:</span> <span class="token string">'es'</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">plugins</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token function">dts</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 literal-property property">external</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\.css$</span><span class="token regex-delimiter">/</span></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>Let's break it down one by one to figure out what's happening here. We are export an array from the config file which contains to objects, each with their input and output targets.</p>
<p>Starting with the first one, the <a href="https://rollupjs.org/guide/en/#input">input key</a> indicates the entry point for Rollup for our component library, which is the <code>index.ts</code> file that we just created, which contains the exports for all our components.</p>
<p>The output key indicates what types of output files will be generated at which place. As mentioned previously, we would be building the ESM and CommonJS bundles, and we read the output file paths for both bundles from the <code>package.json</code>.</p>
<p>The second object is for bundling type declarations and is discussed in the plugins section below.</p>
<p>Lastly there is the plugin array with which we are using the following plugins -</p>
<ul>
<li><a href="https://www.npmjs.com/package/rollup-plugin-peer-deps-external">rollup-plugin-peer-deps-external</a> - This plugin avoids us from bundling the <code>peerDependencies</code> (<code>react</code> and <code>react-dom</code> in our case) in the final bundle as these will be provided by our consumer application.</li>
<li><a href="https://www.npmjs.com/package/@rollup/plugin-node-resolve">@rollup/plugin-node-resolve</a> - This plugin includes the third-party external dependencies into our final bundle (we don't have any dependencies for this tutorial, but you'll definitely need them as your library grows).</li>
<li><a href="https://www.npmjs.com/package/@rollup/plugin-commonjs">@rollup/plugin-commonjs</a> - This plugin allows the rollup plugin to convert CommonJS modules to ESModules so that Rollup can bundle them together with other ES modules.</li>
<li><a href="https://www.npmjs.com/package/rollup-plugin-typescript2">@rollup/plugin-typescript</a> - This plugin compiles the TypeScript code to JavaScript for our final bundle and generates the type declarations for the <code>types</code> key in <code>package.json</code>.</li>
<li><a href="https://www.npmjs.com/package/rollup-plugin-postcss">rollup-plugin-postcss</a> - This plugin helps include the CSS that we created as separate files in our final bundle. It does this by generating minified CSS from the *.css files and includes them via the <code><head></code> tag wherever used in our components.</li>
<li><a href="https://www.npmjs.com/package/rollup-plugin-dts">rollup-plugin-dts</a> - This plugin bundles all the type declarations into a single file. Although the TypeScript plugin generates the type declarations for our library but they are scattered across the files. This plugin helps us bundle them into a single file. We use the type declarations generated by the TypeScript plugin as the input for this plugin and override the declaration file.</li>
</ul>
<p>Now as one last step let's add the script to build our component library, add the following script to your <code>package.json</code> file -</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> ...<br /> <span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> ...<br /> <span class="token property">"build"</span><span class="token operator">:</span> <span class="token string">"rollup -c"</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> ...<br /><span class="token punctuation">}</span></code></pre>
<p>Go ahead and run <code>yarn build</code> from your terminal and you should be able to see the <code>lib</code> folder created. I would recommend exploring this folder further to understand how Rollup and its plugins generate the appropriate bundles for the CommonJS and ESM modules with the type definitions.</p>
<p>Don't forget to add the <code>lib</code> folder to <code>.gitignore</code>.</p>
<h2 class="relative">
<a id="publishing-and-consuming-the-library" href="https://prateeksurana.me/blog/react-component-library-using-storybook-7/#publishing-and-consuming-the-library" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Publishing and consuming the library
</a>
</h2>
<p>Publishing the library to NPM couldn't be any easier. Since we have already defined all the required fields in <code>package.json</code>, you just need to run <code>npm publish</code>.</p>
<p>Once published, you should be able to import your component from your library in the consumer application just like this -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> <span class="token punctuation">{</span> Button <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"my-awesome-component-library"</span><span class="token punctuation">;</span></code></pre>
<p>You can also refer to my <a href="https://prateeksurana.me/blog/react-library-with-typescript/#getting-ready-for-publishing">another article for the detailed steps and best practices for publishing a library to NPM.</a></p>
<p>You might also want to keep your library private. If you have multiple projects in a <a href="https://www.toptal.com/front-end/guide-to-monorepos">monorepo</a> and are using something like <a href="https://classic.yarnpkg.com/en/docs/workspaces/">yarn workspaces</a>, then you don't actually need to publish the package anywhere.</p>
<p>Place the library folder in your monorepo and add it to your workspaces array to the package.json in the root folder -</p>
<pre class="language-json"><code class="language-json"><span class="token comment">// package.json</span><br /><span class="token punctuation">{</span><br /> ...<br /> <span class="token property">"workspaces"</span><span class="token operator">:</span> <span class="token punctuation">[</span><br /> ...<br /> <span class="token string">"my-awesome-component-library"</span><br /> <span class="token punctuation">]</span><span class="token punctuation">,</span><br /> ...<br /><span class="token punctuation">}</span></code></pre>
<p>Then you can directly access it from any other package in your workspace by just adding it as an dependency:</p>
<pre class="language-json"><code class="language-json"><span class="token comment">// my-awesome-frontend/package.json</span><br /><span class="token punctuation">{</span><br /> ...<br /> <span class="token property">"dependencies"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> ...<br /> <span class="token property">"my-awesome-component-library"</span><span class="token operator">:</span> <span class="token number">1.0</span>.<span class="token number">0</span><span class="token punctuation">,</span><br /> ...<br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> ...<br /><span class="token punctuation">}</span></code></pre>
<p>And that's it. You now have a component library which you can build in isolation and preview using storybook and consume in your application as a package. You can find all the code for this tutorial on <a href="https://github.com/prateek3255/my-awesome-component-library">Github</a>.</p>
<h2>Next Steps</h2>
<ul>
<li>Integrate <a href="https://www.netlify.com/">Netlify</a> or some other service to automatically deploy the Storybook whenever a PR is merged into master and to generate pull previews whenever a new PR is opened.</li>
<li>Setup test cases <a href="https://www.pluralsight.com/guides/how-to-test-react-components-in-typescript">using React Testing library and Jest</a>.</li>
<li><a href="https://blog.theodo.com/2021/04/library-tree-shaking/">Make the library tree-shakeable</a> so that your consumer application only ends up including the components that are used in the application instead of the entire library.</li>
</ul>
Integrating reCAPTCHA with Next.jsCheck out how you can take advantage of Next.js' API routes to get the most out of CAPTCHA solutions like reCAPTCHA and hCaptcha.2021-01-17T00:00:00Z2021-01-17T00:00:00Zhttps://prateeksurana.me/blog/integrating-recaptcha-with-next/<p>In this post on integrating reCAPTCHA with Next.js, we will be looking at what is a CAPTCHA, how does it work and why you might need it. Then we'll work on a demo to illustrate how you can take advantage of Next.js features to integrate it nicely with your website.</p>
<p>So you must've probably seen this before, but have you ever wondered what it does?</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/captcha-intro-480.webp 480w, https://prateeksurana.me/img/captcha-intro-768.webp 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/captcha-intro-480.jpeg 480w, https://prateeksurana.me/img/captcha-intro-768.jpeg 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/captcha-intro-768.jpeg" width="768" height="1114" alt="A regular reCAPTCHA" class="max-w-full lg:max-w-md mx-auto" loading="lazy" decoding="async" />
</picture>
<p>A CAPTCHA is a <a href="https://en.wikipedia.org/wiki/Turing_test">Turing test</a> designed to tell humans and bots apart and is generally used by websites to prevent spam and abuse. It uses a challenge that is easy for humans but hard for bots.</p>
<p><a href="https://www.google.com/recaptcha/about/">reCAPTCHA</a> is a CAPTCHA system currently being maintained by Google. The currently maintained versions are v2, which uses an analysis of cookies, canvas rendering, and user behavior to decide whether to show a challenge or not, and v3, which does not interrupt the users at all.</p>
<p>To get the full benefits of reCAPTCHA, you need to verify the captcha response code in the server to verify its validity. With Next.js, this could have never been easier since it easily lets you spin up a serverless function (if you're deploying it via Vercel) just by adding an <a href="https://nextjs.org/docs/api-routes/introduction">API route in the <code>/pages/api/</code> folder</a>.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>This post assumes you are familiar with the basics of React and Next.js. If not, I would recommend checking out the <a href="https://reactjs.org/tutorial/tutorial.html">Intro to React Tutorial by the React team</a> and the <a href="https://nextjs.org/learn/basics/create-nextjs-app">Next.js tutorial from the docs</a>.</p>
</aside>
<p>reCAPTCHA, though more famous than any other solutions out there but is <a href="https://www.fastcompany.com/90369697/googles-new-recaptcha-has-a-dark-side">infamous for its privacy-related concerns</a>. So if you are concerned about your user's privacy, we will also be looking at a privacy-friendly alternative to reCAPTCHA called <a href="https://hcaptcha.com/">hCaptcha</a> later in this post.</p>
<p>We will cover this with the following steps -</p>
<ol>
<li><a href="https://prateeksurana.me/blog/integrating-recaptcha-with-next/#why-you-need-to-use-recaptcha-and-how-does-it-work">Why you need to use reCAPTCHA and how does it work</a></li>
<li><a href="https://prateeksurana.me/blog/integrating-recaptcha-with-next/#setting-up-the-project">Setting up the project</a></li>
<li><a href="https://prateeksurana.me/blog/integrating-recaptcha-with-next/#adding-recaptcha-to-the-frontend">Adding reCAPTCHA to the frontend</a></li>
<li><a href="https://prateeksurana.me/blog/integrating-recaptcha-with-next/#verifying-captcha-via-nextjs-api-routes">Verifying captcha via Next.js' API routes</a></li>
<li><a href="https://prateeksurana.me/blog/integrating-recaptcha-with-next/#bonus-integrating-hcaptcha-and-why-you-might-need-it">Bonus: Integrating hCaptcha and why you might need it</a></li>
</ol>
<h2 class="relative">
<a id="why-you-need-to-use-recaptcha-and-how-does-it-work" href="https://prateeksurana.me/blog/integrating-recaptcha-with-next/#why-you-need-to-use-recaptcha-and-how-does-it-work" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Why you need to use reCAPTCHA and how does it work
</a>
</h2>
<p>Before we dive into integrating reCAPTCHA, let's take a moment to understand why you need it and how does it solve your problems.</p>
<p>If you have a public-facing page with a form that sends the data to your backend server, then adding reCAPTCHA can help you to prevent spammers/bots from flooding your form and thus polluting your database or prevent something like brute force password guessing attack on a login page. Although reCAPTCHA is not the only way to prevent such malicious requests, there are other ways you can <a href="https://elasticemail.com/blog/marketing_tips/how-to-prevent-bots-from-spamming-your-sign-up-forms">prevent spam without disturbing your users</a>. Still, reCAPTCHA is really smart and only shows a challenge if your user fails its cookie and behavior analysis.</p>
<p>The way it works is as soon as the user submits the form, you execute the reCAPTCHA instead of sending the data directly to your backend. In turn, reCAPTCHA provides you a callback for both success and failure, which will be executed if the user passes or fails the reCAPTCHA, respectively.</p>
<p>Now this will prevent your frontend from malicious attacks. However, your backend APIs might still be insecure (assuming you are not using any other kind of protection, e.g., <a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html">CSRF tokens</a>) because anyone can open the network tab to check the APIs getting pinged and run a script to ping the API with spam data. Thankfully reCAPTCHA provides a solution for that as well. When a user successfully clears the reCAPTCHA, you are provided with a token that is valid for 2 minutes. You can now validate this token in your backend with a secret key to verify the request's authenticity.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/recaptcha-working-480.webp 480w, https://prateeksurana.me/img/recaptcha-working-768.webp 768w, https://prateeksurana.me/img/recaptcha-working-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/recaptcha-working-480.jpeg 480w, https://prateeksurana.me/img/recaptcha-working-768.jpeg 768w, https://prateeksurana.me/img/recaptcha-working-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/recaptcha-working-768.jpeg" width="768" height="432" alt="Working of reCAPTCHA" class="article-img" loading="lazy" decoding="async" />
</picture>
<h2 class="relative">
<a id="setting-up-the-project" href="https://prateeksurana.me/blog/integrating-recaptcha-with-next/#setting-up-the-project" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Setting up the project
</a>
</h2>
<p>I will be using a plain starter built using <a href="https://www.npmjs.com/package/create-next-app"><code>create-next-app</code></a> with a simple form. If you want to follow along, you can get the initial state from <a href="https://github.com/prateek3255/recaptch-with-next/tree/4be05a0163a2629b88b6bf8dc6863c9bb29da2a2">this commit</a>. The initial setup looks like this, and it just shows your email in an alert when you click on register</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/initial-recaptcha-app-480.webp 480w, https://prateeksurana.me/img/initial-recaptcha-app-768.webp 768w, https://prateeksurana.me/img/initial-recaptcha-app-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/initial-recaptcha-app-480.jpeg 480w, https://prateeksurana.me/img/initial-recaptcha-app-768.jpeg 768w, https://prateeksurana.me/img/initial-recaptcha-app-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/initial-recaptcha-app-768.jpeg" width="768" height="505" alt="Inital Next.js app" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Let's register a new project on reCAPTCHA and get the required keys. For that, you can go to the <a href="https://www.google.com/recaptcha/admin/create">reCAPTCHA admin console</a>, fill in the required details as mentioned below, and click on submit.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>For this post's scope, we will be focusing on the <a href="https://developers.google.com/recaptcha/docs/versions#recaptcha_v2_invisible_recaptcha_badge">v2 Invisible reCAPTCHA</a> which does not involves the reCAPTCHA checkbox. It invokes the prompt to solve captcha for most suspicious traffic via a JavaScript API call and will directly tell us if the user has passed or not.</p>
<p>v3 reCAPTCHA, though, is more advanced and doesn't require users to solve any challenge, but only provides a score between 0 and 1, requiring you to take action in the context of your site: for instance, requiring additional factors of authentication, sending a post to moderation, or throttling bots that may be scraping content.</p>
</aside>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/recaptcha-registration-480.webp 480w, https://prateeksurana.me/img/recaptcha-registration-768.webp 768w, https://prateeksurana.me/img/recaptcha-registration-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/recaptcha-registration-480.jpeg 480w, https://prateeksurana.me/img/recaptcha-registration-768.jpeg 768w, https://prateeksurana.me/img/recaptcha-registration-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/recaptcha-registration-768.jpeg" width="768" height="667" alt="Registering your reCAPTCHA site" class="mt-5" loading="lazy" decoding="async" />
</picture>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>Notice how I added the only localhost to the list of domains. That's because we would only be using these keys for development purposes. reCAPTCHA also does a domain validation for the site it is being executed on, so we would be creating a separate set of keys for the production environment to only be used on the production domain and not be misused.</p>
</aside>
<p>After clicking submit, you should be able to see the public and secret keys.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/recaptcha-keys-480.webp 480w, https://prateeksurana.me/img/recaptcha-keys-768.webp 768w, https://prateeksurana.me/img/recaptcha-keys-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/recaptcha-keys-480.jpeg 480w, https://prateeksurana.me/img/recaptcha-keys-768.jpeg 768w, https://prateeksurana.me/img/recaptcha-keys-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/recaptcha-keys-768.jpeg" width="768" height="392" alt="reCAPTCHA keys" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>To have separate keys for production and development environments and avoid pushing these keys to version control, we would store these keys in the environment variables. Unlike typical react app setups where you would need to manually <a href="https://prateeksurana.me/blog/using-environment-variables-with-webpack/">setup environment variables manually via Webpack plugins</a>, Next.js comes with <a href="https://nextjs.org/docs/basic-features/environment-variables">built-in support for environment variables</a>. For the development environment, create a file called <code>.env.local</code> and add the following to it, and paste the keys you copied from the reCAPTCHA dashboard here appropriately.</p>
<pre class="language-bash"><code class="language-bash"><span class="token comment"># Add the public site key here</span><br /><span class="token assign-left variable">NEXT_PUBLIC_RECAPTCHA_SITE_KEY</span><span class="token operator">=</span><br /><span class="token comment"># Add the secret key here</span><br /><span class="token assign-left variable">RECAPTCHA_SECRET_KEY</span><span class="token operator">=</span></code></pre>
<p>You can use different environment keys for production with the proper domains added, either using <code>.env.production.local</code> or adding the production environment variables to the tool (e.g., Vercel) you are using to deploy your app.</p>
<p><a href="https://github.com/prateek3255/recaptch-with-next/tree/4be05a0163a2629b88b6bf8dc6863c9bb29da2a2">👨🏻💻 Code till this step</a></p>
<h2 class="relative">
<a id="adding-recaptcha-to-the-frontend" href="https://prateeksurana.me/blog/integrating-recaptcha-with-next/#adding-recaptcha-to-the-frontend" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Adding reCAPTCHA to the frontend
</a>
</h2>
<p>We need the public site key to be available to the client. Adding the <code>NEXT_PUBLIC_</code> suffix to the environment variable would <a href="https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser">make it visible to the browser</a>. The <code>RECAPTCHA_SECRET_KEY</code> environment variable would only be available on the server.</p>
<p>We would be using a library called <a href="https://github.com/dozoisch/react-google-recaptcha"><code>react-google-recaptcha</code></a>, a wrapper around reCAPTCHA v2 that provides access to its APIs via a React component. Let's install it -</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">yarn</span> <span class="token function">add</span> react-google-recaptcha</code></pre>
<p>Since we are using the v2 invisible reCAPTCHA, we would be executing it when we submit the form via a <a href="https://reactjs.org/docs/hooks-reference.html#useref">React ref</a>. Import the ReCAPTCHA component and place it in the <code>pages/index.js</code> file, like this -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> Head <span class="token keyword">from</span> <span class="token string">"next/head"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> ReCAPTCHA <span class="token keyword">from</span> <span class="token string">"react-google-recaptcha"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Home</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>email<span class="token punctuation">,</span> setEmail<span class="token punctuation">]</span> <span class="token operator">=</span> React<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><br /> <span class="token keyword">const</span> recaptchaRef <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createRef</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 /> <span class="token punctuation">.</span><br /> <span class="token punctuation">.</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>form</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">ReCAPTCHA</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>recaptchaRef<span class="token punctuation">}</span></span><br /> <span class="token attr-name">size</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>invisible<span class="token punctuation">"</span></span><br /> <span class="token attr-name">sitekey</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NEXT_PUBLIC_RECAPTCHA_SITE_KEY</span><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>onReCAPTCHAChange<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>input</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>handleChange<span class="token punctuation">}</span></span><br /> <span class="token attr-name">required</span><br /> <span class="token attr-name">type</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><br /> <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><br /> <span class="token attr-name">placeholder</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><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>button</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">Register</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>form</span><span class="token punctuation">></span></span><br /> <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></code></pre>
<p>For the <code>siteKey</code> we are using the environment variable that we created in the last step.</p>
<p>We now need to execute the reCAPTCHA when submitting the form and do what we want when our form is submitted in the <code>ReCAPTCHA</code> component's <code>onChange</code> handler when the captcha is completed. So let's modify the <code>handleSubmit</code> function and define the <code>onReCAPTCHAChange</code> function accordingly in our component -</p>
<pre class="language-jsx"><code class="language-jsx"><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">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> event<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 /> <span class="token comment">// Execute the reCAPTCHA when the form is submitted</span><br /> recaptchaRef<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">execute</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 /><br /><span class="token keyword">const</span> <span class="token function-variable function">onReCAPTCHAChange</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">captchaCode</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// If the reCAPTCHA code is null or undefined indicating that</span><br /> <span class="token comment">// the reCAPTCHA was expired then return early</span><br /> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>captchaCode<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 punctuation">}</span><br /> <span class="token comment">// Else reCAPTCHA was executed successfully so proceed with the </span><br /> <span class="token comment">// alert</span><br /> <span class="token function">alert</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hey, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>email<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 comment">// Reset the reCAPTCHA so that it can be executed again if user </span><br /> <span class="token comment">// submits another email.</span><br /> recaptchaRef<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">reset</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>When you restart the server with <code>yarn dev</code>, if the integration was successful you should see the reCAPTCHA badge at the bottom right corner. And you would be only able to see the alert if you pass the reCAPTCHA.</p>
<p>Note that if a challenge is not being shown to you, it doesn't necessarily mean that there is something wrong with the integration. As I mentioned earlier, reCAPTCHA only shows a challenge if you fail its behavior or cookie analysis. If you still want to see the challenge anyways, you can open the tab in incognito and update the security preference to most secure from the reCAPTCHA admin dashboard.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/recaptcha-security-preference-480.webp 480w, https://prateeksurana.me/img/recaptcha-security-preference-768.webp 768w, https://prateeksurana.me/img/recaptcha-security-preference-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/recaptcha-security-preference-480.jpeg 480w, https://prateeksurana.me/img/recaptcha-security-preference-768.jpeg 768w, https://prateeksurana.me/img/recaptcha-security-preference-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/recaptcha-security-preference-768.jpeg" width="768" height="154" alt="reCAPTCHA security preference" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>You should be able to see the challenge after submitting a form couple of times in a row.</p>
<p><img src="https://prateeksurana.me/img/recaptcha-in-action.gif" width="768" height="437" alt="reCAPTCHA in action" class="article-img" loading="lazy" decoding="async" /></p>
<p><a href="https://github.com/prateek3255/recaptch-with-next/tree/14f229c3a9567938c2be1181517c48b29fc101cc">👨🏻💻 Code till this step</a></p>
<h2 class="relative">
<a id="verifying-captcha-via-nextjs-api-routes" href="https://prateeksurana.me/blog/integrating-recaptcha-with-next/#verifying-captcha-via-nextjs-api-routes" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Verifying captcha via Next.js' API routes
</a>
</h2>
<p>Likely, you don't want to show your user's info in an alert box when he submits your form. You might want to store that info somewhere in your backend instead or provide an appropriate response to the user in case of a login form. For that, we can replace the code that shows the alert with an API call that saves the info the user entered to your backend because we have already added the reCAPTCHA that would prevent any bot or spammers, right?</p>
<p>Well, not really. As I mentioned in the beginning if you're not using any protection for your API and since the API is most probably open, someone can still run a simple script that continuously pings your API with garbage data polluting your database.</p>
<p>Don't worry Next.js and reCAPTCHA have you covered.</p>
<p>Remember the reCAPTCHA token you received in the <code>onReCAPTCHAChange</code> function. That token can be used to verify whether the request you just received is legitimate or not. Google provides an <a href="https://developers.google.com/recaptcha/docs/verify">API for verifying that token</a> in your server via the secret key. The token is valid only for 2 minutes and can only be verified once to prevent any replay attacks.</p>
<p>So do you need to update your API route that saves the user details or create a new server that would handle the verification if you're relying on some third party API?</p>
<p>This is where <a href="https://nextjs.org/docs/api-routes/introduction">Next.js' API routes</a> come in. If you're using Vercel for deployment, it spins up a serverless function whenever you create a new API route.</p>
<p>For our demo, we need an API endpoint that accepts the email and the captcha token and saves the email to the database if the token is valid, and returns an error if it is bogus.</p>
<p>Let's create our API route, create a file called <code>pages/api/register.js</code> and paste the following in it -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// pages/api/register.js</span><br /><span class="token keyword">import</span> fetch <span class="token keyword">from</span> <span class="token string">"node-fetch"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> <span class="token function-variable function">sleep</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">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">resolve</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><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">resolve</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 number">350</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 keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">handler</span><span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> body<span class="token punctuation">,</span> method <span class="token punctuation">}</span> <span class="token operator">=</span> req<span class="token punctuation">;</span><br /><br /> <span class="token comment">// Extract the email and captcha code from the request body</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> email<span class="token punctuation">,</span> captcha <span class="token punctuation">}</span> <span class="token operator">=</span> body<span class="token punctuation">;</span><br /><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>method <span class="token operator">===</span> <span class="token string">"POST"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// If email or captcha are missing return an error</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>email <span class="token operator">||</span> <span class="token operator">!</span>captcha<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">422</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">message</span><span class="token operator">:</span> <span class="token string">"Unproccesable request, please provide the required fields"</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 keyword">try</span> <span class="token punctuation">{</span><br /> <span class="token comment">// Ping the google recaptcha verify API to verify the captcha code you received</span><br /> <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><br /> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://www.google.com/recaptcha/api/siteverify?secret=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">RECAPTCHA_SECRET_KEY</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&response=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>captcha<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><br /> <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token string-property property">"Content-Type"</span><span class="token operator">:</span> <span class="token string">"application/x-www-form-urlencoded; charset=utf-8"</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">"POST"</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 keyword">const</span> captchaValidation <span class="token operator">=</span> <span class="token keyword">await</span> response<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 comment">/**<br /> * The structure of response from the veirfy API is<br /> * {<br /> * "success": true|false,<br /> * "challenge_ts": timestamp, // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)<br /> * "hostname": string, // the hostname of the site where the reCAPTCHA was solved<br /> * "error-codes": [...] // optional<br /> }<br /> */</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>captchaValidation<span class="token punctuation">.</span>success<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// Replace this with the API that will save the data received</span><br /> <span class="token comment">// to your backend</span><br /> <span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Return 200 if everything is successful</span><br /> <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">"OK"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">422</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">message</span><span class="token operator">:</span> <span class="token string">"Unproccesable request, Invalid captcha code"</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 keyword">catch</span> <span class="token punctuation">(</span>error<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>error<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">422</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">message</span><span class="token operator">:</span> <span class="token string">"Something went wrong"</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 /> <span class="token comment">// Return 404 if someone pings the API with a method other than</span><br /> <span class="token comment">// POST</span><br /> <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">404</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">"Not found"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>For simplicity, I have installed a package called <a href="https://www.npmjs.com/package/node-fetch"><code>node-fetch</code></a>, which is a light-weight wrapper that provides the <code>window.fetch</code> like API in Node environment.</p>
<p>Now let's integrate this API on the client. Update the <code>onReCAPTCHAChange</code> function in the <code>pages/index.js</code> with the following snippet -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">onReCAPTCHAChange</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">captchaCode</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// If the reCAPTCHA code is null or undefined indicating that</span><br /> <span class="token comment">// the reCAPTCHA was expired then return early</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>captchaCode<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 punctuation">}</span><br /> <span class="token keyword">try</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/register"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">"POST"</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span><span class="token punctuation">{</span> email<span class="token punctuation">,</span> <span class="token literal-property property">captcha</span><span class="token operator">:</span> captchaCode <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token string-property property">"Content-Type"</span><span class="token operator">:</span> <span class="token string">"application/json"</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><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>response<span class="token punctuation">.</span>ok<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// If the response is ok than show the success alert</span><br /> <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">"Email registered successfully"</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 /> <span class="token comment">// Else throw an error with the message returned</span><br /> <span class="token comment">// from the API</span><br /> <span class="token keyword">const</span> error <span class="token operator">=</span> <span class="token keyword">await</span> response<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 keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span>error<span class="token punctuation">.</span>message<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">alert</span><span class="token punctuation">(</span>error<span class="token operator">?.</span>message <span class="token operator">||</span> <span class="token string">"Something went wrong"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span><br /> <span class="token comment">// Reset the reCAPTCHA when the request has failed or succeeeded</span><br /> <span class="token comment">// so that it can be executed again if user submits another email.</span><br /> recaptchaRef<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">reset</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">setEmail</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><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>To test if the integration is proper, you can replace the captcha code sent to the API with a random string, and you should see this when you click on register.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/invalid-response-code-480.webp 480w, https://prateeksurana.me/img/invalid-response-code-768.webp 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/invalid-response-code-480.jpeg 480w, https://prateeksurana.me/img/invalid-response-code-768.jpeg 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/invalid-response-code-768.jpeg" width="768" height="217" alt="Invalid response code" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>If you followed along till here, then pat yourself on the back. Your frontend and backend database are now fully secure from any spam or bots.</p>
<p><a href="https://github.com/prateek3255/recaptch-with-next/tree/a57aadb1dddf4289892c8949de6d8715ba1a07d1">👨🏻💻 Code till this step</a></p>
<h2 class="relative">
<a id="bonus-integrating-hcaptcha-and-why-you-might-need-it" href="https://prateeksurana.me/blog/integrating-recaptcha-with-next/#bonus-integrating-hcaptcha-and-why-you-might-need-it" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Bonus: Integrating hCAPTCHA and why you might need it
</a>
</h2>
<p>Although reCAPTCHA might be great for security, but if you're concerned about your user's privacy, then <a href="https://www.hcaptcha.com/">hCaptcha</a> might be a better choice. Do checkout why <a href="https://blog.cloudflare.com/moving-from-recaptcha-to-hcaptcha/">Cloudflare moved from reCAPTCHA to hCaptcha</a>. hCaptcha differs from reCAPTCHA in the following ways:</p>
<ul>
<li>They respect for your user's privacy.</li>
<li>Your visitors will solve problems that benefits many companies for labelling the data instead of a single corporation.</li>
<li>It's more user friendly and contains a variety of challenges.</li>
</ul>
<p>Thanks to hCaptcha's clean and similar to reCAPTCHA APIs, it takes no time to switch from reCAPTCHA to hCaptcha. It literally took me just 15 minutes to go through their docs and replace reCAPTCHA with hCaptcha for our demo.</p>
<p>The setting up process is very similar to reCAPTCHA. You can go to their <a href="https://www.hcaptcha.com/signup-interstitial">signup page</a> to create an account and get the site key and secret key for your site. I renamed the keys to <code>NEXT_PUBLIC_HCAPTCHA_SITE_KEY</code> and <code>HCAPTCHA_SECRET_KEY</code>, respectively, in the <code>.env.local</code> file.</p>
<p>They also have a React wrapper component called <a href="https://github.com/hCaptcha/react-hcaptcha"><code>@hcaptcha/react-hcaptcha</code></a>, which also has a very similar API to the React component we used for reCAPTCHA. These are the only changes (apart from renaming reCAPTCHA variables) I had to integrate the component on the client in <code>pages/index.js</code> :</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token punctuation">.</span><br /><span class="token punctuation">.</span><br /><span class="token keyword">import</span> HCaptcha <span class="token keyword">from</span> <span class="token string">"@hcaptcha/react-hcaptcha"</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 tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">HCaptcha</span></span><br /> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>test<span class="token punctuation">"</span></span><br /> <span class="token attr-name">size</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>invisible<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>hcaptchaRef<span class="token punctuation">}</span></span><br /> <span class="token attr-name">sitekey</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NEXT_PUBLIC_HCAPTCHA_SITE_KEY</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">onVerify</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>onHCaptchaChange<span class="token punctuation">}</span></span><br /><span class="token punctuation">/></span></span></code></pre>
<p>For the api route, we just need to change the url and pass the secret and token to the body instead of query params, this is what it looks like in <code>pages/api/register.js</code> :</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><br /> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://hcaptcha.com/siteverify</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span><br /> <span class="token punctuation">{</span><br /> <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token string-property property">"Content-Type"</span><span class="token operator">:</span> <span class="token string">"application/x-www-form-urlencoded; charset=utf-8"</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">response=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>captcha<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&secret=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">HCAPTCHA_SECRET_KEY</span><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 literal-property property">method</span><span class="token operator">:</span> <span class="token string">"POST"</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>Although hCaptcha doesn't work on localhost URLs so you would need to add a <a href="https://docs.hcaptcha.com/#local-development">host entry for localhost</a> according to your system for it to work.</p>
<p>After that you can just run <code>yarn dev</code>, and visit the URL you added the host entry for localhost to to see hCaptcha in action</p>
<p><img src="https://prateeksurana.me/img/hcaptcha-in-action.gif" width="768" height="436" alt="hCaptcha in action" class="article-img" loading="lazy" decoding="async" /></p>
<p>I created a separate branch in the demo repo, for the hCaptcha integration here -</p>
<p><a href="https://github.com/prateek3255/recaptch-with-next/tree/integrate-hcaptcha">👨🏻💻 Code till this step</a></p>
<p>I hope this article helped you in gaining some insight on how you can integrate CAPTCHA with your Next.js website and which CAPTCHA service you should prefer.</p>
<p>You can find the full code for both the <a href="https://github.com/prateek3255/recaptch-with-next">reCAPTCHA</a> and <a href="https://github.com/prateek3255/recaptch-with-next/tree/integrate-hcaptcha">hCaptcha</a> integration on GitHub.</p>
JavaScript tips for React DevelopersCheck out how you can write more efficient, maintainable and clean React code with these simple tips.2021-02-28T00:00:00Z2021-04-18T00:00:00Zhttps://prateeksurana.me/blog/javascript-tips-for-react-developers/<p>I have been working with React for the past couple of years, so naturally, I am not really proud of the code that I wrote when I was just beginning with React, because now I know the mistakes I made which I wasn't aware of back then.</p>
<p>But fast-forwarding to today, I have learned quite a bit along the way through contributing to open source, watching/reading some interesting blogs and conference talks and viewing how other people write code.</p>
<p>Here are some Javascript tips that would've helped my past self and maybe you, in writing more efficient and maintainable React code -</p>
<h2 class="relative">
<a id="1-use-conditional-rendering-effectively" href="https://prateeksurana.me/blog/javascript-tips-for-react-developers/#1-use-conditional-rendering-effectively" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
1. Use conditional rendering effectively
</a>
</h2>
<p>As a React developer, you must've been in a situation where you only want to display a component when a certain condition from a prop or state is satisfied or render different components depending on the different values of the state.</p>
<p>For instance, if you have a component where you want to show a loading indicator when the request is being made and render the component with data when the request is successful, this is the way I like to do it -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">SomeComponent</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> isLoading<span class="token punctuation">,</span> data <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /><br /> <span class="token keyword">if</span><span class="token punctuation">(</span>isLoading<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">Loader</span></span><span class="token punctuation">/></span></span><br /> <span class="token punctuation">}</span><br /><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">DataHandler</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> .<br /> .<br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">DataHandler</span></span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token punctuation">}</span></code></pre>
<p>But what if you want to render something inside JSX when a particular condition is satisfied in that case you can use the Logical AND operator (<code>&&</code>) to render it -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Button</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> showHomeIcon<span class="token punctuation">,</span> children<span class="token punctuation">,</span> onClick <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 tag"><span class="token tag"><span class="token punctuation"><</span>button</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>button<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>onClick<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>showHomeIcon <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">HomeIcon</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</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>button</span><span class="token punctuation">></span></span><br /><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Although a more useful scenario would be doing something like this, where you have an optional prop called icon which is a string and contains the name of the icon that can be used to render the icon component accordingly -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">Button</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> icon<span class="token punctuation">,</span> children<span class="token punctuation">,</span> onClick <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 tag"><span class="token tag"><span class="token punctuation"><</span>button</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>button<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>onClick<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">/* Icon won't be rendered if the value of<br /> icon prop is anything other than a string */</span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span><span class="token keyword">typeof</span> icon <span class="token operator">===</span> <span class="token string">"string"</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 attr-name">name</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>icon<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 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>button</span><span class="token punctuation">></span></span><br /><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// Renders a button with a home icon</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">icon</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>home<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>handleClick<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">Home</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 /><br /><span class="token comment">// Renders a button without an icon</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">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>handleClick<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">About</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></code></pre>
<p>So this solves the problem when you only have one component but what about when you have two or more than two components that you want to render based on some prop or state variable?</p>
<p>For two components ternary operator is my goto method, because of its simiplicity -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">App</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> canViewWelcomeText <span class="token operator">=</span> <span class="token function">isUserAuthenticated</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">return</span> canViewWelcomeText <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 punctuation">></span></span><span class="token plain-text">Hey, there! Welcome back. Its been a while.</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 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 punctuation">></span></span><span class="token plain-text">You need to login to view this page</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 if you have quite a few components that need to be rendered from a condition, then switch case is probably the best one to go with -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">getCurrentComponent</span> <span class="token operator">=</span> <span class="token parameter">currentTab</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">switch</span> <span class="token punctuation">(</span>currentTab<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">case</span> <span class="token string">'profile'</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 class="token class-name">Profile</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /> <span class="token keyword">case</span> <span class="token string">'settings'</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 class="token class-name">Settings</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span><br /> <span class="token keyword">default</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 class="token class-name">Home</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><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> <span class="token function-variable function">Dashboard</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> <span class="token punctuation">[</span>currentTab<span class="token punctuation">,</span> setTab<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">'profile'</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 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>dashboard<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">PrimaryTab</span></span> <span class="token attr-name">currentTab</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>currentTab<span class="token punctuation">}</span></span> <span class="token attr-name">setTab</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>setTab<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 function">getCurrentComponent</span><span class="token punctuation">(</span>currentTab<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><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span> </code></pre>
<h2 class="relative">
<a id="2-avoid-using-truthy-tests" href="https://prateeksurana.me/blog/javascript-tips-for-react-developers/#2-avoid-using-truthy-tests" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
2. Avoid using truthy tests
</a>
</h2>
<p>If you are familiar with JavaScript then you might be aware of <a href="https://developer.mozilla.org/en-US/docs/Glossary/Truthy">truthy</a> and <a href="https://developer.mozilla.org/en-US/docs/Glossary/Falsy">falsy</a> values. So a truthy test is nothing but using this coercion ability of JavaScript in control flow statements like this</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// ❌ Avoid adding checks like these </span><br /><span class="token comment">// for non boolean variables</span><br /><span class="token keyword">if</span> <span class="token punctuation">(</span>somVar<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">doSomething</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>This might look good at first if you want to avoid something like <code>null</code> since it is a falsy value so the statement will work as expected. But the catch here is that this is prone to bugs that can be very difficult to track down. This is because the above statement would block the flow for not <code>null</code> but also for all these falsy values of <code>someVar</code> which we might want to avoid -</p>
<pre class="language-jsx"><code class="language-jsx">someVar <span class="token operator">=</span> <span class="token number">0</span><br />someVar <span class="token operator">=</span> <span class="token string">""</span><br />someVar <span class="token operator">=</span> <span class="token boolean">false</span><br />someVar <span class="token operator">=</span> <span class="token keyword">undefined</span></code></pre>
<p>So what is the correct way for these checks?</p>
<p>The valid way is being as straightforward as possible for these checks to avoid any bugs from creeping in. For the above case it would be -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// ✅ Explictly check for the conditions you want</span><br /><span class="token keyword">if</span> <span class="token punctuation">(</span>someVar <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">doSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>If you're sure that the variable being passed is a boolean then you can use the former approach.</p>
</aside>
<p>This also applies when doing conditional rendering with the Logical and operator that we saw in the previous tip.</p>
<p>If the first operator is falsy then JavaScript returns that object. So in case of an expression like <code>0 && "javascript"</code> will return <code>0</code> and <code>false && "javascript"</code> will return <code>false</code> . This can bite you if you were doing something like this -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// ❌ This will end up rendering 0 as the text if </span><br /><span class="token comment">// the array is empty</span><br /><span class="token punctuation">{</span>cats<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">AllCats</span></span> <span class="token attr-name">cats</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>cats<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span><br /><br /><span class="token comment">// ✅ Use this instead because the result of the </span><br /><span class="token comment">// condition would be a boolean</span><br /><span class="token punctuation">{</span>cats<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&&</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">AllCats</span></span> <span class="token attr-name">cats</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>cats<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">}</span></code></pre>
<h2 class="relative">
<a id="3-use-optional-chaining-and-nullish-coalescing" href="https://prateeksurana.me/blog/javascript-tips-for-react-developers/#3-use-optional-chaining-and-nullish-coalescing" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
3. Use optional chaining and nullish coalescing
</a>
</h2>
<p>When dealing with data in our apps we often need to deal with parts of data that call be <code>null</code> or <code>undefined</code> and provide default values.</p>
<p>Let's suppose we have an API that returns the details of a Pet in the following format -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// Endpoint - /api/pets/{id}</span><br /><span class="token punctuation">{</span><br /> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token number">42</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Ghost'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'Mammal'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">diet</span><span class="token operator">:</span> <span class="token string">'Carnivore'</span><br /> <span class="token literal-property property">owner</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">first_name</span><span class="token operator">:</span> <span class="token string">'Jon'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">last_name</span><span class="token operator">:</span> <span class="token string">'Snow'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">family</span><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">'Stark'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">location</span><span class="token operator">:</span> <span class="token string">'Winterfell'</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>So you could do something like this if you wanted the first name of the pet owner</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> ownerName <span class="token operator">=</span> pet<span class="token punctuation">.</span>owner<span class="token punctuation">.</span>first_name<span class="token punctuation">;</span></code></pre>
<p>But like all things in the universe can't be perfect, our API doesn't guarantee that all the details would be available for any given pet and can be <code>null</code> or <code>undefined</code>.</p>
<p>In that case, the above line of code can result and the following error "Reference error cannot read property <code>first_name</code> of <code>null</code>" and crash your app if the owner is <code>null</code>.</p>
<p>This is where <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining">optional chaining</a> saves you. The optional chaining operator (<code>?.</code>) allows you to read the property deep in the chain without having to validate whether the chain is valid, and instead of a reference error, it would return the same old <code>undefined</code>.</p>
<p>So we could easily check for the owner name or even the owner family name without worrying about any errors, like this -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> ownerName <span class="token operator">=</span> pet<span class="token operator">?.</span>owner<span class="token operator">?.</span>first_name<span class="token punctuation">;</span><br /><span class="token keyword">const</span> ownerFamily <span class="token operator">=</span> pet<span class="token operator">?.</span>owner<span class="token operator">?.</span>family<span class="token operator">?.</span>name<span class="token punctuation">;</span></code></pre>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>Optional chaining can also be used with function calls on properties you are not really sure to exist. For instance, you can do this with an array <code>friends?.join(",")</code> , this won't result in an error if friends is anything other than an array, even <code>undefined</code>.</p>
</aside>
<p>Now, this would avoid errors but you still wouldn't want your users to show <code>undefined</code> in case it is not available. This is where Nullish Coalescing comes in -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> ownerName <span class="token operator">=</span> pet<span class="token operator">?.</span>owner<span class="token operator">?.</span>first_name <span class="token operator">??</span> <span class="token string">'Unknown'</span><span class="token punctuation">;</span></code></pre>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator">Nullish Coalescing operator</a> (<code>??</code>) returns the right hand side operand when the left hand side is <code>null</code> or <code>undefined</code> and otherwise it returns the left hand side operand.</p>
<p>You might think here that the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR">Logical Or operator</a> (<code>||</code>) would also have done the same thing. Well in that case I hope you haven't forgotten the truthy and falsy hell of JavaScript that we just covered. Since this operator would return the right hand side operand for all falsy values and can cause hard to debug errors as mentioned in the previous section.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>Since these methods were recently released with <a href="https://auth0.com/blog/javascript-whats-new-es2020/">ECMAScript 2020 spec</a> browser compatibility for these methods is still in the preliminary stages and only the modern browsers support it right now.</p>
<p>But don't worry most setups we use to compile our apps have already got us covered. If you're using TypeScript ≥ 3.7 then <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html">these methods are supported out of the box</a>.</p>
<p>Else if you're using a typical Webpack + babel setup then you can include the <a href="https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining"><code>@babel/plugin-proposal-optional-chaining</code></a> and <a href="https://babeljs.io/docs/en/babel-plugin-proposal-nullish-coalescing-operator"><code>@babel/plugin-proposal-nullish-coalescing-operator</code></a> plugins in your babel config to support these methods.</p>
</aside>
<h2 class="relative">
<a id="4-avoid-premature-optimization" href="https://prateeksurana.me/blog/javascript-tips-for-react-developers/#4-avoid-premature-optimization" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
4. Avoid premature optimization
</a>
</h2>
<p>Be really careful when you want to memoize something in React, because if not done properly it might lead to even worse performance.</p>
<p>I have often seen people prematurely optimizing everything they come across without considering the cost it comes with. For instance, using <code>useCallback</code> in situations like this -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">MyForm</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>firstName<span class="token punctuation">,</span> setFirstName<span class="token punctuation">]</span> <span class="token operator">=</span> React<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><br /><br /> <span class="token keyword">const</span> <span class="token function-variable function">handleSubmit</span> <span class="token operator">=</span> <span class="token parameter">event</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">/**<br /> * Ommitted for brevity<br /> */</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">// ❌ useCallback is unnecessary and </span><br /> <span class="token comment">// can actually be worse for performance</span><br /> <span class="token keyword">const</span> handleChange <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token parameter">event</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setFirstName</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>target<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> <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><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>form</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>input</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>text<span class="token punctuation">"</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 attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>handleChange<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">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"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>form</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>Now you might've heard that <code>useCallback</code> is known to improve performance by memoizing the function and only updating it when the dependencies change. That is true but you need to understand that <strong>every optimization comes with a cost associated with it</strong>.</p>
<p>In the above case, you are doing more work by creating a <code>useCallback</code> which in itself is running some logical expression checks, hence you're better off with just defining the inline function directly like this -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">handleChange</span> <span class="token operator">=</span> <span class="token parameter">event</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setFirstName</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>target<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>The same things apply with <code>React.memo</code>. If you have a component like this that accepts children props, then memoizing the component is basically useless if the children are not memoized -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> UselessMemoizedHeader <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">memo</span><span class="token punctuation">(</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 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>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><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> <span class="token function-variable function">SomeComponent</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>count<span class="token punctuation">,</span> setCount<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</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 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">UselessMemoizedHeader</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><span class="token punctuation">></span></span><span class="token plain-text">Header</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><span class="token class-name">UselessMemoizedHeader</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> Count: </span><span class="token punctuation">{</span>count<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> <br /> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>button<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><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setCount</span><span class="token punctuation">(</span><span class="token parameter">currentCount</span> <span class="token operator">=></span> currentCount <span class="token operator">+</span> <span class="token number">1</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 /> Increment count<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 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 the above case, the <code>UselessMemoizedHeader</code> component would re-render every time you increment the count even though you might think it is memoized.</p>
<p>But why? Since memo just does a shallow comparison of the current props and previous props, and because the children prop won't be <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness">refrentially equal</a> you end up re-rendering the <code>UselessMemoizedHeader</code> component every time the count changes.</p>
<p>Your code ends up being even worse off because of that unnecessary children prop comparison on every render.</p>
<p>So when do you actually need to memoize? Well I wrote another article that <a href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/">covers all the above things with when you should memoize in great detail</a>.</p>
<h2 class="relative">
<a id="5-be-vigilant-with-dependency-arrays" href="https://prateeksurana.me/blog/javascript-tips-for-react-developers/#5-be-vigilant-with-dependency-arrays" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
5. Be vigilant with dependency arrays
</a>
</h2>
<p>The React hooks related to memoization(<a href="https://reactjs.org/docs/hooks-reference.html#usecallback"><code>useCallback</code></a> and <a href="https://reactjs.org/docs/hooks-reference.html#usememo"><code>useMemo</code></a>) and the <a href="https://reactjs.org/docs/hooks-reference.html#useeffect"><code>useEffect</code></a> hook take a second argument as an array usually known as dependency array.</p>
<p>In case of <code>useEffect</code> the effect is re-run only when a shallow equality check of the dependency array is not equal to the previous values.</p>
<pre class="language-jsx"><code class="language-jsx">React<span class="token punctuation">.</span><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">/**<br /> * Fetch data with new query<br /> * and update the state<br /> */</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>query<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// < The effect reruns only when the query changes</span></code></pre>
<p>Similarly, the memoization hooks, are recomputed only when the values in their dependency array change</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> someValue <span class="token operator">=</span> React<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> <br /> <span class="token function">computationallyExpensiveCalculation</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">,</span> <br /><span class="token punctuation">[</span>count<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// < someValue is recomputed only when count changes</span></code></pre>
<p>So now that's clear. Can you find out why does the effect runs everytime the CatSearch component re-renders, even when the query, height and color props are essentially the same -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">CatSearch</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> height<span class="token punctuation">,</span> color<span class="token punctuation">,</span> query<span class="token punctuation">,</span> currentCat <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> filters <span class="token operator">=</span> <span class="token punctuation">{</span><br /> height<span class="token punctuation">,</span><br /> color<span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> React<span class="token punctuation">.</span><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">fetchCats</span><span class="token punctuation">(</span>query<span class="token punctuation">,</span> filters<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>query<span class="token punctuation">,</span> filters<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// ❌ This effect will run on every render</span><br /><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <span class="token comment">/**<br /> * Ommited for brevity<br /> */</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>Well as we discussed in the last section, React just does a shallow comparison of the items in the dependency array and since the filter object gets created in every render it can never be referentially equal to the filter object in the previous render.</p>
<p>So the correct way to do this would be -</p>
<pre class="language-jsx"><code class="language-jsx">React<span class="token punctuation">.</span><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">fetchCats</span><span class="token punctuation">(</span>query<span class="token punctuation">,</span> <span class="token punctuation">{</span> height<span class="token punctuation">,</span> color <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>query<span class="token punctuation">,</span> height<span class="token punctuation">,</span> color<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// ✅ The effect will </span><br /><span class="token comment">// now run only when one of these props changes</span></code></pre>
<p>The same applies for spreading the dependencies like this -</p>
<pre class="language-jsx"><code class="language-jsx">React<span class="token punctuation">.</span><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">/**<br /> * Ommited for brevity<br /> */</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token operator">...</span>filterArray<span class="token punctuation">,</span> query<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// ❌ This effect </span><br /><span class="token comment">// would also run on every render</span></code></pre>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>If you are using <a href="https://eslint.org/">ESLint</a> as your linter then you should definitely install the <a href="https://www.npmjs.com/package/eslint-plugin-react-hooks">eslint-plugin-react-hooks</a>, it will warn you and even fix issues automatically (if <a href="https://www.digitalocean.com/community/tutorials/workflow-auto-eslinting">autofix on save</a> is enabled) for most of the mistakes people generally make with these hooks.</p>
</aside>
<p>Also if you're more interested in how <code>useEffect</code> works and how the dependency array affects the effect, then you should definitely check out <a href="https://overreacted.io/a-complete-guide-to-useeffect/">A Complete Guide to useEffect</a> by <a href="https://twitter.com/dan_abramov">Dan Abramov</a>.</p>
When should you memoize in ReactExplore why premature optimization can be bad in React and when should you actually use the memoization methods provided by React2021-04-18T00:00:00Z2021-04-21T00:00:00Zhttps://prateeksurana.me/blog/when-should-you-memoize-in-react/<p>If you have heard about or used the React memoization methods (useMemo, useCallback, and memo), you might often get tempted to use them in situations where you might not need them.</p>
<p>When I first learned about these methods, I also often ended up using them everywhere because what harm optimizing something could do, right?</p>
<p>Well, as you might have guessed by now, I was wrong because these hooks and methods exist for some specific use cases, and if they're used mindlessly everywhere, they can actually worsen your app's performance.</p>
<p>In this article, I'll try my best to explain -</p>
<ol>
<li><a href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#why-premature-optimization-is-bad">Why premature optimization is bad</a></li>
<li><a href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#how-can-you-optimize-your-code-without-memoizing">How can you optimize your code without memoizing</a></li>
<li><a href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#when-should-you-actually-memoize">When should you actually memoize</a></li>
</ol>
<h2 class="relative">
<a id="why-premature-optimization-is-bad" href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#why-premature-optimization-is-bad" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Why premature optimization is bad
</a>
</h2>
<p>You might have heard this famous quote by <a href="https://en.wikipedia.org/wiki/Donald_Knuth">Donald Knuth</a>, that "Premature optimization is the root of all evil." Well, the quote might be old, but it still holds its value for software engineers like us trying to eagerly optimize without analyzing its benefits. So let's understand why it is bad to prematurely memoize in React -</p>
<h3 class="relative">
<a id="usecallback" href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#usecallback" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
useCallback
</a>
</h3>
<p>Let's start with an example. What do you think about, handleChange in the below code snippet?</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">MyForm</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>firstName<span class="token punctuation">,</span> setFirstName<span class="token punctuation">]</span> <span class="token operator">=</span> React<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><br /><br /> <span class="token keyword">const</span> <span class="token function-variable function">handleSubmit</span> <span class="token operator">=</span> <span class="token parameter">event</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">/**<br /> * Omitted for brevity<br /> */</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">const</span> handleChange <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token parameter">event</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setFirstName</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>target<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> <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><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>form</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>input</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>text<span class="token punctuation">"</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 attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>handleChange<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">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"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>form</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>I used to think that <code>useCallback</code> improves performance by returning a memoized callback that only changes if one of the dependencies changes. In our case, since the dependency array is empty, it would get memoized and would be more efficient than the normal inline function, right?</p>
<p>But, it's not as simple as that, because <strong>every optimization comes with a cost associated with it</strong>. And in the above case, the optimization is not worth the cost it comes with. But why?</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> handleChange <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token parameter">event</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token function">setFirstName</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>target<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> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>In the above case, <code>useCallback()</code> is called every time our <code>MyForm</code> component re-renders. Even though it returns the same function object, still the inline function is created on every render, <code>useCallback</code> just skips it to have the same reference to the function. Not only that, but we also have the empty dependency array, which itself is doing some work by running through some logical expressions to check if the variables inside have changed, etc.</p>
<p>So this is not really an optimization since <strong>the optimization costs more than not having the optimization</strong>. Also, our code is a bit more difficult to read than it was before because of the function being wrapped in a useCallback.</p>
<p>And as far as inline functions go, this is what <a href="https://reactjs.org/docs/faq-functions.html#is-it-ok-to-use-arrow-functions-in-render-methods">the official documentation on the React website</a> says, and <a href="https://reacttraining.com/blog/react-inline-functions-and-performance/">they are not actually as bad as you think they are</a>.</p>
<h3 class="relative">
<a id="usememo-different-yet-similar" href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#usememo-different-yet-similar" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
useMemo different yet similar
</a>
</h3>
<p><code>useMemo</code> is also very similar to <code>useCallback</code>, with the only difference that it allows memoization to any value type. It does so by accepting a function that returns a value and is only recomputed when the items in the dependency list change. So again, if I didn't want to initialize something on every render, I could do this right?</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">MightiestHeroes</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> heroes <span class="token operator">=</span> React<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> <br /> <span class="token punctuation">[</span><span class="token string">'Iron man'</span><span class="token punctuation">,</span> <span class="token string">'Thor'</span><span class="token punctuation">,</span> <span class="token string">'Hulk'</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><br /> <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><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span><span class="token comment">/* Does something with heroes, Ommited for brevity */</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><br /><br /><span class="token punctuation">}</span></code></pre>
<p>Again the savings are so minimal that making the code more complex isn't worth it, and it's probably worse because of the same reasons, which we discussed in the previous section.</p>
<p>For a case like this you would be much better off by defining the array outside the component.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> heroes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'Iron man'</span><span class="token punctuation">,</span> <span class="token string">'Thor'</span><span class="token punctuation">,</span> <span class="token string">'Hulk'</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">MightiestHeroes</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">// Ommited for brevity </span><br /> <br /><span class="token punctuation">}</span></code></pre>
<h3 class="relative">
<a id="edge-cases-with-memo" href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#edge-cases-with-memo" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Edge cases with memo
</a>
</h3>
<p>The same thing goes with <code>memo</code>, if we're not careful enough your memoized component might end up doing more work and hence being more inefficient than the normal counterpart</p>
<p>Take this sandbox for example, how many times do you think this memoized component will render when you are incrementing the count.</p>
<iframe src="https://codesandbox.io/embed/musing-brahmagupta-chx76?expanddevtools=1&fontsize=12&hidenavigation=1&module=%2Fsrc%2FApp.js&theme=dark&view=split" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" loading="lazy" title="musing-brahmagupta-chx76" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p>But shouldn't it render only once because it takes only one <code>children</code> prop which doesn't appear to be changing across renders?</p>
<p>Well <code>memo</code> does a shallow comparison of the previous props and the new props and re-renders only when the props have changed. So if you've been working with JavaScript for some time then you must be aware of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#strict_equality_using">Referential Equality</a> -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token number">2</span> <span class="token operator">===</span> <span class="token number">2</span> <span class="token comment">// true</span><br /><span class="token boolean">true</span> <span class="token operator">===</span> <span class="token boolean">true</span> <span class="token comment">// true</span><br /><span class="token string">'prateek'</span> <span class="token operator">===</span> <span class="token string">'prateek'</span> <span class="token comment">// true</span><br /><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 comment">// false</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 comment">// false</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 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 comment">// false</span></code></pre>
<p>And since <code>typeof children === 'object</code>, the equality check in memo always returns false, so whenever the parent re-renders, it will cause our memoized component to re-render as well.</p>
<h2 class="relative">
<a id="how-can-you-optimize-your-code-without-memoizing" href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#how-can-you-optimize-your-code-without-memoizing" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
How can you optimize your code without memoizing
</a>
</h2>
<p>In most cases, check if you can split the parts that change from the parts that don't change, this will probably solve most of the problems without needing to use memoization. For example, in the previous React.memo example, if we separate the heavy lifting component from the counting logic, then we can prevent the unnecessary re-renders.</p>
<iframe src="https://codesandbox.io/embed/summer-bird-w6nvm?expanddevtools=1&fontsize=12&hidenavigation=1&module=%2Fsrc%2FApp.js&theme=dark&view=split" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" loading="lazy" title="summer-bird-w6nvm" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p>You can checkout Dan Abramov's article <a href="https://overreacted.io/before-you-memo/">Before you Memo</a> if you want to read more about it.</p>
<p>But in some cases, you would need to use the memoization hooks and functions, so let's look at when you should use these methods.</p>
<h2 class="relative">
<a id="when-should-you-actually-memoize" href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#when-should-you-actually-memoize" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
When should you actually memoize
</a>
</h2>
<h3 class="relative">
<a id="usecallback-and-usememo" href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#usecallback-and-usememo" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
useCallback and useMemo
</a>
</h3>
<p>The main purpose of <code>useCallback</code> is to maintain <strong>referential equality</strong> of a function when passing it to a memoized component or using it in a dependency array (since functions are not referentially equal, as discussed above). For <code>useMemo</code> apart from referential equality and like <code>memo</code>, it is also a way to <strong>avoid recomputing expensive calculations.</strong> Let's understand how they work with some examples -</p>
<h4 class="relative">
<a id="referential-equality" href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#referential-equality" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Referential Equality
</a>
</h4>
<p>First, let's see how these hooks help us maintain referential equality, take a look at the following example (keep in mind that this is a contrived example to explain the use case of these hooks, actual implementations will vary)</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">PokemonSearch</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> weight<span class="token punctuation">,</span> power<span class="token punctuation">,</span> realtimeStats <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>searchquery<span class="token punctuation">,</span> setSearchQuery<span class="token punctuation">]</span> <span class="token operator">=</span> React<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><br /><br /> <span class="token keyword">const</span> filters <span class="token operator">=</span> <span class="token punctuation">{</span><br /> weight<span class="token punctuation">,</span><br /> power<span class="token punctuation">,</span><br /> searchquery<span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> isLoading<span class="token punctuation">,</span> result <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">usePokemonSearch</span><span class="token punctuation">(</span>filters<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">const</span> <span class="token function-variable function">updateQuery</span> <span class="token operator">=</span> <span class="token parameter">newQuery</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">/**<br /> * Some other stuff related to<br /> * analytics, omitted for brevity<br /> */</span><br /> <span class="token function">setSearchQuery</span><span class="token punctuation">(</span>newQuery<span class="token punctuation">)</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 punctuation">(</span><br /> <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">RealTimeStats</span></span> <span class="token attr-name">stats</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>realtimeStats<span class="token punctuation">}</span></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><span class="token class-name">MemoizedSearch</span></span> <span class="token attr-name">query</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>searchquery<span class="token punctuation">}</span></span> <span class="token attr-name">updateQuery</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>updateQuery<span class="token punctuation">}</span></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><span class="token class-name">SearchResult</span></span> <span class="token attr-name">data</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>result<span class="token punctuation">}</span></span> <span class="token attr-name">isLoading</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>isLoading<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><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> <span class="token function-variable function">usePokemonSearch</span> <span class="token operator">=</span> <span class="token parameter">filters</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> React<span class="token punctuation">.</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 /><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>result<span class="token punctuation">,</span> setResult<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</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 /><br /> React<span class="token punctuation">.</span><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">/**<br /> * Fetch the pokemons using filters<br /> * and update the loading and result state<br /> * accordingly, omitted for brevity<br /> */</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>filters<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> result<span class="token punctuation">,</span> isLoading <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 this example, we have a <code>PokemonSearch</code> component that uses the <code>usePokemonSearch</code> custom hook to fetch the pokemons for a given set of filters. Our component receives the weight and power filters from the parent component. It also receives a prop for real-time stats, which changes quite often, as the name suggests.</p>
<p>Our component itself handles the last filter, called <code>searchQuery</code>, via <code>useState</code>. We pass this filter to a memoized component called <code>MemoizedSearch</code> with a method to update it called <code>updateQuery</code>.</p>
<p>You might have noticed by now the first problem with our example, every time our <code>PokemonSearch</code> re-renders, a new reference of our <code>updateQuery</code> function would be created (which would not be equal to the previous reference because of how referential equality works in JavaScript), causing the <code>MemoizedSearch</code> component to re-render unnecessarily, even when the <code>searchQuery</code> is same.</p>
<p>This is where <code>useCallback</code> saves the day -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> updateQuery <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token parameter">newQuery</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">/**<br /> * Some other stuff related to<br /> * analytics, omitted for brevity<br /> */</span><br /> <span class="token function">setSearchQuery</span><span class="token punctuation">(</span>newQuery<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>This would help us in maintaining the same reference of the <code>updateQuery</code> function which will avoid the unnecessary re-renders of our <code>MemoizedSearch</code> component causing it to re-render only when the <code>searchQuery</code> changes.</p>
<p>If you check the <code>usePokemonSearch</code> custom hook, it has a <code>useEffect</code> that relies on the <code>filters</code> prop to decide whether to fetch the details of the pokemons whenever it changes. I hope that you noticed the next problem with our example as well. Every time the <code>PokemonSearch</code> re-renders, let's suppose not due to the change in one of the filters, it creates a new reference to our <code>filters</code> object, which won't be referentially equal to the last one causing the <code>useEffect</code> to run with every render of <code>PokemonSearch</code> and hence making a lot of unnecessary API calls.</p>
<p>Let's fix this with <code>useMemo</code> -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> filters <span class="token operator">=</span> React<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> <span class="token punctuation">(</span><span class="token punctuation">{</span><br /> weight<span class="token punctuation">,</span><br /> power<span class="token punctuation">,</span><br /> searchquery<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>weight<span class="token punctuation">,</span> power<span class="token punctuation">,</span> searchQuery<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Now the filter object reference will only be updated when either of our filter changes, thus calling the <code>useEffect</code> only when one of our filters change.</p>
<p>So the final code with all the optimizations looks like this -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">PokemonSearch</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> weight<span class="token punctuation">,</span> power<span class="token punctuation">,</span> realtimeStats <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>searchquery<span class="token punctuation">,</span> setSearchQuery<span class="token punctuation">]</span> <span class="token operator">=</span> React<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><br /><br /> <span class="token keyword">const</span> filters <span class="token operator">=</span> React<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> <span class="token punctuation">(</span><span class="token punctuation">{</span><br /> weight<span class="token punctuation">,</span><br /> power<span class="token punctuation">,</span><br /> searchquery<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>weight<span class="token punctuation">,</span> power<span class="token punctuation">,</span> searchQuery<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 punctuation">{</span> isLoading<span class="token punctuation">,</span> result <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">usePokemonSearch</span><span class="token punctuation">(</span>filters<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">const</span> updateQuery <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token parameter">newQuery</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">/**<br /> * Some other stuff related to<br /> * analytics, omitted for brevity<br /> */</span><br /> <span class="token function">setSearchQuery</span><span class="token punctuation">(</span>newQuery<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 /><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><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">RealTimeStats</span></span> <span class="token attr-name">stats</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>realtimeStats<span class="token punctuation">}</span></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><span class="token class-name">MemoizedSearch</span></span> <span class="token attr-name">query</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>searchquery<span class="token punctuation">}</span></span> <span class="token attr-name">updateQuery</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>updateQuery<span class="token punctuation">}</span></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><span class="token class-name">SearchResult</span></span> <span class="token attr-name">data</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>result<span class="token punctuation">}</span></span> <span class="token attr-name">isLoading</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>isLoading<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><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> <span class="token function-variable function">usePokemonSearch</span> <span class="token operator">=</span> <span class="token parameter">filters</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> React<span class="token punctuation">.</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 /><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>result<span class="token punctuation">,</span> setResult<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</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 /><br /> React<span class="token punctuation">.</span><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">/**<br /> * Fetch the pokemons using filters<br /> * and update the loading and result state<br /> * accordingly, omitted for brevity<br /> */</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>filters<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> result<span class="token punctuation">,</span> isLoading <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<h4 class="relative">
<a id="avoiding-recomputing-expensive-calculations" href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#avoiding-recomputing-expensive-calculations" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Avoiding recomputing expensive calculations
</a>
</h4>
<p>Apart from referential equality, the <code>useMemo</code> hook, similar to the <code>memo</code> function, serves one more purpose of avoiding recomputing expensive calculations with every render if they are not required.</p>
<p>For instance, take the following example, if you try to update the name really fast, you will be able to see a certain lag because the 35th Fibonacci number (which is purposefully slow and blocks the main thread while computing) is getting calculated every time your component re-renders even though the position remains the same.</p>
<iframe src="https://codesandbox.io/embed/expensive-calculation-without-usememo-p393q?fontsize=12&hidenavigation=1&module=%2Fsrc%2FApp.js&theme=dark&view=split" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" loading="lazy" title="expensive-calculation-without-usememo" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p>Now let's try this with <code>useMemo</code>. Try updating the name really fast again and see the difference -</p>
<iframe src="https://codesandbox.io/embed/expensive-calculation-with-usememo-s8hmx?fontsize=12&hidenavigation=1&theme=dark&view=split" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" loading="lazy" title="expensive-calculation-with-usememo" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p>With <code>useMemo</code> we only re-calculate the Fibonacci number only when the position changes thus avoiding the unnecessary main thread work.</p>
<h3 class="relative">
<a id="memo" href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#memo" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
memo
</a>
</h3>
<p>If your component re-renders the same result given the same props, <code>React.memo</code> can give you a performance boost by skipping re-rendering if the props haven't changed.</p>
<p>Dmitri created a really nice illustration in his article <a href="https://dmitripavlutin.com/use-react-memo-wisely/">Use React.memo() Wisely</a> which you should use a general rule of thumb when you're thinking about memoizing a component.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/when-to-use-react-memo-480.webp 480w, https://prateeksurana.me/img/when-to-use-react-memo-768.webp 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/when-to-use-react-memo-480.jpeg 480w, https://prateeksurana.me/img/when-to-use-react-memo-768.jpeg 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/when-to-use-react-memo-768.jpeg" width="768" height="1125" alt="When should you use React.memo" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Enough with the concepts, let's try to understand this with an example on when <code>React.memo</code> can be handy. In the below sandbox, we have a <code>usePokemon</code> hook that returns some static and real-time data for a pokemon.</p>
<p>The static details include the name image and abilities of the Pokemon. In contrast, the real-time info includes details like the number of people who want this Pokemon and the number of people who own the Pokemon, which change quite often.</p>
<p>These details are rendered by three components <code>PokemonDetails</code> which renders the static details, and <code>Cravers</code> and <code>Owners</code>, which render the real-time info, respectively.</p>
<iframe src="https://codesandbox.io/embed/pokemon-memo-tdkel?fontsize=12&hidenavigation=1&module=%2Fsrc%2FApp.js&theme=dark&view=split" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" loading="lazy" title="pokemon-memo" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p>Now, if you check the console in the above sandbox, it doesn't look good because even though <code>PokemonDetails</code> consist of static data, it still re-renders every time any of our real-time values change, which is not very performant. So let's use the Checklist by Dmitri mentioned above to see if we should memoize it -</p>
<ul>
<li>
<p><strong>Is it a pure functional component, that given the same props renders the same output?</strong></p>
<p>Yes, our <code>PokemonDetails</code> component is functional and renders the same output with the same props ✅</p>
</li>
<li>
<p><strong>Does it re-render often?</strong></p>
<p>Yes, it re-renders often because of the realtime values provided by our custom hook ✅</p>
</li>
<li>
<p><strong>Does it re-render with the same props?</strong></p>
<p>Yes, the props it uses don't change at all across all its renders ✅</p>
</li>
<li>
<p><strong>Is it a medium to big size component?</strong></p>
<p>Since this is a very contrived example, it isn't really isn't in the sandbox, but for the sake of this example let's assume it is (Although even though isn't very expensive but given that it satisfies the above three conditions it still is a pretty good case for memoization) ✅</p>
</li>
</ul>
<p>Since, our component satisfies the above conditions, let's memoize it -</p>
<iframe src="https://codesandbox.io/embed/pokemon-memo-forked-1j97f?fontsize=12&hidenavigation=1&module=%2Fsrc%2FApp.js&theme=dark&view=split" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" loading="lazy" title="pokemon-memo (forked)" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p>If you check the console in the above sandbox, you'll see that it gets re-rendered only once, optimizing our code quite a bit by saving us potentially expensive re-renders.</p>
<h2 class="relative">
<a id="conclusion" href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#conclusion" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Conclusion
</a>
</h2>
<p>If you've reached this far, I assume you get the point I am trying to make here. I'll repeat it <strong>every optimization you do comes with a cost associated with it</strong>, and the optimization is only worth it if the benefits outweigh the cost. In most cases, you might even not need to apply these methods if you can separate the parts that often change from the parts that don't change that much, as we discussed above.</p>
<p>I know it's a bit annoying, and maybe in the future, some really smart compiler could automatically take care of these things for you, but till then, we would have to be careful and <a href="https://reactjs.org/docs/profiler.html">analyze the benefits</a> while using these optimizations.</p>
<h2 class="relative">
<a id="have-i-read-this-before" href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/#have-i-read-this-before" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Have I read this before?
</a>
</h2>
<p>You might have because some parts of it were inspired by <a href="https://kentcdodds.com/blog/usememo-and-usecallback">this excellent post</a> by Kent C. Dodds. I really enjoyed the post, and realized that these methods were still often misused and hence deserved more attention, so I decided to write about it with examples from some situations that I have faced.</p>
Supercharge your git workflow with GitLensSome of my favorite features of the GitLens VSCode extension that can help you optimize your git workflow in VSCode.2021-05-20T00:00:00Z2021-05-20T00:00:00Zhttps://prateeksurana.me/blog/supercharge-git-workflow-with-gitlens/<p>This post will discuss some of my favorite features of the <a href="https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens">GitLens VSCode extension</a> that might help you optimize your git workflow in VSCode.</p>
<h2 class="relative">
<a id="why-bother-using-this-extension" href="https://prateeksurana.me/blog/supercharge-git-workflow-with-gitlens/#why-bother-using-this-extension" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Why bother using this extension?
</a>
</h2>
<p>Although VSCode has pretty good <a href="https://code.visualstudio.com/docs/editor/versioncontrol">built-in git tools</a> for basic git stuff like adding, committing, pushing, changing/creating branches. But if you want some additional features and want to "Supercharge your git workflow," then GitLens is possibly the best extension out there for this.</p>
<p>GitLens lets you do almost everything with git that you could imagine, directly from your code editor with a friendly GUI. I would go as far as to say that it even does stuff that I didn't even know was possible with git.</p>
<p>So here are some of my favorite GitLens features that I use regularly. You can find the <a href="https://gitlens.amod.io/#features">full list of the features here</a>.</p>
<h2 class="relative">
<a id="1-current-line-blame" href="https://prateeksurana.me/blog/supercharge-git-workflow-with-gitlens/#1-current-line-blame" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
1. Current line blame
</a>
</h2>
<p>This is probably the first feature you will notice when you install the extension. As soon as you hover over any line in the code (in a project using git and has some commits), you will be able to see this popover.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/gitlens-line-blame-default-480.webp 480w, https://prateeksurana.me/img/gitlens-line-blame-default-768.webp 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/gitlens-line-blame-default-480.jpeg 480w, https://prateeksurana.me/img/gitlens-line-blame-default-768.jpeg 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/gitlens-line-blame-default-768.jpeg" width="768" height="234" alt="GitLens line blame default view" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>This popover gives you almost all the information that you might need for the current line like, what did the line looked like before the most recent commit that affected it, the commit hash that introduced the change, the PR that introduced the change (would need to connect to GitHub for this one), etc.</p>
<p>If you click on the open changes button, you will be able to view all the changes introduced by that commit in the current file.</p>
<p><img src="https://prateeksurana.me/img/gitlens-open-changes-line-blame.gif" width="768" height="405" alt="GitLens line blame open changes" class="article-img" loading="lazy" decoding="async" /></p>
<p>Clicking on the commit hash in the popup gives you many options for that commit, like resetting, switching, or creating a branch from the commit, etc.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/gitlens-commit-options-480.webp 480w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/gitlens-commit-options-480.jpeg 480w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/gitlens-commit-options-480.jpeg" width="480" height="435" alt="GitLens commit options" class="article-img" loading="lazy" decoding="async" />
</picture>
<h2 class="relative">
<a id="2-file-blame" href="https://prateeksurana.me/blog/supercharge-git-workflow-with-gitlens/#2-file-blame" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
2. File blame
</a>
</h2>
<p>This is also a neat feature that allows you to view which lines were intrduced by which commit and author for a particular file. This can be useful for exploring the history of the file and answering questions about which commit/PR introduced a particular set of changes and who made those commits.</p>
<p>To toggle file blame, you can either use the <a href="https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette">VSCode command palette</a> (Cmd + Shift + P) and search for "GitLens: Toggle file blame" or use the keyboard shortcut (Option + Cmd + G B) to open it.</p>
<p><img src="https://prateeksurana.me/img/gitlens-file-blame.gif" width="768" height="429" alt="GitLens file blame" class="article-img" loading="lazy" decoding="async" /></p>
<p>Once you are in the file blame mode, you can select the commits from the sidebar that appears next to the gutter to explore the changes introduced by that commit.</p>
<h2 class="relative">
<a id="3-commits-view" href="https://prateeksurana.me/blog/supercharge-git-workflow-with-gitlens/#3-commits-view" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
3. Commits view
</a>
</h2>
<p>After you install the extension it adds a new menu for some quick actions in your sidebar as well.</p>
<p>Although there are many sections in its sidebar menu, like repositories, file history, line history, etc., the one I use most often is the commits view.</p>
<p>It shows you all the commits of the current branch and again allows you to do a bunch of stuff like exploring the changed files in the commit, the PR that introduced the commit, etc.</p>
<p><img src="https://prateeksurana.me/img/gitlens-commit-view.gif" width="768" height="248" alt="GitLens commits view" class="article-img" loading="lazy" decoding="async" /></p>
<p>If you right-click on a particular commit, you can take some actions over the commit like reset the commit, compare it with head, and, the one I use most often, switch to commit, which checks out the commit so you can explore the project in that commit state</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/gitlens-switch-to-commit-413.webp 413w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/gitlens-switch-to-commit-413.jpeg 413w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/gitlens-switch-to-commit-413.jpeg" width="413" height="466" alt="GitLens switch to commit" class="article-img" loading="lazy" decoding="async" />
</picture>
<h2 class="relative">
<a id="4-file-heatmap" href="https://prateeksurana.me/blog/supercharge-git-workflow-with-gitlens/#4-file-heatmap" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
4. File heatmap
</a>
</h2>
<p>The usefulness of this feature might be arguable, but it is pretty cool, so I thought it deserved a spot on this list.</p>
<p>File heatmap shows you the spots in the file that have been changed most recently by adding a heatmap on the edge of the gutter. To enable it, search for "GitLens: Toggle File heatmap" via the <a href="https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette">VSCode command palette</a> (Cmd + Shift + P).</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/gitlens-file-heatmap-480.webp 480w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/gitlens-file-heatmap-480.jpeg 480w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/gitlens-file-heatmap-480.jpeg" width="480" height="384" alt="GitLens file heatmap" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Redder color means changes were made very recently in that area. Again you can fully customize age thresholds, locations, the color of the most recent and least recent changes, etc., <a href="https://github.com/eamodio/vscode-gitlens#gutter-heatmap-settings-">from the settings</a>.</p>
<h2 class="relative">
<a id="5-interactive-rebase-editor" href="https://prateeksurana.me/blog/supercharge-git-workflow-with-gitlens/#5-interactive-rebase-editor" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
5. Interactive rebase editor
</a>
</h2>
<p>Last but not least, GitLens also comes with a user-friendly interactive rebase editor, which lets you easily drop, squash re-order your commits during an <a href="https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History">interactive git rebase session</a>.</p>
<p>This is an excellent tool for interactive rebases if you, like me, hate the vi editor that appears during interactive rebase with git.</p>
<p><img src="https://prateeksurana.me/img/gitlens-interactive-rebase.gif" width="480" height="366" alt="GitLens interactive rebase" class="article-img" loading="lazy" decoding="async" /></p>
<p>To use this directly from your terminal (e.g., when you use <code>git rebase -i</code> ), run the following command to set VS Code as your git rebase editor -</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">git</span> config --global core.editor <span class="token string">"code --wait"</span></code></pre>
<h2 class="relative">
<a id="conclusion" href="https://prateeksurana.me/blog/supercharge-git-workflow-with-gitlens/#conclusion" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Conclusion
</a>
</h2>
<p>GitLens helps you better understand your code by "Supercharging," the git abilities build into VS Code. It allows you to explore the history and evolution of a codebase effortlessly.</p>
<p>With the features I explained here, I have barely scratched the surface of what this extension can do, and even I haven't explored most of the features yet, so feel free to share in the comments below if you find something interesting that deserves to be in the list.</p>
Why using object spread with reduce probably a bad ideaExplore why using object spread with .reduce() can sometimes significantly affect the performance of your JavaScript apps and libraries.2021-06-13T00:00:00Z2021-06-13T00:00:00Zhttps://prateeksurana.me/blog/why-using-object-spread-with-reduce-bad-idea/<p>This tweet regarding a simplified way to use reduce got a lot of attention recently in the JavaScript community.</p>
<div style="display:flex; justify-content:center;">
<blockquote class="twitter-tweet" style="margin-left:auto; margin-right:auto;" data-dnt="true" data-theme="light"><p lang="en" dir="ltr">Simplify your .reduce calls by doing as much work as possible in .filter and .map instead. Compare these two snippets. Which is more readable? Notice that when map arranges data exactly how we want, our .reduce can be just Object.assign.<a href="https://twitter.com/hashtag/typescript?src=hash&ref_src=twsrc%5Etfw">#typescript</a> <a href="https://twitter.com/hashtag/javascript?src=hash&ref_src=twsrc%5Etfw">#javascript</a> <a href="https://t.co/3TW57kaCar">pic.twitter.com/3TW57kaCar</a></p>— Rupert Foggo McKay (@maxfmckay) <a href="https://twitter.com/maxfmckay/status/1396252890721918979?ref_src=twsrc%5Etfw">May 22, 2021</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</div>
<p>If you see the replies, you'll find how most people are concerned over the fact that the 2nd method is worse (which also doesn't work as expected, as <a href="https://prateeksurana.me/blog/why-using-object-spread-with-reduce-bad-idea/#reduce-object-assign-bug">we'll discuss later in this post</a>) because we are iterating the array thrice when in fact the former one is much slower. After all, the spread operator takes almost O(n) time, resulting in the overall time complexity of O(n<sup>2</sup>) for the first method. The latter is still O(n) (yes 3 times O(n) is still O(n), measuring 'hot paths' in code for time complexity is worthless) which is far better that O(n<sup>2</sup>) because of the exponential nature of the latter.</p>
<h2 class="relative">
<a id="why-is-the-spread-operator-so-slow" href="https://prateeksurana.me/blog/why-using-object-spread-with-reduce-bad-idea/#why-is-the-spread-operator-so-slow" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Why is the spread operator so slow?
</a>
</h2>
<p>The speed of the spread operator can vary depending upon how your JavaScript is getting compiled, but for the sake of this article, let's consider it is compiled using the v8 engine, which Node and Chrome use.</p>
<p>So let's assume we have a spread operator like this -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> obj1 <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">a</span><span class="token operator">:</span> <span class="token string">'hello'</span><span class="token punctuation">,</span> <span class="token literal-property property">b</span><span class="token operator">:</span> <span class="token string">'world'</span> <span class="token punctuation">}</span><br /><br /><span class="token keyword">const</span> obj2 <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span>obj1<span class="token punctuation">,</span> <span class="token literal-property property">c</span><span class="token operator">:</span> <span class="token string">'foo'</span> <span class="token punctuation">}</span></code></pre>
<p>Not going too deep into the implementation details of how it works, but when you use the spread operator like the above example, the compiler has to do a bunch of stuff like creating a new object literal, iterating and copying the <code>obj1</code> keys to the new object, and then adding the new keys ( <code>c: 'foo'</code> ).</p>
<p>Let's take this example where we convert an array of objects to an object via reduce -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> arr <span class="token operator">=</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'ironman'</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Tony Stark'</span><span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'hulk'</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Bruce Banner'</span><span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'blackwidow'</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Natasha Romanoff'</span><span class="token punctuation">}</span><span class="token punctuation">,</span><br /><span class="token punctuation">]</span><br /><br /><span class="token keyword">const</span> superheroes <span class="token operator">=</span> arr<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">acc<span class="token punctuation">,</span> item</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 operator">...</span>acc<span class="token punctuation">,</span><br /> <span class="token punctuation">[</span>item<span class="token punctuation">.</span>id<span class="token punctuation">]</span><span class="token operator">:</span> <span class="token punctuation">[</span>item<span class="token punctuation">.</span>name<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><br /><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Here the <code>reduce</code> loop runs n times for the array (where n is the number of items in the array). And as we discussed earlier, there is an invisible inner loop here as well with the spread operator that copies the existing accumulator keys to the new object.</p>
<p>Although the inner loop doesn't exactly run n times because it depends on the number of keys currently present in the accumulator, which grows with every iteration of the outer loop.</p>
<p>To simplify the above paragraph, this is how it works -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// First iteration</span><br />acc <span class="token operator">-</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 operator">...</span>acc<span class="token punctuation">,</span> <span class="token literal-property property">ironman</span><span class="token operator">:</span> <span class="token string">'Tony Stark'</span> <span class="token punctuation">}</span><span class="token comment">// No key needs to be copied</span><br /><br /><span class="token comment">// Second iteration</span><br />acc <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token literal-property property">ironman</span><span class="token operator">:</span> <span class="token string">'Tony Stark'</span> <span class="token punctuation">}</span> <br /><span class="token punctuation">{</span> <span class="token operator">...</span>acc<span class="token punctuation">,</span> <span class="token literal-property property">hulk</span><span class="token operator">:</span> <span class="token string">'Bruce Banner'</span> <span class="token punctuation">}</span> <span class="token comment">// 1 key from the accumulator </span><br /><span class="token comment">// would be copied to the new object</span><br /><br /><span class="token comment">// Thrid iteration</span><br />acc <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token literal-property property">ironman</span><span class="token operator">:</span> <span class="token string">'Tony Stark'</span><span class="token punctuation">,</span> <span class="token literal-property property">hulk</span><span class="token operator">:</span> <span class="token string">'Bruce Banner'</span> <span class="token punctuation">}</span> <br /><span class="token punctuation">{</span> <span class="token operator">...</span>acc<span class="token punctuation">,</span> <span class="token literal-property property">blackwidow</span><span class="token operator">:</span> <span class="token string">'Natasha Romanoff'</span> <span class="token punctuation">}</span> <span class="token comment">// 2 keys from the accumulator </span><br /><span class="token comment">// would be copied to the new object </span><br /><br /><span class="token comment">// ... and so on</span></code></pre>
<p>So even though the inner loop doesn't exactly run n times on every iteration, it's safe to say that it's in the same class of solutions that execute n<sup>2</sup> times, so we can say its O(n<sup>2</sup>).</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>I am assuming here that no duplicate keys are generated in the target object because the time complexity would still be classified as O(n<sup>2</sup>), but the actual run time will vary depending upon the number of duplicate keys since the size of the accumulator would vary accordingly with every iteration.</p>
</aside>
<p>But what if you're using some transpiler like <a href="https://babeljs.io/">Babel</a> or <a href="https://www.typescriptlang.org/play">TypeScript's transpiler</a> and targeting ES5 (which we generally do to support older browsers).</p>
<p>Well, first, if you see the specification of the <a href="https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md">object rest spread proposal on tc39</a>, you will see that -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> ab <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span>a<span class="token punctuation">,</span> <span class="token operator">...</span>b <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// Desugars to - </span><br /><span class="token keyword">let</span> ab <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">assign</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> a<span class="token punctuation">,</span> b<span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre>
<p>Here, the desugared syntax also has a similar time complexity as the spread operator because it is doing pretty much the same thing, creating a new object and then iterating and copying the keys of the remaining objects to the new object.</p>
<p>Babel transpiles the <a href="https://babeljs.io/repl#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&build=&builtIns=false&corejs=false&spec=true&loose=true&code_lz=MYewdgzgLgBCBGArAjDAvDA3jAHgLhgE4YBfAKDNElgUQCZ0sYBPA5ABlIqul0ewB0Q2sgA0MIQNp1xALwIAOEjDJA&debug=false&forceAllTransforms=true&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=script&lineWrap=true&presets=env%2Creact&prettier=false&targets=&version=7.14.3&externalPlugins=">spread syntax to something similar to the desugared TC39 proposal</a>. Although <a href="https://www.typescriptlang.org/play?#code/MYewdgzgLgBCBGArAjDAvDA3jAHgLhgE4YBfAWAChLRJYFEAmdLGATwOQAZTLrxpczbADpR9ZABoYo4fQZSAXgQAcJGJSA">TypeScripts transpiler creates nested <code>Object.assign</code> calls</a>, which I'm not sure why.</p>
<p>If you want to get into the weeds of what I tried to explain above, you can refer to <a href="https://www.richsnapp.com/article/2019/06-09-reduce-spread-anti-pattern">this awesome</a> article by <a href="https://twitter.com/snapwich">Rich snapp</a> that covers the same topic in much greater detail.</p>
<h2 class="relative">
<a id="the-better-solution" href="https://prateeksurana.me/blog/why-using-object-spread-with-reduce-bad-idea/#the-better-solution" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
The better solution
</a>
</h2>
<p>So what is the right way of converting an array to an object like this? Well, there are many ways, and if you see the Twitter thread, you will see a lot of people arguing about how some methods are faster than others. I won't be covering all that, but speaking strictly in terms of time complexity, here is my favorite one -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><br /><br />users<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">user</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>user<span class="token punctuation">.</span>active<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> result<span class="token punctuation">[</span>user<span class="token punctuation">.</span>id<span class="token punctuation">]</span> <span class="token operator">=</span> user<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><span class="token punctuation">;</span></code></pre>
<p>In the above method since we are mutating the result object directly the complexity here is O(n), also its more readable as well.</p>
<p>This method using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries"><code>Object.fromEntries</code></a> is also very neat because <code>Object.fromEntries</code> is built to convert iterables to object.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> filteredUsers <span class="token operator">=</span> users<br /> <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">user</span><span class="token punctuation">)</span> <span class="token operator">=></span> user<span class="token punctuation">.</span>active<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 punctuation">(</span><span class="token parameter">user</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span>user<span class="token punctuation">.</span>id<span class="token punctuation">,</span> user<span class="token punctuation">.</span>name<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> result <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">fromEntries</span><span class="token punctuation">(</span>filteredUsers<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<div class="bg-blue-100 px-9 py-1 rounded-md mt-12 relative" x-data="{ showMore: false }" id="reduce-object-assign-bug">
<div class="absolute p-2 bg-white rounded-full" style="top:-14px; left:-14px;">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
</div>
<p>Lastly, let's talk about the method that the tweet author suggested. As I mentioned initially, it might look much worse, but 3x O(n) is still O(n), so complexity wise we are probably good. Still, a small mistake leads to a slightly unexpected result and a significantly worse performance.</p>
<pre class="language-jsx"><code class="language-jsx">users<br /> <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">user</span><span class="token punctuation">)</span> <span class="token operator">=></span> user<span class="token punctuation">.</span>active<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 punctuation">(</span><span class="token parameter">user</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>user<span class="token punctuation">.</span>id<span class="token punctuation">]</span><span class="token operator">:</span> user<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 punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span>Object<span class="token punctuation">.</span>assign<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>
<button x-show="!showMore" x-transition.opacity.duration.100ms="" x-on:click="showMore = !showMore" class="flex gap-1 m-0 p-0 border-none bg-transparent items-center font-bold mb-3 mt-4">
Show more <span> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg> </span>
</button>
<div x-cloak="" x-show="showMore" x-transition.opacity.duration.300ms="">
<p>The problem here is in the <code>reduce</code> call where we directly pass <code>Object.assign</code> to it. If you check the documentation for <code>reduce</code> you'll find that it passes the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#how_reduce_works">following arguments to the callback function</a> -</p>
<pre class="language-jsx"><code class="language-jsx">arr<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">accumulator<span class="token punctuation">,</span> currentValue<span class="token punctuation">,</span> currentIndex<span class="token punctuation">,</span> array</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// Ommitted for brevity</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> initialValue<span class="token punctuation">)</span></code></pre>
<p>So we end up passing all these parameters to <code>Object.assign</code>, which increases the complexity of the operation and returns a slightly weird result. To understand, let's take a look at one of the iterations of the <code>reduce</code> call -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// users = [</span><br /><span class="token comment">// { id: 'id1', name: 'foo', active: true }, </span><br /><span class="token comment">// { id: 'id2', name: 'bar', active: true }</span><br /><span class="token comment">// ]</span><br /><br /><span class="token comment">// After the processing data is done by the filter and map</span><br /><span class="token comment">// functions, this gets passed to the Object.assign in the first</span><br /><span class="token comment">// iteration of the reduce</span><br /><br />Object<span class="token punctuation">.</span><span class="token function">assign</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 literal-property property">id1</span><span class="token operator">:</span> <span class="token string">'foo'</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><span class="token punctuation">{</span> <span class="token literal-property property">id1</span><span class="token operator">:</span> <span class="token string">'foo'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">id2</span><span class="token operator">:</span> <span class="token string">'bar'</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">)</span><br /><br /><span class="token comment">// Let's take a look at the arguments being passed -</span><br /><br /><span class="token comment">// First argument is the accumulator which for the first iteration is </span><br /><span class="token comment">// an empty object - {}</span><br /><br /><span class="token comment">// Second argument is the currentValue which is the first item from </span><br /><span class="token comment">// the array generated by the preceding map - { id1: 'foo' }</span><br /><br /><span class="token comment">// Third argument is the currentIndex which represents the index of the</span><br /><span class="token comment">// current item - 0</span><br /><br /><span class="token comment">// Last argument is the array itself which is generated by map in the </span><br /><span class="token comment">// previous step - [{ id1: 'foo' }, { id2: 'bar' }]</span></code></pre>
<p>The first two arguments are what we expected, the third argument being <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#primitives_will_be_wrapped_to_objects">a number doesn't have any effect,</a> the last argument is where the problem starts. Since arrays are also objects in JavaScript, they are also copied via <code>Object.assign</code> with their indexes as the keys. You can view the <a href="https://replit.com/@prateek3255/object-assign-bug#index.js">generated result in this repl</a>.</p>
<p>The correct way would be to pass only required arguments to <code>Object.assign</code> -</p>
<pre class="language-jsx"><code class="language-jsx">users<br /> <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">user</span><span class="token punctuation">)</span> <span class="token operator">=></span> user<span class="token punctuation">.</span>active<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 punctuation">(</span><span class="token parameter">user</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>user<span class="token punctuation">.</span>id<span class="token punctuation">]</span><span class="token operator">:</span> user<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 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> currentValue</span><span class="token punctuation">)</span> <span class="token operator">=></span> Object<span class="token punctuation">.</span><span class="token function">assign</span><span class="token punctuation">(</span>acc<span class="token punctuation">,</span> currentValue<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><a href="https://twitter.com/jaffathecake">Jake</a> wrote a really nice article about this pattern explaining <a href="https://jakearchibald.com/2021/function-callback-risks/">why you shouldn't use functions as callbacks unless they're designed for it</a>.</p>
</div>
</div>
<h2 class="relative">
<a id="wait-isnt-this-premature-optimization" href="https://prateeksurana.me/blog/why-using-object-spread-with-reduce-bad-idea/#wait-isnt-this-premature-optimization" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Wait, isn't this premature optimization?
</a>
</h2>
<p>As much as I want to say it is, it's probably not. Since the reduce with spread technique is exponential in nature, it gets gruesome with just a little increase in data.</p>
<p>Take <a href="https://replit.com/@prateek3255/reduce-timings#index.js">this repl running on Node 12</a>, for example. You can see how the time taken goes from just a couple of milliseconds for 10 times to a few 100 ms for 100 items and a staggering 1-2 seconds for 1000 items. Meanwhile, the difference between the rest of O(n) methods stays under 10ms for all the cases.</p>
<p>The results could be even worse if you're running this on a browser and your users are using some low-powered devices. You could block the main thread and block any interactions for a few hundred milliseconds, resulting in a very laggy UX.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/comparison-computational-complexity-480.webp 480w, https://prateeksurana.me/img/comparison-computational-complexity-768.webp 768w, https://prateeksurana.me/img/comparison-computational-complexity-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/comparison-computational-complexity-480.jpeg 480w, https://prateeksurana.me/img/comparison-computational-complexity-768.jpeg 768w, https://prateeksurana.me/img/comparison-computational-complexity-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/comparison-computational-complexity-768.jpeg" width="768" height="664" alt="Comparing computational complexity of different Big O notations" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>So if we were arguing amongst the O(n) methods that we discussed in the previous section, then that probably would have been a case for premature optimization but not this. Since as its clear from the above graph how the reduce with spread method (with O(n<sup>2</sup>) complexity) gets considerably worse with the increasing input size.</p>
<h2 class="relative">
<a id="conclusion" href="https://prateeksurana.me/blog/why-using-object-spread-with-reduce-bad-idea/#conclusion" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Conclusion
</a>
</h2>
<p>So, in conclusion, you should try to avoid this pattern in your code, even if you think that the data set is small enough to have any significant impact on performance because you may end up using this method in places where it actually hampers the performance.</p>
Why you should avoid using state for computed propertiesUnderstand why creating state variables for properties that can be computed is a bad idea, and how you can handle some edge cases when you need to derive your state from props.2021-07-18T00:00:00Z2021-07-18T00:00:00Zhttps://prateeksurana.me/blog/why-you-should-avoid-using-state-for-computed-properties/<p>I have often seen many people (including my past self) creating state variables for any kind of value that can change across renders including the ones that can be directly dervied from existing state or props. This pattern can often lead to some nasty and hard to debug state synchronization bugs, which can be easily avoided by computing those properties on the fly instead.</p>
<p>Let's try to understand with an example of what I meant in the above paragraph. Consider this example where we have a form with a field for name, and a submit button, which remains disabled until the user enters a name (A much better way to do this would be using <a href="https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation">HTML form validation</a>, but don't forget this is a contrived example 😅). Right now, it has two state variables, one for keeping track of the name and the other for error (<a href="https://codesandbox.io/s/simple-name-state-9l8c5?file=/src/App.js">Try it out on codesandbox</a>) -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">App</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>name<span class="token punctuation">,</span> setName<span class="token punctuation">]</span> <span class="token operator">=</span> React<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><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>hasError<span class="token punctuation">,</span> setHasError<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</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 /><br /> <span class="token keyword">const</span> <span class="token function-variable function">handleNameChange</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> value <span class="token operator">=</span> event<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">;</span><br /> <span class="token function">setName</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">setHasError</span><span class="token punctuation">(</span>value<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length <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 /><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">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>App<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>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>form-item<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>label</span><span class="token punctuation">></span></span><span class="token plain-text">Name:</span><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 attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<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>name<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>handleNameChange<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>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>button</span> <span class="token attr-name">disabled</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>hasError<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>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>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></code></pre>
<p>Now, this might seem fine at first but imagine if a new requirement comes in, and you need to add a new field to the form, which is also required, so you would now need to update the <code>hasError</code> value there as well to keep the value in sync.</p>
<p>To exemplify the above problem, let's extend our above example by adding a field for age, and let's imagine the age needs to be greater than 18 years. (<a href="https://codesandbox.io/s/simple-name-with-gender-state-7wroc?file=/src/App.js">Try it out on codesandbox)</a></p>
<pre class="language-js"><code class="language-js"><span class="highlight-line"><span class="token keyword">function</span> <span class="token function">App</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br /><span class="highlight-line"> <span class="token keyword">const</span> <span class="token punctuation">[</span>name<span class="token punctuation">,</span> setName<span class="token punctuation">]</span> <span class="token operator">=</span> React<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><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">const</span> <span class="token punctuation">[</span>age<span class="token punctuation">,</span> setAge<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</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></mark><br /><span class="highlight-line"> <span class="token keyword">const</span> <span class="token punctuation">[</span>hasError<span class="token punctuation">,</span> setHasError<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</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></span><br /><span class="highlight-line"></span><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">const</span> <span class="token function-variable function">handleErrorUpdate</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> currentName<span class="token punctuation">,</span> currentAge <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token function">setHasError</span><span class="token punctuation">(</span>currentName<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length <span class="token operator"><</span> <span class="token number">1</span> </mark><br /><mark class="highlight-line highlight-line-active"> <span class="token operator">||</span> currentAge <span class="token operator"><</span> <span class="token number">18</span> </mark><br /><mark class="highlight-line highlight-line-active"> <span class="token operator">||</span> currentAge <span class="token operator">></span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token punctuation">}</span><span class="token punctuation">;</span></mark><br /><span class="highlight-line"></span><br /><span class="highlight-line"> <span class="token keyword">const</span> <span class="token function-variable function">handleNameChange</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span></span><br /><span class="highlight-line"> <span class="token keyword">const</span> value <span class="token operator">=</span> event<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">;</span></span><br /><span class="highlight-line"> <span class="token function">setName</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"> <span class="token function">handleErrorUpdate</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">currentName</span><span class="token operator">:</span> value<span class="token punctuation">,</span> <span class="token literal-property property">currentAge</span><span class="token operator">:</span> age <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"> <span class="token punctuation">}</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"></span><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">const</span> <span class="token function-variable function">handleAgeChange</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">const</span> value <span class="token operator">=</span></mark><br /><mark class="highlight-line highlight-line-active"> event<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span> </mark><br /><mark class="highlight-line highlight-line-active"> <span class="token operator">?</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span> </mark><br /><mark class="highlight-line highlight-line-active"> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token function">setAge</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token function">handleErrorUpdate</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">currentName</span><span class="token operator">:</span> name<span class="token punctuation">,</span> <span class="token literal-property property">currentAge</span><span class="token operator">:</span> value <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token punctuation">}</span><span class="token punctuation">;</span></mark><br /><span class="highlight-line"></span><br /><span class="highlight-line"> <span class="token keyword">return</span> <span class="token punctuation">(</span></span><br /><span class="highlight-line"> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token string">"App"</span><span class="token operator">></span></span><br /><span class="highlight-line"> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token string">"form-item"</span><span class="token operator">></span></span><br /><span class="highlight-line"> <span class="token operator"><</span>label<span class="token operator">></span>Name<span class="token operator">:</span><span class="token operator"><</span><span class="token operator">/</span>label<span class="token operator">></span></span><br /><span class="highlight-line"> <span class="token operator"><</span>input type<span class="token operator">=</span><span class="token string">"text"</span> value<span class="token operator">=</span><span class="token punctuation">{</span>name<span class="token punctuation">}</span> onChange<span class="token operator">=</span><span class="token punctuation">{</span>handleNameChange<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span></span><br /><span class="highlight-line"> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span></span><br /><mark class="highlight-line highlight-line-active"> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token string">"form-item"</span><span class="token operator">></span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token operator"><</span>label<span class="token operator">></span>Age<span class="token operator">:</span><span class="token operator"><</span><span class="token operator">/</span>label<span class="token operator">></span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token operator"><</span>input type<span class="token operator">=</span><span class="token string">"number"</span> value<span class="token operator">=</span><span class="token punctuation">{</span>age<span class="token punctuation">}</span> onChange<span class="token operator">=</span><span class="token punctuation">{</span>handleAgeChange<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span></mark><br /><span class="highlight-line"> <span class="token operator"><</span>button disabled<span class="token operator">=</span><span class="token punctuation">{</span>hasError<span class="token punctuation">}</span><span class="token operator">></span>Submit<span class="token operator"><</span><span class="token operator">/</span>button<span class="token operator">></span></span><br /><span class="highlight-line"> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span></span><br /><span class="highlight-line"> <span class="token punctuation">)</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre>
<p>For the sake of DRY, I have moved the error update logic to a separate function. If we go by this logic, then we would have to call the <code>handleErrorUpdate</code> method every time we add or update a required form field. If we miss updating the error state, it can cause the <code>hasError</code> state to go out of sync and result in hard to debug errors for complex applications.</p>
<p>Now instead of doing it like this, we can calculate the error in a <code>useEffect</code> and set the error state there like this (<a href="https://codesandbox.io/s/simple-name-with-gender-state-use-effect-5zu27?file=/src/App.js">Try it out on codesandbox</a>) -</p>
<pre class="language-jsx"><code class="language-jsx">React<span class="token punctuation">.</span><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">setHasError</span><span class="token punctuation">(</span>name<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length <span class="token operator"><</span> <span class="token number">1</span> <span class="token operator">||</span> age <span class="token operator"><</span> <span class="token number">18</span> <span class="token operator">||</span> age <span class="token operator">></span> <span class="token number">100</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>name<span class="token punctuation">,</span> age<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>And yes, this does simplify the code by removing the unnecessary code for calling the error handler wherever the relevant state is supposed to be updated. Also, if you're using <a href="https://www.npmjs.com/package/eslint-plugin-react-hooks">eslint-plugin-react-hooks</a> (which you should definitely use), it would warn you if you add some new variable to the <code>setHasError</code> logic and don't include it in the dependency array.</p>
<p>But what if there's something even better? As you can see, the <code>hasError</code> state is just being derived from the existing state that we already have in our component. So instead of maintaining a separate state for it, we can calculate it on the fly with every render like this -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> hasError <span class="token operator">=</span> name<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length <span class="token operator"><</span> <span class="token number">1</span> <span class="token operator">||</span> age <span class="token operator"><</span> <span class="token number">18</span> <span class="token operator">||</span> age <span class="token operator">></span> <span class="token number">100</span><span class="token punctuation">;</span></code></pre>
<p>This way, we wouldn't need to worry about <code>hasError</code> getting out of sync by introducing a new dependency. Also, it is a lot easier to understand and saves us an additional render. (<a href="https://codesandbox.io/s/simple-name-with-gender-state-final-8znyv?file=/src/App.js">Try it out on codesandbox)</a></p>
<p>One thing you might argue about is performance. Since we calculate this computed state property on every render wouldn't it be less performant than calculating it only when one of the dependent variables changes as we did previously?</p>
<p>The answer is yes, it depends. It could be if it's some computationally expensive calculation and variable that the calculation relies on don't change that often with every render, but guess what the <a href="https://reactjs.org/docs/hooks-reference.html#usememo"><code>React.useMemo</code></a> hook is built exactly for these kinds of situations.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>Although for most cases like the example we are working on, using <code>React.useMemo</code> would be a case of premature optimization because the variables depend on all the variables that cause a re-render, and it's not that expensive. You can also check out my article on <a href="https://prateeksurana.me/blog/when-should-you-memoize-in-react/">when you should memoize</a> if you are interested in exploring this topic further.</p>
</aside>
<h2 class="relative">
<a id="what-about-derived-state-from-props" href="https://prateeksurana.me/blog/why-you-should-avoid-using-state-for-computed-properties/#what-about-derived-state-from-props" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
What about derived state from props?
</a>
</h2>
<p>For props as well, you can rely on the same pattern of directly deriving the desired values from props as we discussed in the previous section and avoid managing the state internally to steer clear of any state synchronization problems. So for our previous example, if the name and age values were provided via props from the parent component, our implementation for <code>hasError</code> would have remained the same.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">App</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> name<span class="token punctuation">,</span> age <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span><br /> <span class="token keyword">const</span> hasError <span class="token operator">=</span> name<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length <span class="token operator"><</span> <span class="token number">1</span> <span class="token operator">||</span> age <span class="token operator"><</span> <span class="token number">18</span> <span class="token operator">||</span> age <span class="token operator">></span> <span class="token number">100</span><span class="token punctuation">;</span><br /> <span class="token operator">...</span><br /><span class="token punctuation">}</span> </code></pre>
<p>Although there are some edge cases where you need the props just for initial values and then manage them via some state internally.</p>
<p>Let's try to understand when you might run into a situation like this, with <a href="https://github.com/facebook/react/issues/15523">an example from this issue on the React repo</a> asking the same question.</p>
<p>In this example, we have a list of items and every item has an edit button next to it, clicking on which opens an editor on the side where the user can edit the item properties and can save or cancel the updates. Currently, the item properties are passed as props to the editor component, which it then uses as initial values for its internal state, which handles the editor inputs.</p>
<iframe src="https://codesandbox.io/embed/dervied-state-from-props-problem-2r0js?fontsize=12&hidenavigation=1&theme=dark&view=preview" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="dervied-state-from-props-problem" loading="lazy" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p>This is what the code for the Editable list looks like -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> React<span class="token punctuation">,</span> <span class="token punctuation">{</span> useState <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> <span class="token function-variable function">StatefulEditor</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>name<span class="token punctuation">,</span> setName<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>item<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>description<span class="token punctuation">,</span> setDescription<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>item<span class="token punctuation">.</span>description<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 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>editor<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">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></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>name<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 punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setName</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><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>input</span><br /> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></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>description<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 punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setDescription</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><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">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>button-container<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><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><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span><br /> props<span class="token punctuation">.</span><span class="token function">onConfirm</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> props<span class="token punctuation">.</span>item<span class="token punctuation">.</span>id<span class="token punctuation">,</span> name<span class="token punctuation">,</span> description <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span></span><br /> <span class="token punctuation">></span></span><span class="token plain-text"><br /> Ok<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 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>props<span class="token punctuation">.</span>onCancel<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">Cancel</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>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><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> <span class="token function-variable function">EditableList</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>itemUnderEdit<span class="token punctuation">,</span> setItemUnderEdit<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 /><br /> <span class="token keyword">const</span> <span class="token function-variable function">closeEditor</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">setItemUnderEdit</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 function-variable function">saveChangedItem</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">itemToSave</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> props<span class="token punctuation">.</span><span class="token function">handleItemUpdate</span><span class="token punctuation">(</span>itemToSave<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">closeEditor</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 /><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">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>editable-list<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>div</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>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">item</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">key</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>item<span class="token punctuation">.</span>id<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>item<span class="token punctuation">.</span>name<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 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">setItemUnderEdit</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">Edit</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>div</span><span class="token punctuation">></span></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 plain-text"><br /> </span><span class="token punctuation">{</span>itemUnderEdit <span class="token operator">&&</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">StatefulEditor</span></span><br /> <span class="token attr-name">item</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>itemUnderEdit<span class="token punctuation">}</span></span><br /> <span class="token attr-name">onConfirm</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>saveChangedItem<span class="token punctuation">}</span></span><br /> <span class="token attr-name">onCancel</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>closeEditor<span class="token punctuation">}</span></span><br /> <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>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><br /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> EditableList<span class="token punctuation">;</span></code></pre>
<p>If you click on 'Ok' or 'Cancel' to close the editor after editing an item and then opening another item, this seems to be working fine. But try clicking on the edit button for any other item without closing the editor. You will notice the problem with this approach. The values in the editor remain the same even though the props have changed.</p>
<p>So why does this happen? It is because the state only gets initialized during the initial component mount, and even though the change in props cause a re-render, our <code>useState</code> cannot be re-initialized. This is one of the reasons why the <a href="https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#anti-pattern-erasing-state-when-props-change">React docs recommend avoiding this pattern</a>.</p>
<p>But for our case, we have to rely on this pattern, so what can we do to keep the state and props in sync for this case?</p>
<p>Well, as it turns out, there are a few ways of fixing it. One is that you can add a <code>key</code> prop with the value as the id of your item to the Editor component. This would cause React to <a href="https://stackoverflow.com/a/43892905/8252081">unmount the previous instance of the component and remount it</a> causing our state to be initialized again whenever the props, i.e. the key with item id, change.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="highlight-line"><span class="token operator">...</span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">StatefulEditor</span></span><br /><mark class="highlight-line highlight-line-active"> <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>item<span class="token punctuation">.</span>id<span class="token punctuation">}</span></span></mark><br /><span class="highlight-line"> <span class="token attr-name">item</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>itemUnderEdit<span class="token punctuation">}</span></span></span><br /><span class="highlight-line"> <span class="token attr-name">onConfirm</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>saveChangedItem<span class="token punctuation">}</span></span></span><br /><span class="highlight-line"> <span class="token attr-name">onCancel</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>closeEditor<span class="token punctuation">}</span></span></span><br /> <span class="token punctuation">/></span></span><br /><span class="highlight-line"><span class="token operator">...</span></span></code></pre>
<p>This should suffice for most situations. If your component tree is expensive, then the above method can slightly affect performance because your component gets unmounted and remounted again. So what the <a href="https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops">React docs recommend</a> is that you update the state during rendering, and React will re-run the component with updated state immediately after the current render. So in our case, this is what it would look like -</p>
<pre class="language-jsx"><code class="language-jsx"><span class="highlight-line"><span class="token keyword">const</span> <span class="token function-variable function">StatefulEditor</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></span><br /><span class="highlight-line"> <span class="token keyword">const</span> <span class="token punctuation">[</span>name<span class="token punctuation">,</span> setName<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>item<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"> <span class="token keyword">const</span> <span class="token punctuation">[</span>description<span class="token punctuation">,</span> setDescription<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>item<span class="token punctuation">.</span>description<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">const</span> <span class="token punctuation">[</span>id<span class="token punctuation">,</span> setId<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>item<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span></mark><br /><mark class="highlight-line highlight-line-active"></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">if</span> <span class="token punctuation">(</span>props<span class="token punctuation">.</span>item<span class="token punctuation">.</span>id <span class="token operator">!==</span> id<span class="token punctuation">)</span> <span class="token punctuation">{</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token function">setName</span><span class="token punctuation">(</span>props<span class="token punctuation">.</span>item<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token function">setId</span><span class="token punctuation">(</span>props<span class="token punctuation">.</span>item<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token function">setDescription</span><span class="token punctuation">(</span>props<span class="token punctuation">.</span>item<span class="token punctuation">.</span>description<span class="token punctuation">)</span><span class="token punctuation">;</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token punctuation">}</span></mark><br /><span class="highlight-line"></span><br /><span class="highlight-line"> <span class="token keyword">return</span> <span class="token punctuation">(</span></span><br /><span class="highlight-line"> <span class="token operator">...</span></span><br /><span class="highlight-line"> <span class="token punctuation">)</span></span><br /><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre>
<p>Here is the sandbox with the above fix, and if you check again you'll see that the issue is gone now -</p>
<iframe src="https://codesandbox.io/embed/dervied-state-from-props-solution-bmpsy?fontsize=12&hidenavigation=1&theme=dark&view=preview" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="dervied-state-from-props-solution" loading="lazy" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p>Since this is a contrived example, it doesn't look great. In a real-world scenario, you might want to use <a href="https://reactjs.org/docs/hooks-reference.html#usereducer"><code>useReducer</code></a> if you have too many individual states to manage like this.</p>
<h2 class="relative">
<a id="conclusion" href="https://prateeksurana.me/blog/why-you-should-avoid-using-state-for-computed-properties/#conclusion" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Conclusion
</a>
</h2>
<p>So I hope this article helped you understand why creating state variables for computed properties is not a good idea and how you can compute them on the fly instead and optimize via <code>useMemo</code> if needed. We also saw how you could sync state with props in some edge cases where you don't have any other reasonable option. You can also check out <a href="https://kentcdodds.com/blog/dont-sync-state-derive-it">this article by Kent C. Dodd's</a>, which talks about the same topic, with some additional thoughts on how you can handle it in other libraries as well.</p>
<p>Also, let me know in the comments section below if I have missed something or if you have better alternative ideas for the examples I used in this article.</p>
What is the difference between extends and plugins in ESLint configLearn how ESLint works, what are the role of plugins and extends keys in your ESLint config and how they make ESLint an extremely configurable and versatile JavaScript Linter.2021-09-11T00:00:00Z2021-09-11T00:00:00Zhttps://prateeksurana.me/blog/difference-between-eslint-extends-and-plugins/<p>When setting up ESLint in my projects, I often used to get confused about the difference between <code>eslint-plugin-xxx</code> and <code>eslint-config-xxx</code> npm modules, and why some ask you to add something to the <code>plugins</code> key in your project's ESLint config while others ask you to modify the <code>extends</code> key. If you have also gone through the same confusion or if you want to learn about how ESLint works then this article is for you.</p>
<h2 class="relative">
<a id="what-is-eslint" href="https://prateeksurana.me/blog/difference-between-eslint-extends-and-plugins/#what-is-eslint" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
What is ESLint?
</a>
</h2>
<p>Now I know if you searched for the difference than you probably already know what ESLint is and what it does. But let's take a quick look anyways just so we're on the same page.</p>
<p>ESLint is an open-source JavaScript Linting utility. Its job is to statically analyze and fix problematic patterns in your JavaScript code or code that doesn't adhere to some style guidelines that you would want to be followed throughout your whole project. The project owner can choose the rules they want to enforce via a configuration file <code>.eslintrc.{js,yml,json}</code> in the root directory of their project.</p>
<p>What makes it so special is that apart from having some really good built-in rules it also allows developers to create their own linting rules and have those rules completely pluggable. So developers can add any number of these pluggable rules and enforce them according to their needs, via the ESLint config file in their project.</p>
<h2 class="relative">
<a id="plugins" href="https://prateeksurana.me/blog/difference-between-eslint-extends-and-plugins/#plugins" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Plugins
</a>
</h2>
<p>Although ESLint ships with <a href="https://eslint.org/docs/rules/">some good set of rules</a>, usually they are not enough to cover all the needs for your project, especially if you're building with libraries and frameworks like React, Vue, etc. ESLint plugins allow you to add custom rules according to the needs of your project. Plugins are published as npm modules with names in the format of <code>eslint-plugin-<plugin-name></code>.</p>
<p>To use the plugin, you need to first install it via npm, and then you can add it to your <code>eslintrc</code> configuration via the plugins key. For example, if you want to use a plugin called <code>eslint-plugin-my-awesome-plugin</code>, you can add it to your configuration file like this -</p>
<pre class="language-json"><code class="language-json"><span class="token comment">// .eslintrc</span><br /><br /><span class="token punctuation">{</span><br /> <span class="token property">"plugins"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"my-awesome-plugin"</span><span class="token punctuation">]</span> <span class="token comment">// The "eslint-plugin" suffix </span><br /> <span class="token comment">// can be ommited</span><br /><span class="token punctuation">}</span></code></pre>
<p>Keep in mind that, adding a plugin does not mean that all the rules for the plugins will be applied automatically, you still need to individually apply each and every rule you would want to use with that plugin, with the rules object in your config file -</p>
<pre class="language-json"><code class="language-json"><span class="token comment">// .eslintrc</span><br /><br /><span class="token punctuation">{</span><br /> <span class="token property">"rules"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"eqeqeq"</span><span class="token operator">:</span> <span class="token string">"off"</span><span class="token punctuation">,</span><br /> <span class="token property">"curly"</span><span class="token operator">:</span> <span class="token string">"error"</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>I am not covering how rules work in ESLint because that would be slightly out of scope of this article, if you're interested in how to configure rules you can checkout this <a href="https://eslint.org/docs/user-guide/configuring/rules">awesome explanation in the ESLint docs.</a></p>
</aside>
<p>But configuring each and every rule you want to use would be gruesome, wouldn't it be nice if you could just use some of the recommended set of rules by the plugin owner or the by the framework you are using. Well that's where our next section comes in.</p>
<h2 class="relative">
<a id="shareable-configs" href="https://prateeksurana.me/blog/difference-between-eslint-extends-and-plugins/#shareable-configs" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Shareable configs
</a>
</h2>
<p>The ESLint configs we create for our project are an important part of our project and more often than not we have multiple projects that need more or less the same configs. So ESLint lets you share your config by <a href="https://eslint.org/docs/developer-guide/shareable-configs#publishing-a-shareable-config">allowing you to publish it to npm</a>. Similar to plugins shareable configs are also published with names in the format of <code>eslint-config-<config-name></code>.</p>
<p>To use the shareable config, you need to install it from npm similar to what we saw with plugins and then you can extend the ESLint config of your project with the <code>extends</code> key, so if you have installed a config called <code>eslint-config-awesome</code> then you can use it in your config like this -</p>
<pre class="language-json"><code class="language-json"><span class="token comment">// .eslintrc</span><br /><br /><span class="token punctuation">{</span><br /> <span class="token property">"extends"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"awesome"</span><span class="token punctuation">]</span> <span class="token comment">// The "eslint-config" suffix </span><br /> <span class="token comment">// can be omitted here as well</span><br /><span class="token punctuation">}</span></code></pre>
<p>And yes you can extend from multiple configs by adding them to the array, and if the configs modify same rules then the rules of the preceding config would be overwritten by the succeeding one, so the order does matter in these cases.</p>
<p>Note that shareable configs aren't just meant for sharing rulesets, they can be full fledged configs with their own plugins, formatters etc. and can also even extend from other configs.</p>
<p>You might already be extending some popular configs in your project like the <a href="https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb"><code>eslint-config-airbnb</code></a> which includes plugins like <code>eslint-plugin-react</code> , <code>eslint-plugin-import</code> etc. and enforce rules based on the Airbnb JavaScript style guide. Although you still need to install the plugins via npm because they are a <a href="https://flaviocopes.com/npm-peer-dependencies/">peer dependency</a> of <code>eslint-config-aribnb</code> .</p>
<h3 class="relative">
<a id="plugins-with-configs" href="https://prateeksurana.me/blog/difference-between-eslint-extends-and-plugins/#plugins-with-configs" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Plugins with configs
</a>
</h3>
<p>We saw earlier how plugins allow you to add more rules for linting your project, and how you need to add the rules you want to use in your config or extend from some other shareable config that has the rules.</p>
<p>Guess what plugins can also come with different sets of shareable configs and you can use any of them according to your needs in your project.</p>
<p>You can use these configs that come with your plugins by with the <code>plugin:</code> prefix. For instance let's say you are using a plugin called <code>eslint-plugin-my-awesome-plugin</code> and it comes with a config called <code>recommended-config</code> . You can then add <code>plugin:my-awesome-plugin/recommended-config</code> to the <code>extends</code> key in your config to extend from that shareable config.</p>
<p>Let's also take the example of <a href="https://github.com/yannickcr/eslint-plugin-react"><code>eslint-plugin-react</code></a>. If you see the <a href="https://github.com/yannickcr/eslint-plugin-react/blob/master/index.js#L118-L179"><code>configs</code> key in its code</a>, you'll find that it has three configs available <code>recommended</code> , <code>all</code> and <code>jsx-runtime</code> .</p>
<p>Now suppose I just want to use the <a href="https://github.com/yannickcr/eslint-plugin-react/blob/9c1aee5eab8776b94d9d46cbcfa4bb53a8b4e175/index.js#L119-L152"><code>recommended</code></a> config of this plugin, which enforces the rules recommend for React good practices, this is all that I need to add in my project's ESLint config -</p>
<pre class="language-json"><code class="language-json"><span class="token comment">// .eslintrc</span><br /><br /><span class="token punctuation">{</span><br /> <span class="token property">"extends"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"plugin:react/recommended"</span><span class="token punctuation">]</span><br /><span class="token punctuation">}</span></code></pre>
<p>Notice how you don't even need to add the <code>plugins</code> key with <code>eslint-plugin-react</code> in it because it is already included in the recommended config.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>The above example is just to demonstrate how you can use shareable configs that come with the plugin, if you are actually using this plugin than you should include <code>eslint:recommended</code> config as well for best results as mentioned in the <a href="https://eslint.org/docs/user-guide/configuring/configuration-files#extending-configuration-files">project's README</a>.</p>
</aside>
<h2 class="relative">
<a id="conclusion" href="https://prateeksurana.me/blog/difference-between-eslint-extends-and-plugins/#conclusion" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Conclusion
</a>
</h2>
<p>So to summarize in this article we saw a brief overview of what ESLInt is and how ESLint plugins provide you with custom rules that you can individually apply according to your needs, and how the <code>extends</code> key in your configs lets you extend from other shareable configs. We also saw that how you can load the configuration files that may be provided by the plugin to apply the rules that the authors think are logically grouped or recommended by the community.</p>
A JavaScript developer’s guide to browser cookiesLearn how browser cookies work and how you can access, manipulate and control their visibiliy across browser with JavaScript.2021-09-26T00:00:00Z2021-09-26T00:00:00Zhttps://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/<p>Whether you like them or not, cookies are an essential part of modern web development. Yet, they are often misunderstood amongst developers and sometimes used where they shouldn’t have been or are missing attributes that can make your website vulnerable. This article will discuss how browser cookies work, how you can access and manipulate them both from the client and server, and how to control their visibility in the browser using their attributes and make your cookies more secure. We'll also take a brief look at privacy concerns related to third-party cookies at the end.</p>
<h2 class="relative">
<a id="what-are-cookies-and-how-do-they-work" href="https://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/#what-are-cookies-and-how-do-they-work" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
What are cookies, and how do they work?
</a>
</h2>
<p>A browser cookie is a small piece of data stored in the browser that can be created either by the client-side JavaScript or by the server during an HTTP request. The browser can then send that cookie back with later requests to the same server and/or let the client-side JavaScript of the webpage access cookie when the user revisits the page.</p>
<p>Cookies are generally used for session management, personalization (themes or similar settings), and tracking user behavior across websites.</p>
<p>There was a time when cookies were used for all kinds of client-side storage. But there's was an issue with this approach; since all the cookies for a domain are sent with every request to the server on that domain, they could significantly affect performance, especially with low bandwidth mobile data connections. For the same reason, browsers also typically set limits for the size of the cookie and the number of cookies allowed for a particular domain (Typically 4kb and 20 per domain).</p>
<p>With the modern web, we got the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">Web Storage APIs</a> (<code>localStorage</code> and <code>sessionStorage</code>) for client-side storage, which allow browsers to store client-side data in the form of key-value pairs. So if you want to persist data only on the client-side, then using these APIs would be a much better way to do it because they are much more intuitive and easy to use than cookies and can store much more data (usually up to 5MB).</p>
<h2 class="relative">
<a id="setting-and-accessing-cookies" href="https://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/#setting-and-accessing-cookies" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Setting and accessing cookies
</a>
</h2>
<p>You can set and access the cookies both via the server and the client. Cookies also have various attributes that decide where and how they can be accessed and modified, which we will discuss in detail in the next section. First, let's look at how you access and manipulate the cookies on the client and the server.</p>
<h3 class="relative">
<a id="client-browser" href="https://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/#client-browser" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Client (Browser)
</a>
</h3>
<p>The JavaScript that is downloaded and is executed on the browser whenever you visit a website is generally called the client-side JavaScript. It can access the cookies via the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document">Document</a> property <code>cookie</code>, i.e., you can read all the cookies that are accessible on the current location with <code>document.cookie</code>. It gives you a string containing a semicolon-separated list of cookies in <code>key=value</code> format.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> allCookies <span class="token operator">=</span> document<span class="token punctuation">.</span>cookie<span class="token punctuation">;</span><br /><span class="token comment">// The value of allCookies would be something like</span><br /><span class="token comment">// "cookie1=value1; cookie2=value2"</span></code></pre>
<p>Similarly, to set a cookie, we need to set the value of <code>document.cookie</code> setting the cookie is also done with a string in <code>key=value</code> format with the attributes separated by a semicolon.</p>
<pre class="language-js"><code class="language-js">document<span class="token punctuation">.</span>cookie <span class="token operator">=</span> <span class="token string">"hello=world; domain=example.com; Secure"</span><span class="token punctuation">;</span><br /><span class="token comment">// Sets a cookie with key as hello and value as world, with</span><br /><span class="token comment">// two attributes SameSite and Secure (We will be discussing these</span><br /><span class="token comment">// attributes in the next section)</span></code></pre>
<p>Just so you're not confused, the above statement does not override any existing cookies; it just creates a new one or updates the value of an existing one if a cookie with the same name already exists.</p>
<p>Now I know this is not the cleanest API you have ever seen. That's why I would recommend using a wrapper or a library like <a href="https://github.com/js-cookie/js-cookie">js-cookie</a> to handle client cookies.</p>
<pre class="language-js"><code class="language-js">Cookies<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'hello'</span><span class="token punctuation">,</span> <span class="token string">'world'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">domain</span><span class="token operator">:</span> <span class="token string">'example.com'</span><span class="token punctuation">,</span> <span class="token literal-property property">secure</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 />Cookies<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'hello'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// -> world</span></code></pre>
<p>Not only does it provide a clean API for CRUD operations on cookies, but it also supports TypeScript, thus helping you avoid any spelling mistakes with the attributes.</p>
<h3 class="relative">
<a id="server-nodejs" href="https://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/#server-nodejs" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Server (Node.js)
</a>
</h3>
<p>The server can access and modify cookies via an HTTP request's request and response headers respectively. Whenever the browser sends an HTTP request to the server, it attaches all the relevant cookies to that site with the <code>cookie</code> header. Check the request headers of probably any web app you use, and you'll find the cookies being sent to the server with request headers as a semicolon-separated string.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/cookie-sent-to-server-with-headers-480.webp 480w, https://prateeksurana.me/img/cookie-sent-to-server-with-headers-768.webp 768w, https://prateeksurana.me/img/cookie-sent-to-server-with-headers-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/cookie-sent-to-server-with-headers-480.jpeg 480w, https://prateeksurana.me/img/cookie-sent-to-server-with-headers-768.jpeg 768w, https://prateeksurana.me/img/cookie-sent-to-server-with-headers-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/cookie-sent-to-server-with-headers-768.jpeg" width="768" height="337" alt="Cookies being sent to the server with request headers" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>You can then read these cookies on the server from the request headers. For example, if you are using Node.js on the server, you can read them from the request object like the snippet below, and you will get the semicolon-separated <code>key=value</code> pairs similar to what we saw in the previous section.</p>
<pre class="language-js"><code class="language-js">http<span class="token punctuation">.</span><span class="token function">createServer</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">request<span class="token punctuation">,</span> response</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">var</span> cookies <span class="token operator">=</span> request<span class="token punctuation">.</span>headers<span class="token punctuation">.</span>cookie<span class="token punctuation">;</span><br /> <span class="token comment">// "cookie1=value1; cookie2=value2"</span><br /> <span class="token operator">...</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">8124</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Similarly, to set a cookie, you can add a <code>Set-Cookie</code> header in the response headers, with cookie in the <code>key=value</code> format and attributes separated by semicolon, if any. This is how you can do it in Node.js -</p>
<pre class="language-js"><code class="language-js">response<span class="token punctuation">.</span><span class="token function">writeHead</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><br /> <span class="token string-property property">'Set-Cookie'</span><span class="token operator">:</span> <span class="token string">'mycookie=test; domain=example.com; Secure'</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Also, chances are you won't be using plain Node.js; instead, you would be using it with a web framework like Express. Accessing and modifying cookies gets much easier with Express. For reading, add a middleware like <a href="https://www.npmjs.com/package/cookie-parser">cookie-parser</a>, and you get all the cookies in form of an JavaScript object with <a href="https://expressjs.com/en/4x/api.html#req.cookies"><code>req.cookies</code></a>. You can also use the built-in <a href="https://expressjs.com/en/4x/api.html#res.cookie"><code>res.cookie()</code></a> method that comes with Express for setting cookies.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'express'</span><span class="token punctuation">)</span><br /><span class="token keyword">var</span> cookieParser <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'cookie-parser'</span><span class="token punctuation">)</span><br /> <br /><span class="token keyword">var</span> app <span class="token operator">=</span> <span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br />app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token function">cookieParser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <br />app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</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">'Cookies: '</span><span class="token punctuation">,</span> req<span class="token punctuation">.</span>cookies<span class="token punctuation">)</span><br /> <span class="token comment">// Cookies: { cookie1: 'value1', cookie2: 'value2' }</span><br /><br /> res<span class="token punctuation">.</span><span class="token function">cookie</span><span class="token punctuation">(</span><span class="token string">'name'</span><span class="token punctuation">,</span> <span class="token string">'tobi'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">domain</span><span class="token operator">:</span> <span class="token string">'example.com'</span><span class="token punctuation">,</span> <span class="token literal-property property">secure</span><span class="token operator">:</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><br /> <br />app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">8080</span><span class="token punctuation">)</span></code></pre>
<p>And yes, all this is supported with TypeScript, so there is no chance of typos on the server as well.</p>
<h2 class="relative">
<a id="attributes-of-cookies" href="https://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/#attributes-of-cookies" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Attributes of cookies
</a>
</h2>
<p>Now that you know how you can set and access cookies let's dive into the attributes of cookies.
Apart from name and value cookies also have attributes that control a variety of aspects which include the security aspects, lifetime and where and how they would be accessible in the browser.</p>
<h3 class="relative">
<a id="domain" href="https://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/#domain" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Domain
</a>
</h3>
<p>According to <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_where_cookies_are_sent">MDN</a>, the domain attribute tells the browser which hosts are allowed to access a cookie. If unspecified, it defaults to the same host that set the cookie. So when accessing a cookie using client-side JavaScript, only the cookies that have the domain same as the one in the URL bar are accessible. Similarly, only the cookies that share the same domain as the HTTP request's domain are sent along with the request headers to the server.</p>
<p>Remember that having this attribute doesn't mean that you can set cookies for any domain because that would obviously be a huge security risk. (Imagine an attacker on <a href="http://evil.com/">evil.com</a> creating the cookies for your site <a href="http://awesome.com/">awesome.com</a>.)</p>
<p>So the only reason this attribute exists is to make the domain less restrictive and to make the cookie accessible on subdomains. For example, if your current domain is <code>abc.xyz.com</code>, and when setting a cookie if you don't specify the domain attribute, it would default to <code>abc.xyz.com</code>, and the cookies would be restricted only to that domain. But, you might want the same cookie to be available on other subdomains as well, so you can set <code>Domain=xyz.com</code> to make it available on other subdomains like <code>def.xyz.com</code> and the primary domain <code>xyz.com</code>.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/cookie-domain-attribute-480.webp 480w, https://prateeksurana.me/img/cookie-domain-attribute-768.webp 768w, https://prateeksurana.me/img/cookie-domain-attribute-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/cookie-domain-attribute-480.jpeg 480w, https://prateeksurana.me/img/cookie-domain-attribute-768.jpeg 768w, https://prateeksurana.me/img/cookie-domain-attribute-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/cookie-domain-attribute-768.jpeg" width="768" height="402" alt="Domain attribute allowing cookies to be accessed via subdomains" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Also, this does not mean that you can set any domain value for cookies; TLDs like <code>.com</code> and pseudo TLDs like <code>.co.uk</code> would be ignored by a well-secured browser. Initially, browser vendors maintained lists of such public domains internally, which inevitably caused inconsistent behavior across browsers.</p>
<p>To tackle this, the Mozilla Foundation started a project called the <a href="http://publicsuffix.org/">Public Suffix List</a> that records all these public domains and shares them across vendors. This list also includes services like <code>github.io</code> and <code>vercel.app</code> that restricts anyone from setting cookies for these domains, making <code>abc.vercel.app</code> and <code>def.vercel.app</code> count as separate sites with their own separate set of cookies.</p>
<h3 class="relative">
<a id="path" href="https://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/#path" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Path
</a>
</h3>
<p>This attribute specifies the path in the request URL that must be present to access the cookie. Apart from restricting cookies to domains, you can also restrict them via path. A cookie with the path attribute as <code>Path=/store</code> would only be accessible on the path <code>/store</code> and its subpaths <code>/store/cart</code>, <code>/store/gadgets</code>, etc.</p>
<h3 class="relative">
<a id="expires" href="https://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/#expires" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Expires
</a>
</h3>
<p>This attribute allows setting an expiration date after which the cookies are destroyed. This can come in handy when you are using a cookie to check if the user has been shown an interstitial ad, and you can set the cookie to expire in a month so that the ad can be shown again after a month.</p>
<p>And guess what? It is also used to remove cookies by <a href="https://stackoverflow.com/a/53573622/8252081">setting the <code>Expires</code> date in the past</a>.</p>
<h3 class="relative">
<a id="secure" href="https://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/#secure" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Secure
</a>
</h3>
<p>A cookie with the <code>Secure</code> attribute is only sent to the server over the secure HTTPS protocol and never over the HTTP protocol (except on localhost). This helps in preventing <a href="https://developer.mozilla.org/en-US/docs/Glossary/MitM">Man in the Middle attacks</a> by making the cookie inaccessible over unsecured connections. Unless you are serving your websites via an unsecured HTTP connection (<a href="https://web.dev/why-https-matters/">which you shouldn't</a>) you should always use this attribute with all your cookies.</p>
<h3 class="relative">
<a id="httponly" href="https://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/#httponly" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
HTTPOnly
</a>
</h3>
<p>This attribute, as the name probably suggests, allows cookies to be only accessible via the server. So, only the server can set them via the response headers, the browser will then send them to the server with every subsequent request’s headers, and they won’t be accessible via the client-side JavaScript.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/cookie-httponly-attribute-480.webp 480w, https://prateeksurana.me/img/cookie-httponly-attribute-768.webp 768w, https://prateeksurana.me/img/cookie-httponly-attribute-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/cookie-httponly-attribute-480.jpeg 480w, https://prateeksurana.me/img/cookie-httponly-attribute-768.jpeg 768w, https://prateeksurana.me/img/cookie-httponly-attribute-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/cookie-httponly-attribute-768.jpeg" width="768" height="515" alt="HTTPOnly attribute example" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>This can partially help secure cookies with sensitive information, like auth tokens, from <a href="https://owasp.org/www-community/attacks/xss/">XSS attacks</a> since any client-side script won't be able to read the cookies. But remember it does not guarantee complete security from XSS attacks. Its because, if the attacker can execute third-party scripts on your website, then they might not be able to access the cookies, but instead, they can directly execute any relevant API requests to your server and the browser will readily attach your secure HTTPOnly cookies with the request headers. So imagine one of your users visits a page where the hacker has injected their malicious script on your website. They can execute any API with that script and act on the user's behalf without them ever knowing.</p>
<p>The point is when people say that HTTPOnly cookies cause XSS attacks to be useless, they are not completely correct because if a hacker can execute scripts on your website, you have <a href="https://twitter.com/benawad/status/1264974111165550593?lang=en">much bigger problems to deal with.</a> There are ways to <a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html">prevent XSS attacks</a>, but they are out of the scope of this article.</p>
<h3 class="relative">
<a id="samesite" href="https://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/#samesite" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
SameSite
</a>
</h3>
<p>If you remember, at the beginning of this article, we saw how cookies for a particular domain are sent with every request to the server for the corresponding domain. This means that if your user visits a third-party site and if that site makes a request to APIs on your domain, then all the cookies for your domain will be sent along that request to your server. This can be both a boon and a curse depending on your use case.</p>
<p>Boon in case of something like YouTube embeds. So, for example, if a user who is logged in to YouTube on their browser visits a third-party website containing YouTube embeds, they can click on the "Watch Later" button on the embed and add it to their library without needing to leave that website. This works because the browser sends the relevant cookies for youtube.com to the server confirming the user’s authentication status. These types of cookies are also called <strong>third-party cookies</strong>.</p>
<p>A curse in basically any other case you didn't intend it to happen. Imagine a case where the user visits a malicious website where that website makes a request to your server, and if your server doesn't validate the request properly, then the attacker can take actions on the user's behalf without their knowledge. This is basically what we call a <a href="https://owasp.org/www-community/attacks/csrf">CSRF attack</a>.</p>
<p>To help prevent this type of attack, <a href="https://datatracker.ietf.org/doc/html/draft-west-first-party-cookies-07">the IETF in 2016 proposed</a> a new attribute in cookies called SameSite. This attribute helps to tackle the above problem by allowing you to restrict your cookies only to the first-party context, i.e., <strong>only attach cookies to the request when the domain in your URL bar matches the cookie's domain.</strong></p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/cookie-samesite-attribute-480.webp 480w, https://prateeksurana.me/img/cookie-samesite-attribute-768.webp 768w, https://prateeksurana.me/img/cookie-samesite-attribute-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/cookie-samesite-attribute-480.jpeg 480w, https://prateeksurana.me/img/cookie-samesite-attribute-768.jpeg 768w, https://prateeksurana.me/img/cookie-samesite-attribute-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/cookie-samesite-attribute-768.jpeg" width="768" height="515" alt="SameSite attribute with Strict" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>There are three types of values you can set for the <code>SameSite</code> attribute:</p>
<ul>
<li><code>Strict</code>: When set to strict, your cookies will only be sent in a first-party context.</li>
<li><code>Lax</code>: This value is slightly less restrictive than <code>Strict</code> by allowing the cookies to be sent with Top-Level navigations. This means the cookie will be sent to the server with the request for the page in cases like when a user clicks on your website from a google search result or is redirected via a shortened URL.</li>
<li><code>None</code>: As the name suggests, this attribute allows you to create third-party cookies by sending the relevant cookies with every request irrespective of the site user for the cases like that of YouTube embeds we discussed above.</li>
</ul>
<p>You can learn more about the <code>SameSite</code> attribute in detail with this <a href="https://web.dev/samesite-cookies-explained/">awesome article</a> by web.dev.</p>
<h2 class="relative">
<a id="privacy-and-third-party-cookies" href="https://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/#privacy-and-third-party-cookies" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Privacy and third-party cookies
</a>
</h2>
<p>We saw a brief explanation of what third-party cookies are in the previous section. In short, any cookie set by a site other than the one you are currently on is a third-party cookie.</p>
<p>You may also have heard about how infamous third-party cookies are for tracking you across websites and showing personalized ads. Now that you know the rules of cookies, you can probably guess how they might do it.</p>
<p>Basically, whenever a website uses a script or adds an embed via iframe for third-party services, that third-party service can set a cookie for that service's domain with HTTP response headers. Also, these cookies can be used to track you across websites that use the same third-party service's embeds. And finally, the data collected by these third-party services by identifying you via the cookies can then be used to show you personalized ads.</p>
<p>To tackle this, many browsers like Firefox have started blocking popular third-party tracking cookies via a new feature they call <a href="https://blog.mozilla.org/en/products/firefox/firefox-now-available-with-enhanced-tracking-protection-by-default/">ETP (Enhanced tracking protection)</a>. Although this protects users from the 3000 most common identified trackers, its protection relies on the complete and up-to-date list.</p>
<p>Hence browsers are eventually planning to get rid of third-party cookies. Firefox is implementing state partitioning, which will result in every third-party cookie having a separate container for every website. You can read about how it works in <a href="https://hacks.mozilla.org/2021/02/introducing-state-partitioning/">more detail on their blog</a>.</p>
<p>Now you might think that something like State Partitioning will also break legitimate use cases for third-party cookies apart from tracking, and you're right. So browsers are working on a new API called <a href="https://privacycg.github.io/storage-access/">Storage Access</a>, which will allow third-party context to request first-party storage access via asking permission from the user, which would give the service unpartitioned access to its first-party state. Again if you're more interested in how it works in detail, you can check it out on the <a href="https://hacks.mozilla.org/2021/02/introducing-state-partitioning/">same blog by Mozilla.</a></p>
<h2 class="relative">
<a id="conclusion" href="https://prateeksurana.me/blog/javascript-developer-guide-to-browser-cookies/#conclusion" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Conclusion
</a>
</h2>
<p>I hope this article helped you learn something new about JavaScript cookies and gave you a brief overview of how they work, how they can be accessed and modified from the server and the client, and lastly, how the different attributes of cookies let you control their visibility and lifespan in the browser.</p>
Mastering data fetching with React Query and Next.jsLearn how React Query simplifies data fetching and caching for you and how it works in tandem with the Next.js pre-rendering methods2021-11-02T00:00:00Z2021-11-02T00:00:00Zhttps://prateeksurana.me/blog/mastering-data-fetching-with-react-query-and-next-js/<p>React takes pride in calling itself an unopinionated UI library by giving you as a developer a choice for everything from bundling, routing, state management, etc. But it also has its downsides since there is no standard/recommended way of doing things, choosing from so many available options can get overwhelming sometimes.</p>
<p>This paradox of choices gave birth to frameworks like Next.js that take the burden off your shoulders by managing things like routing, bundling, server-side and static rendering, etc., yet giving you the best possible developer experience. But you're still mostly on your own when it comes to state management and data fetching, and for that you can use a library or no library according to the nature and scale of your application.</p>
<p>Most web apps rely heavily on fetching and modifying data on the server and displaying it to the user. Though managing and storing asynchronous data can be handled inside components with states and effects, this can get out of hand quickly. Especially as your application grows, and an increasing number of components require the same piece of data across different pages or parts of your app.</p>
<p>This is where react-query comes in by allowing you to manage and cache server state throughout your application, with a zero-config yet customizable API. So in this post, we will look at how react-query works, the problems it solves, and how it nicely integrates with the different rendering mechanisms of Next.js.</p>
<h2 class="relative">
<a id="why-react-query" href="https://prateeksurana.me/blog/mastering-data-fetching-with-react-query-and-next-js/#why-react-query" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Why React Query?
</a>
</h2>
<p>When it comes to client-state management libraries for React, most of the popular ones (Redux, MobX, etc.) are great for managing client-side only state, but they require a lot of boilerplate code and are not efficient when it comes to managing async or server state.</p>
<p>React Query takes pride in calling itself a server state library for React. What it means is that instead of you doing the work of making the API requests, storing the response in a globally accessible state, and modifying that state when mutating some data on the server, React Query does all that for you with almost zero-config.</p>
<p>This sentence <a href="https://react-query.tanstack.com/guides/does-this-replace-client-state">from the docs</a> summarizes it the best, I believe -</p>
<blockquote>
<p>React Query replaces the boilerplate code and related wiring used to manage cache data in your client state and replaces it with just a few lines of code.</p>
</blockquote>
<p>Apart from the things mentioned above, React Query also handles things like refetching and updating stale data in the background, deduping multiple requests requesting the same data into one, pagination, lazy loading, garbage collection of server state, and many more things that wouldn't have been easy to implement yourself from scratch.</p>
<p>So if a majority of your application relies on managing asynchronous server state, React Query is a library worth checking out.</p>
<p>I recommend you also check the <a href="https://react-query.tanstack.com/overview#motivation">motivation section in their docs</a> if you're interested in reading about what I discussed above in more detail.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>I know there are other libraries worth mentioning (<a href="https://swr.vercel.app/">SWR</a>, <a href="https://redux-toolkit.js.org/rtk-query/overview">RTK-Query</a>) that do pretty much the same thing that React Query does, and they are also pretty good at it. But the reason I chose React Query over the others is that it is highly configurable, has a nice API, and provides more features than the others. You can find a complete comparison with other similar <a href="https://react-query.tanstack.com/comparison">libraries in their docs</a>.</p>
</aside>
<h2 class="relative">
<a id="setup" href="https://prateeksurana.me/blog/mastering-data-fetching-with-react-query-and-next-js/#setup" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Setup
</a>
</h2>
<p>Throughout this post, we will be building a simple Pokémon app that allows you to search your favorite Pokémon and show details like XP, abilities etc. for those Pokémon on a dedicated page via the <a href="https://pokeapi.co/">PokéAPI</a>.</p>
<p>To begin with, we will be using <a href="https://nextjs.org/docs/api-reference/create-next-app"><code>create-next-app</code></a> to create a simple Next.js TypeScript project -</p>
<pre class="language-bash"><code class="language-bash">npx create-next-app@latest --ts<br /><span class="token comment"># or</span><br /><span class="token function">yarn</span> create next-app --typescript</code></pre>
<p>Since we are going to use React Query, we will also need to install the <code>react-query</code> package -</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> react-query<br /><span class="token comment"># or</span><br /><span class="token function">yarn</span> <span class="token function">add</span> react-query</code></pre>
<p>Now, to use queries anywhere in our app, we need to create a <code>QueryClient</code> that allows the queries to interact with the cache. And for your <code>QueryClient</code> to be globally available for your application, you need to wrap your application with the <code>QueryClientProvider</code>.</p>
<p>The way we do it in Next.js is by creating a <a href="https://nextjs.org/docs/advanced-features/custom-app">Custom App</a> component via <code>pages/_app.tsx</code> -</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token comment">// pages/_app.tsx</span><br /><br /><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> AppProps <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"next/app"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> QueryClient<span class="token punctuation">,</span> QueryClientProvider <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react-query"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> ReactQueryDevtools <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react-query/devtools"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">MyApp</span><span class="token punctuation">(</span><span class="token punctuation">{</span> Component<span class="token punctuation">,</span> pageProps <span class="token punctuation">}</span><span class="token operator">:</span> AppProps<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>queryClient<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</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 keyword">new</span> <span class="token class-name">QueryClient</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 comment">// Provide the client to your App</span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">QueryClientProvider</span></span> <span class="token attr-name">client</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>queryClient<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">Component</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>pageProps<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">ReactQueryDevtools</span></span> <span class="token attr-name">initialIsOpen</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token boolean">false</span><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">QueryClientProvider</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><br /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> MyApp<span class="token punctuation">;</span><br /></code></pre>
<p>Since apart from running the above code on the client, Next.js runs it on the server as well, we are creating the <code>QueryClient</code> instance inside the app on React state (you can use <code>useRef</code> as well). This ensures that data is not shared between different users and requests, while still only creating the <code>QueryClient</code> once per component lifecycle.</p>
<p>You may have also noticed the <code>ReactQueryDevtools</code> in the code above. React Query comes with its dedicated devtools that help tremendously with inspecting and debugging your queries. It is a must have when you're starting your journey with React Query. Also, by default, it's only included in your app's bundle when <code>process.env.NODE_ENV === 'development'</code> so you don't need to worry about excluding them from your production build.</p>
<p><a href="https://codesandbox.io/s/initial-setup-react-query-k39bw?file=/pages/_app.tsx:0-498">💻 CodeSandbox up to this point</a></p>
<h2 class="relative">
<a id="fetching-data-on-the-client" href="https://prateeksurana.me/blog/mastering-data-fetching-with-react-query-and-next-js/#fetching-data-on-the-client" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Fetching data on the client
</a>
</h2>
<p>Now that we have the base setup ready, let's start writing our first Query. Since the purpose of this guide is to give your an overview of data fetching with React Query and Next.js, I won't be focusing on the styling aspects and will be using some already pre-built presentational components with styles.</p>
<p>To begin with, we will be creating a search page that allows you to search for Pokémon, and displays the names of matching pokémon.</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token comment">// pages/index.tsx</span><br /><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> useQuery <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react-query"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> useDebounce <span class="token keyword">from</span> <span class="token string">"../utils/useDebounce"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> searchPokemons <span class="token keyword">from</span> <span class="token string">"../utils/searchPokemons"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">IndexPage</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>searchValue<span class="token punctuation">,</span> setSearchValue<span class="token punctuation">]</span> <span class="token operator">=</span> React<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><br /> <span class="token keyword">const</span> debounedSearchValue <span class="token operator">=</span> <span class="token function">useDebounce</span><span class="token punctuation">(</span>searchValue<span class="token punctuation">,</span> <span class="token number">300</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> isLoading<span class="token punctuation">,</span> isError<span class="token punctuation">,</span> isSuccess<span class="token punctuation">,</span> data <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useQuery</span><span class="token punctuation">(</span><br /> <span class="token punctuation">[</span><span class="token string">"searchPokemons"</span><span class="token punctuation">,</span> debounedSearchValue<span class="token punctuation">]</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">searchPokemons</span><span class="token punctuation">(</span>debounedSearchValue<span class="token punctuation">)</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 punctuation">(</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>home<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>h1</span><span class="token punctuation">></span></span><span class="token plain-text">Search Your Pokemon</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</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">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<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 punctuation">(</span><span class="token punctuation">{</span> target<span class="token operator">:</span> <span class="token punctuation">{</span> value <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setSearchValue</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">}</span></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>searchValue<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 punctuation">></span></span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p><em>(If you're curious about the helpers imported at the start or the styles used, you will be able check them out in the sandbox attached at the end of this section)</em></p>
<p>Let's break down what's happening in the above piece of code. Apart from the <code>useQuery</code> hook, we have a pretty simple UI that consists of a text field where the user will type their search query and a state for managing that query. We also use a <a href="https://usehooks.com/useDebounce/"><code>useDebounce</code> hook</a> that gives us a <a href="https://css-tricks.com/debouncing-throttling-explained-examples/">debounced</a> value for the search query that updates at most every 300ms. We will be using this debounced value while making API requests so that we don't end up making a request for every keystroke the user types in the input field.</p>
<p>Now let's take a look at what's happening with the <code>useQuery</code> hook. If you check the <a href="https://react-query.tanstack.com/guides/queries">guide for queries</a> in documentation, you will find that to subscribe to any query in your component, you need at least two things. A <strong>unique key</strong>, that will be used as the query hash for caching your query, and <strong>a function that returns a promise</strong> that resolves the data or throws an error.</p>
<p>When it comes to <a href="https://react-query.tanstack.com/guides/query-keys">query keys</a>, they can be anything from simple strings to something as complex as an array or even nested objects. The only thing that React Query asks from you is that they should be unique to your Query's data.</p>
<p>In our case, the resulting data depends on the <code>debounedSearchValue</code> . It will return different results for different values and the same result for the same values. Hence we are using an <a href="https://react-query.tanstack.com/guides/query-keys#array-keys">array key</a> - <code>["searchPokemons", debounedSearchValue]</code>, which will always be unique for our data.</p>
<p>Lastly, we have the second argument, which is a function that returns a promise. In our case, it would be the <code>searchPokemons</code> method that returns a promise resolving to an array of strings containing the names of the found Pokémon for a given query string.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>The PokéAPI doesn't have the support for searching pokémon, so I have created a function that searches a local array of all the ~900 Pokémon species and returns a Promise that resolves with an artificial delay. If you're curious, there is an <a href="https://github.com/PokeAPI/pokeapi/issues/474">open issue for implementing search in PokéAPI</a>. I will integrate the actual API if it's ever implemented.</p>
</aside>
<p>Now that we have the initial query ready, lets render the searched output -</p>
<pre class="language-js"><code class="language-js"><span class="highlight-line"><span class="token comment">// pages/index.tsx</span></span><br /><span class="highlight-line"></span><br /><span class="highlight-line"><span class="token keyword">import</span> <span class="token punctuation">{</span> useQuery <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react-query"</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"><span class="token keyword">import</span> useDebounce <span class="token keyword">from</span> <span class="token string">"../utils/useDebounce"</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"><span class="token keyword">import</span> searchPokemons <span class="token keyword">from</span> <span class="token string">"../utils/searchPokemons"</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"><span class="token keyword">import</span> PokemonsSearchResult <span class="token keyword">from</span> <span class="token string">"../components/CompactPokemonCard"</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"></span><br /><span class="highlight-line"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">IndexPage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br /><span class="highlight-line"> <span class="token keyword">const</span> <span class="token punctuation">[</span>searchValue<span class="token punctuation">,</span> setSearchValue<span class="token punctuation">]</span> <span class="token operator">=</span> React<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><br /><span class="highlight-line"> <span class="token keyword">const</span> debounedSearchValue <span class="token operator">=</span> <span class="token function">useDebounce</span><span class="token punctuation">(</span>searchValue<span class="token punctuation">,</span> <span class="token number">300</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"></span><br /><span class="highlight-line"> <span class="token keyword">const</span> <span class="token punctuation">{</span> isLoading<span class="token punctuation">,</span> isError<span class="token punctuation">,</span> isSuccess<span class="token punctuation">,</span> data <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useQuery</span><span class="token punctuation">(</span></span><br /><span class="highlight-line"> <span class="token punctuation">[</span><span class="token string">"searchPokemons"</span><span class="token punctuation">,</span> debounedSearchValue<span class="token punctuation">]</span><span class="token punctuation">,</span></span><br /><span class="highlight-line"> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">searchPokemons</span><span class="token punctuation">(</span>debounedSearchValue<span class="token punctuation">)</span><span class="token punctuation">,</span></span><br /><mark class="highlight-line highlight-line-active"> <span class="token punctuation">{</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token literal-property property">enabled</span><span class="token operator">:</span> debounedSearchValue<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token punctuation">}</span></mark><br /><span class="highlight-line"> <span class="token punctuation">)</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"></span><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">const</span> <span class="token function-variable function">renderResult</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></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">if</span> <span class="token punctuation">(</span>isLoading<span class="token punctuation">)</span> <span class="token punctuation">{</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">return</span> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token string">"search-message"</span><span class="token operator">></span>Loading<span class="token operator">...</span><span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token punctuation">}</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">if</span> <span class="token punctuation">(</span>isError<span class="token punctuation">)</span> <span class="token punctuation">{</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">return</span> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token string">"search-message"</span><span class="token operator">></span>Something went wrong<span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token punctuation">}</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">if</span> <span class="token punctuation">(</span>isSuccess<span class="token punctuation">)</span> <span class="token punctuation">{</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">return</span> <span class="token operator"><</span>PokemonsSearchResult pokemons<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></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token punctuation">}</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token keyword">return</span> <span class="token operator"><</span><span class="token operator">></span><span class="token operator"><</span><span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span></mark><br /><mark class="highlight-line highlight-line-active"> <span class="token punctuation">}</span><span class="token punctuation">;</span></mark><br /><span class="highlight-line"></span><br /><span class="highlight-line"> <span class="token keyword">return</span> <span class="token punctuation">(</span></span><br /><span class="highlight-line"> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token string">"home"</span><span class="token operator">></span></span><br /><span class="highlight-line"> <span class="token operator"><</span>h1<span class="token operator">></span>Search Your Pokemon<span class="token operator"><</span><span class="token operator">/</span>h1<span class="token operator">></span></span><br /><span class="highlight-line"> <span class="token operator"><</span>input</span><br /><span class="highlight-line"> type<span class="token operator">=</span><span class="token string">"text"</span></span><br /><span class="highlight-line"> onChange<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> <span class="token literal-property property">target</span><span class="token operator">:</span> <span class="token punctuation">{</span> value <span class="token punctuation">}</span> <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setSearchValue</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">}</span></span><br /><span class="highlight-line"> value<span class="token operator">=</span><span class="token punctuation">{</span>searchValue<span class="token punctuation">}</span></span><br /><span class="highlight-line"> <span class="token operator">/</span><span class="token operator">></span></span><br /><mark class="highlight-line highlight-line-active"> <span class="token punctuation">{</span><span class="token function">renderResult</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span></mark><br /><span class="highlight-line"> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span></span><br /><span class="highlight-line"> <span class="token punctuation">)</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre>
<p><strong>Checkout the full code in <a href="https://codesandbox.io/s/fetching-data-on-client-5t7e4?file=/pages/index.tsx">the sandbox 💻</a></strong></p>
<p>You can also test out the final result below (<a href="https://csb-5t7e4-a6yfohygb-prateeksurana3255.vercel.app/">live URL</a>):</p>
<iframe src="https://csb-5t7e4-a6yfohygb-prateeksurana3255.vercel.app/" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="Pokedex client" loading="lazy"></iframe>
<p>We have now added a function <code>renderResult</code> that renders the query's result based on its state, which we get from the query result. We show a simple message for loading and error states and use a presentational component that renders the Pokémon names in a grid for a successful response.</p>
<p>One more thing you will notice is that I have added an argument to the <code>useQuery</code> hook. Apart from the <code>queryKey</code> and the function that returns a Promise, <code>useQuery</code> also accepts an object as a third argument which allows you to control various behaviors of the hook. You can find the <a href="https://react-query.tanstack.com/reference/useQuery">complete list of options in the docs</a>.</p>
<p>In our case, we are using the <code>enabled</code> option, which prevents the Query from running automatically when set to false. So we wouldn't want to execute a request initially when the search string is empty or when the user clears the text field.If you're interested in exploring the other options, feel free to try them out in <a href="https://codesandbox.io/s/fetching-data-on-client-5t7e4?file=/pages/index.tsx">the sandbox</a>.</p>
<p>You can also test out the "show stale data and update in background" feature, which we discussed initially, by searching for the same term again. You'll notice that it doesn't show the loading state for it and shows you the cached data while it makes the request in the background. (Check out the <a href="https://react-query-v2.tanstack.com/docs/guides/caching">guide for caching in the docs</a> if you're interested in how it works and how you can customize this behavior).</p>
<p>So to summarize, in this section, we saw how we can the <code>useQuery</code> hook works and how it simplifies data fetching on the client-side for you. But if you're using a framework like Next.js you probably won't be fetching the data on the client-side all the time, for some of the pages, you might want to pre-render pages at build time or on the server. Well, guess what? React Query has got your back. Let's check out how you would handle it in the next section.</p>
<p><a href="https://codesandbox.io/s/fetching-data-on-client-5t7e4?file=/pages/index.tsx">💻 CodeSandbox up to this point</a></p>
<h2 class="relative">
<a id="fetching-data-on-the-server" href="https://prateeksurana.me/blog/mastering-data-fetching-with-react-query-and-next-js/#fetching-data-on-the-server" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Fetching data on the server
</a>
</h2>
<p>In the last section, we saw how React Query simplifies data fetching and managing server state on the client for you. But that's not all.</p>
<p>If you've been using Next.js for a while, you are probably aware of how it simplifies Server-side rendering and Static site generation for you (If not, I would definitely recommend you to check out their <a href="https://nextjs.org/learn/basics/data-fetching">tutorial on pre-rendering</a>).</p>
<p>Let's implement this in our Pokémon example to get a better understanding of how this works.</p>
<p>To demonstrate this, we'll be continuing the current example and adding a new page that will display the details of a particular Pokémon. We'll be using <a href="https://nextjs.org/docs/routing/dynamic-routes">Next.js' dynamic routes</a> to create a new route <code>/pokemon/[id]</code> for it.</p>
<p>First, let's start with what we learned in the previous section about fetching the queries on the client and then build upon that. To create the route, we want create a file called <code>pages/pokemon/[id].tsx</code> and add the following code to it -</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token comment">// pages/pokemon/[id].tsx</span><br /><br /><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> axios <span class="token keyword">from</span> <span class="token string">"axios"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> useQuery <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react-query"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> useRouter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"next/router"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> PokemonCard <span class="token keyword">from</span> <span class="token string">"../../components/PokemonCard"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> <span class="token function-variable function">fetchPokemon</span> <span class="token operator">=</span> <span class="token punctuation">(</span>id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=></span><br /> axios<br /> <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://pokeapi.co/api/v2/pokemon/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>id<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 /> <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> data <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=></span> data<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Pokemon</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token function">useRouter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> pokemonID <span class="token operator">=</span> <span class="token keyword">typeof</span> router<span class="token punctuation">.</span>query<span class="token operator">?.</span>id <span class="token operator">===</span> <span class="token string">"string"</span> <span class="token operator">?</span> router<span class="token punctuation">.</span>query<span class="token punctuation">.</span>id <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> isSuccess<span class="token punctuation">,</span> data<span class="token operator">:</span> pokemon<span class="token punctuation">,</span> isLoading<span class="token punctuation">,</span> isError <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useQuery</span><span class="token punctuation">(</span><br /> <span class="token punctuation">[</span><span class="token string">"getPokemon"</span><span class="token punctuation">,</span> pokemonID<span class="token punctuation">]</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">fetchPokemon</span><span class="token punctuation">(</span>pokemonID<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token punctuation">{</span><br /> enabled<span class="token operator">:</span> pokemonID<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>isSuccess<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">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>container<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">PokemonCard</span></span><br /> <span class="token attr-name">name</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>pokemon<span class="token punctuation">.</span>name<span class="token punctuation">}</span></span><br /> <span class="token attr-name">image</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>pokemon<span class="token punctuation">.</span>sprites<span class="token operator">?.</span>other<span class="token operator">?.</span><span class="token punctuation">[</span><span class="token string">"official-artwork"</span><span class="token punctuation">]</span><span class="token operator">?.</span>front_default<span class="token punctuation">}</span></span><br /> <span class="token attr-name">weight</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>pokemon<span class="token punctuation">.</span>weight<span class="token punctuation">}</span></span><br /> <span class="token attr-name">xp</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>pokemon<span class="token punctuation">.</span>base_experience<span class="token punctuation">}</span></span><br /> <span class="token attr-name">abilities</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>pokemon<span class="token punctuation">.</span>abilities<span class="token operator">?.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span> <span class="token operator">=></span> item<span class="token punctuation">.</span>ability<span class="token punctuation">.</span>name<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 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><br /><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>isLoading<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">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>center<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">Loading...</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 /><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>isError<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">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>center<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> We couldn't find your pokemon</span><span class="token punctuation">{</span><span class="token string">" "</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> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>img<span class="token punctuation">"</span></span> <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>sad<span class="token punctuation">"</span></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>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><br /><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 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><strong>Check out the code on <a href="https://codesandbox.io/s/fetching-data-on-server-s95i4?file=/pages/pokemon/%5Bid%5D.tsx">this sandbox</a></strong></p>
<p>The above snippet is very similar to the code we saw in the previous section. For the second argument of <code>useQuery</code>, we are using <a href="https://github.com/axios/axios"><code>axios</code></a> to fetch the data from the PokéAPI this time, which also returns a Promise with the data or the error.</p>
<p>I have also linked the cards in the search result we created in the previous section to this page. You can test out searching and click on any of the items from the result <a href="https://codesandbox.io/s/fetching-data-on-server-s95i4?file=/pages/index.tsx">in the sandbox.</a></p>
<p>Now with this page, our Pokémon app might look complete. However we are still loading the data on the client side, due to which the transition isn't very smooth, and we are presented with a loading indicator on the initial page load.</p>
<p>Also this page isn't very SEO friendly. If we were to add some meta tags with the Pokémon details they would only be added when the browser render the page, executes the JavaScript and then fetches the Pokémon details (Although Google says that the googlebot runs the client side JavaScript and renders pages but still nothing beats a pre-rendered page with all the meta tags and content already available). Similarly, the social media previews won't work for this page because they don't execute the client-side JavaScript.</p>
<p>So let's statically pre-render these pages with Next.js' <a href="https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation"><code>getStaticProps</code> method</a>.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>Using Static pre-rendering instead of server-side pre-rendering because the data is publicly available, will barely ever change, and we want the best possible SEO performance. Do check out <a href="https://nextjs.org/docs/basic-features/data-fetching#when-should-i-use-getstaticprops">when should I use <code>getStaticProps</code></a> in the Next.js docs for more info. Although the code would have been very similar if we were creating a server-rendered page as well.</p>
</aside>
<p>Before we get into the next part of this section, there are a couple of terms that you should be aware of to understand what is happening -</p>
<ul>
<li><strong>dehydrating queries</strong> - Dehydration refers to creating a frozen representation of the cache. This can be later hydrated on the browser with React Query's hydrate methods. This is useful if you want to store the cache for later use, for instance in <code>localstorage</code> or in our case sending the cache from server to client.</li>
<li><strong>hydrating queries</strong> - Hydration lets you add any previously dehydrated state to the cache on a <code>QueryClient</code> instance with the full functionality of the library when the app is rendering on the browser.</li>
</ul>
<p>React Query lets you fetch any number of queries you want during any of the Next.js pre-rendering steps and then dehydrate those queries. This allows you to pre-render your markup that will be available with all the data on page load and once the page renders on the client, React Query will hydrate those dehydrated queries with the full functionality of the library.</p>
<p>To begin with, we will need to modify <code>_app.tsx</code> so that the dehydrated queries (which we will be passing as props in the next step) can be hydrated when the app renders on the client.</p>
<p>Make the following changes in the <code>_app.tsx</code> file -</p>
<pre class="language-ts"><code class="language-ts"><span class="highlight-line"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"><span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> AppProps <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"next/app"</span><span class="token punctuation">;</span></span><br /><mark class="highlight-line highlight-line-active"><span class="token keyword">import</span> <span class="token punctuation">{</span> QueryClient<span class="token punctuation">,</span> QueryClientProvider<span class="token punctuation">,</span> Hydrate <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react-query"</span><span class="token punctuation">;</span></mark><br /><span class="highlight-line"><span class="token keyword">import</span> <span class="token punctuation">{</span> ReactQueryDevtools <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react-query/devtools"</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"><span class="token keyword">import</span> <span class="token string">"../styles.css"</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"></span><br /><span class="highlight-line"><span class="token keyword">function</span> <span class="token function">MyApp</span><span class="token punctuation">(</span><span class="token punctuation">{</span> Component<span class="token punctuation">,</span> pageProps <span class="token punctuation">}</span><span class="token operator">:</span> AppProps<span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br /><span class="highlight-line"> <span class="token keyword">const</span> <span class="token punctuation">[</span>queryClient<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</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 keyword">new</span> <span class="token class-name">QueryClient</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="highlight-line"> <span class="token keyword">return</span> <span class="token punctuation">(</span></span><br /><span class="highlight-line"> <span class="token comment">// Provide the client to your App</span></span><br /><span class="highlight-line"> <span class="token operator"><</span>QueryClientProvider client<span class="token operator">=</span><span class="token punctuation">{</span>queryClient<span class="token punctuation">}</span><span class="token operator">></span></span><br /><mark class="highlight-line highlight-line-active"> <span class="token operator"><</span>Hydrate state<span class="token operator">=</span><span class="token punctuation">{</span>pageProps<span class="token punctuation">.</span>dehydratedState<span class="token punctuation">}</span><span class="token operator">></span></mark><br /><span class="highlight-line"> <span class="token operator"><</span>Component <span class="token punctuation">{</span><span class="token operator">...</span>pageProps<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span></span><br /><span class="highlight-line"> <span class="token operator"><</span>ReactQueryDevtools initialIsOpen<span class="token operator">=</span><span class="token punctuation">{</span><span class="token boolean">false</span><span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span></span><br /><mark class="highlight-line highlight-line-active"> <span class="token operator"><</span><span class="token operator">/</span>Hydrate<span class="token operator">></span></mark><br /><span class="highlight-line"> <span class="token operator"><</span><span class="token operator">/</span>QueryClientProvider<span class="token operator">></span></span><br /><span class="highlight-line"> <span class="token punctuation">)</span><span class="token punctuation">;</span></span><br /><span class="highlight-line"><span class="token punctuation">}</span></span><br /><span class="highlight-line"></span><br /><span class="highlight-line"><span class="token keyword">export</span> <span class="token keyword">default</span> MyApp<span class="token punctuation">;</span></span></code></pre>
<p>In the above snippet, the <a href="https://react-query.tanstack.com/reference/hydration#hydrate-1"><code>Hydrate</code> component</a> will hydrate the <code>queryClient</code> with the cached data we fetched on the server.</p>
<p>Notice how we're using <code>pageProps.dehydratedState</code> for the <code>state</code> prop of the <code>Hydrate</code> component. This prop is for the dehydrated state will be hydrated on the client. If you check the <a href="https://nextjs.org/docs/advanced-features/custom-app">Next.js docs for the <code>App</code> component</a>, you'll see that <code>pageProps</code> is an object with the initial props that were preloaded by any of their <a href="https://nextjs.org/docs/basic-features/data-fetching">data fetching methods</a>.</p>
<p>So for the hydration to work, we will need to return the dehydrated cache with the <code>dehydratedState</code> prop from the <code>getStaticProps</code> method that we'll be using for the <code>pages/pokemon/[id].tsx</code> page.</p>
<p>Add the following snippet to the end of <code>pages/pokemon/[id].tsx</code> file -</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token comment">// pages/pokemon/[id].tsx</span><br /><br /><span class="token comment">// ...rest of the code we added in the previous section</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">const</span> getStaticProps<span class="token operator">:</span> <span class="token function-variable function">GetStaticProps</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>context<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> id <span class="token operator">=</span> context<span class="token punctuation">.</span>params<span class="token operator">?.</span>id <span class="token keyword">as</span> <span class="token builtin">string</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> queryClient <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">QueryClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">await</span> queryClient<span class="token punctuation">.</span><span class="token function">prefetchQuery</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"getPokemon"</span><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 operator">=></span> <span class="token function">fetchPokemon</span><span class="token punctuation">(</span>id<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 punctuation">{</span><br /> props<span class="token operator">:</span> <span class="token punctuation">{</span><br /> dehydratedState<span class="token operator">:</span> <span class="token function">dehydrate</span><span class="token punctuation">(</span>queryClient<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><br /><br /><span class="token keyword">export</span> <span class="token keyword">const</span> getStaticPaths<span class="token operator">:</span> <span class="token function-variable function">GetStaticPaths</span> <span class="token operator">=</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">return</span> <span class="token punctuation">{</span><br /> paths<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> fallback<span class="token operator">:</span> <span class="token string">"blocking"</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><strong>Check out the code on <a href="https://codesandbox.io/s/fetching-data-on-server-final-stl70?file=/pages/pokemon/%5Bid%5D.tsx">this sandbox</a></strong></p>
<p>In the above snippet, we are using Next.js' <code>getStaticProps</code> method to prefetch the pokémon on the server via the <code>prefetchQuery</code> method with the same key that we're using in the component. And then we are dehydrating the <code>queryClient</code> to the <code>dehyrdatedState</code> prop that will be used by <code>_app</code> as we saw in the previous section.</p>
<p>Since this is a dynamic page, we have to use <code>getStaticPaths</code> that provides an initial set of paths that can be used to pre-render these pages at build time. Although we are passing it an empty array, none of the pages would be generated at build time.</p>
<p>Instead, we are using <a href="https://nextjs.org/docs/basic-features/data-fetching#fallback-blocking"><code>fallback: "blocking"</code></a> in which any new page is server-side generated and then cached for future requests, so it only happens once per path. So if anyone visits <code>/pokemon/pikachu</code> for the first time, the page would be pre-rendered on server-side, and for any future requests for this page, Next.js would directly return the cached pre-rendered HTML.</p>
<p>Similar to what we did in the <code>_app</code> component, you should always create a new <code>QueryClient</code> instance in these server side data fetching methods, <strong>this ensures that data is not shared between different users and requests.</strong></p>
<p>That's it. With these changes, this page is now pre-rendered, and there's no more loading indicator and you can very easily add some meta tags for nice social media previews. My favorite part was how we didn't even have to touch the component code when we moved this page from a client-side rendered page to a pre-rendered page.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>One thing you'll notice is that even though we are pre-rendering the page when the page renders on the client, React Query still makes an API request to the same pokémon API in the browser.</p>
<p>That's because of how React Query sets its defaults. By default, the <code>staleTime</code> is 0, which means that as soon as the component renders on the client and the Query is rehydrated, it is stale for React Query, and it would be refetched in the background as we saw earlier. But don't worry we can easily change this behavior on a query level by modifying the <code>staleTime</code> .</p>
<p>This behavior also allows you to implement neat tricks so that you don't have to regenerate pages on the server frequently yet show the latest data on the client. For example,, you can regenerate a page only once a day on the server but set the <code>staleTime</code> to an hour so that if the page is older than an hour, it would fetch the data in the background on the client and update the cache showing the latest data to the end-user.</p>
<p>For our case, since we know that the data will never change, I have already set <code>staleTime</code> to <code>Infinity</code>, which means that as long as we have already fetched data in the cache, it would never refetch in the background.</p>
</aside>
<p>You can checkout the final result below (<a href="https://csb-stl70.vercel.app/">live URL</a>):</p>
<iframe src="https://csb-stl70.vercel.app/" style="width:100%; height:525px; border:0; border-radius: 4px; overflow:hidden;" title="Pokedex final" loading="lazy"></iframe>
<p><a href="https://codesandbox.io/s/fetching-data-on-server-final-stl70?file=/pages/pokemon/%5Bid%5D.tsx">💻 Checkout the final sandbox here</a></p>
<h2 class="relative">
<a id="conclusion" href="https://prateeksurana.me/blog/mastering-data-fetching-with-react-query-and-next-js/#conclusion" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Conclusion
</a>
</h2>
<p>To summarize in this article we saw how React Query simplifies data fetching and caching for you, how you can easily fetch data on the client and the server-side with it, and how it works in tandem with the existing Next.js pre-rendering methods.</p>
<p>Although there is a whole another area of things like mutation and cache manipulation, infinite loading etc. which we didn't even touch, but is an important part of this library. I would recommend you to <a href="https://react-query.tanstack.com/overview">checkout the docs</a> if you're interested in learning more about it.</p>
<p>I hope this post helps you in making an informed decision regarding whether use this library is suited your project. If you feel that I missed anything or if something could have been explained better feel free to add it to the comments section below.</p>
How JavaScript Classes work under the hoodLearn what prototypes are in JavaScript, what are they used for, what prototype chaining is and how do JavaScript classes work under the hood with these concepts.2022-01-01T00:00:00Z2022-01-01T00:00:00Zhttps://prateeksurana.me/blog/how-javascript-classes-work-under-the-hood/<p>Classes in JavaScript are a blueprint for creating objects which allow you to encapsulate, data with code to work on that data. The <code>class</code> keyword was officially introduced in JavaScript with ES6 in 2015, but the classes created with the <code>class</code> keyword are mostly just syntactic sugar over the already existing pre-ES6 syntax for creating objects with custom prototypes.</p>
<p>So in this article, we'll explore what prototypes are, what are they used for, what prototype chaining is and how it helps with prototypal inheritance, and then finally, how do classes work under the hood with all these concepts. Throughout this article we'll also be looking at examples which you can try out yourself with any JavaScript compiler or even the browser console.</p>
<h2 class="relative">
<a id="what-are-prototypes-anyways" href="https://prateeksurana.me/blog/how-javascript-classes-work-under-the-hood/#what-are-prototypes-anyways" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
What are prototypes anyways?
</a>
</h2>
<p>Prototypes are a core part of JavaScript, so much so that JavaScript is often referred to as a prototype-based language.</p>
<p>Although it is entirely possible that you've been using JavaScript for a while now and haven't ever heard about prototypes (they're mostly abstracted out by the syntax) and knowing about them might not directly help in the day-to-day code we write but it will help you get a better understanding of JavaScript.</p>
<p>In a nutshell, prototypes are a mechanism by which JavaScript objects can inherit features from one another. Objects can have a prototype object which they use as a template to inherit their properties and methods from.</p>
<p>Have you ever wondered whenever you create an object or an array you get a bunch of handy methods ( <code>hasOwnProperty</code> , <code>toString</code>, etc.) out of the box even though you didn't define them when you created the object or the array?</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> obj <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">"Prateek"</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br />obj<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// true</span><br /><br /><span class="token keyword">let</span> nums <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 number">3</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br />nums <span class="token operator">=</span> nums<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">num</span> <span class="token operator">=></span> num <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// [2, 3, 4]</span></code></pre>
<p>Whenever you create objects and arrays in JavaScript, they come with their default prototype and the properties you saw above, defined on the default prototypes of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object#instance_methods"><code>Object</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object#instance_methods"><code>Array</code></a>, respectively.</p>
<p>The way it works is that whenever you try to access a property on an object like <code>obj.hasOwnProperty</code> above, JavaScript first checks whether the object has a property called <code>hasOwnProperty</code> or not. If the property isn't defined on the object, it looks into its prototype, and if it cannot find it there, only then it returns <code>undefined</code>. In the example above, <code>hasOwnProperty</code> is a function defined on the Object's prototype; hence we are able to call it.</p>
<p>You can see all the methods like <code>hasOwnProperty</code>, that are available with the default Object prototype under <code>[[Prototype]]</code>, just by logging any object in the console.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/prototypes-in-console-480.webp 480w, https://prateeksurana.me/img/prototypes-in-console-768.webp 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/prototypes-in-console-480.jpeg 480w, https://prateeksurana.me/img/prototypes-in-console-768.jpeg 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/prototypes-in-console-768.jpeg" width="768" height="446" alt="The prototype visible under [[Prototype]] in console" class="article-img" loading="lazy" decoding="async" />
</picture>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>The double sqaure bracket <code>[[Prototype]]</code> signifies that its an internal property of the object, and <a href="https://stackoverflow.com/questions/17174786/what-is-the-significance-of-the-double-brackets-for-the-prototype-property-i">exists purely for expository purposes</a>.</p>
</aside>
<p>To get the prototype of any object, you can use the <code>Object.getPrototypeof</code> method. Try running the below snippet in the console:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> obj <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">foo</span><span class="token operator">:</span> <span class="token string">'bar'</span><span class="token punctuation">}</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>Object<span class="token punctuation">.</span><span class="token function">getPrototypeOf</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/prototypes-getprototypeof-480.webp 480w, https://prateeksurana.me/img/prototypes-getprototypeof-768.webp 768w, https://prateeksurana.me/img/prototypes-getprototypeof-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/prototypes-getprototypeof-480.jpeg 480w, https://prateeksurana.me/img/prototypes-getprototypeof-768.jpeg 768w, https://prateeksurana.me/img/prototypes-getprototypeof-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/prototypes-getprototypeof-768.jpeg" width="768" height="285" alt="The object prototype with Object.getPrototypeOf" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Here is a visualization to help you understand how it works:</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/prototypes-object-visualization-480.webp 480w, https://prateeksurana.me/img/prototypes-object-visualization-768.webp 768w, https://prateeksurana.me/img/prototypes-object-visualization-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/prototypes-object-visualization-480.jpeg 480w, https://prateeksurana.me/img/prototypes-object-visualization-768.jpeg 768w, https://prateeksurana.me/img/prototypes-object-visualization-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/prototypes-object-visualization-768.jpeg" width="768" height="468" alt="Object prototype visualization" class="article-img" loading="lazy" decoding="async" />
</picture>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>You can also access the prototype via the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto"><code>__proto__</code></a> property (try logging <code>obj.__proto__</code> in the console) on any object. But it's deprecated and will probably soon be dropped from the web standards.</p>
</aside>
<p>You might think that since the prototype is just a JavaScript object, what happens if you mutate it. Try running the below snippet in the browser console and see what happens.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> obj <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">'jon'</span> <span class="token punctuation">}</span><br /><span class="token keyword">let</span> prototype <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">getPrototypeOf</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span><br />prototype<span class="token punctuation">.</span>foo <span class="token operator">=</span> <span class="token string">'bar'</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>obj<span class="token punctuation">.</span>foo<span class="token punctuation">)</span><br /><br /><span class="token keyword">let</span> obj2 <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">'arya'</span> <span class="token punctuation">}</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>obj2<span class="token punctuation">.</span>foo<span class="token punctuation">)</span> <span class="token comment">// "bar"</span></code></pre>
<p>Since we mutated the default <code>Object</code> prototype which is referenced by all the objects by default, accessing the <code>foo</code> property on any of them would give you <code>"bar"</code> as the value.</p>
<p>What we just did above is known as prototype pollution, it used to be a popular way to add custom properties to shared objects, but it was not only dangerous (imagine an attacker making application-wide changes by modifying the prototype), with modern browsers it is also a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf">very slow operation</a> hence it's not recommended.</p>
<p>The recommended way to create objects with a custom prototype is using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create"><code>Object.create</code></a> with accepts the prototype as its argument.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> car <span class="token operator">=</span> <span class="token punctuation">{</span> <br /> <span class="token literal-property property">wheels</span><span class="token operator">:</span> <span class="token number">4</span><span class="token punctuation">,</span><br /> <span class="token function-variable function">honk</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><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Beep!'</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">let</span> prius <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>car<span class="token punctuation">)</span><br />prius<span class="token punctuation">.</span>manufacturer <span class="token operator">=</span> <span class="token string">'Toyota'</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>prius<span class="token punctuation">.</span>manufacturer<span class="token punctuation">)</span> <span class="token comment">// 'Toyota'</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>prius<span class="token punctuation">.</span>wheels<span class="token punctuation">)</span> <span class="token comment">// 4</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>prius<span class="token punctuation">.</span><span class="token function">honk</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// Beep!</span></code></pre>
<p>Again, here's a visualization to help you better understand the above snippet:</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/prototypes-car-example-visualization-480.webp 480w, https://prateeksurana.me/img/prototypes-car-example-visualization-768.webp 768w, https://prateeksurana.me/img/prototypes-car-example-visualization-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/prototypes-car-example-visualization-480.jpeg 480w, https://prateeksurana.me/img/prototypes-car-example-visualization-768.jpeg 768w, https://prateeksurana.me/img/prototypes-car-example-visualization-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/prototypes-car-example-visualization-768.jpeg" width="768" height="742" alt="Object.create visualization" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>In the above example, the object <code>prius</code> has <code>car</code> as its prototype, so it would be able to access the properties of the prototype while adding some of its own properties as well. With this example I think you might be starting to understand how prototypes can be used for inheritance in JavaScript. We'll be exploring this in more detail and revisiting this method in ther next sections.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>You can also create object with no prototype by passing <code>null</code> as the argument to <code>Object.create</code>. By doing this the prototype of the object would be <code>null</code> and you won't be able to access any default <code>Object</code> prototype properties, like <code>hasOwnProperty</code> and <code>toString</code> on it.</p>
<p>Try it yourself in the console by running <code>let obj = Object.create(null)</code> and then accessing any of the default <code>Object</code> prototype properties on it.</p>
</aside>
<h2 class="relative">
<a id="the-prototype-for-creating-prototypes" href="https://prateeksurana.me/blog/how-javascript-classes-work-under-the-hood/#the-prototype-for-creating-prototypes" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
The prototype for creating prototypes
</a>
</h2>
<p>In the last section, we saw the role of prototypes in JavaScript objects, how you can manipulate object prototypes and how you can create an object with a custom prototype via <code>Object.create</code>. Although creating objects with custom prototypes via <code>Object.create</code> is not ideal because you cannot set any properties on the object while creating the object, also the syntax is not very neat.</p>
<p>So, now let's take a look at another more prevalent way of creating objects with custom prototypes that you may have seen/used before, constructor functions.</p>
<p>Before we dive into what constrctor functions are and how they work, let's look at an interesting thing about JavaScript functions. Whenever you create a function in JavaScript, it has a <code>prototype</code> property which is an object and that object has a property called <code>constructor</code> that points back to the function itself. You can go ahead and try creating a function and logging the <code>prototype</code> property in the browser to see it for yourself. We'll be looking at why it exists in a minute.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/prototypes-constructor-function-example-480.webp 480w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/prototypes-constructor-function-example-480.jpeg 480w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/prototypes-constructor-function-example-480.jpeg" width="480" height="163" alt="Constructor function example" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Hence if you do <code>foo.prototype.constructor === foo</code> it would return <code>true</code></p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>Keep in mind that this property is not the same as the prototype as we saw in the last section. In fact, you can go ahead and verify it for yourself by running <code>Object.getPrototypeOf(foo) === foo.prototype</code> in the context of the above example and you'll get <code>false</code> as the output. Also, the object in the <code>prototype</code> property has its own prototype which is the same as the Object prototype we saw in the previous sections.</p>
<p>So to avoid confusion for the rest of the article I'll be using <code>prototype</code> when referring to the prototype property of constructor functions and "prototype of {object}" when referring to the prototype of the object.</p>
</aside>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/prototypes-constructor-function-visualization-480.webp 480w, https://prateeksurana.me/img/prototypes-constructor-function-visualization-768.webp 768w, https://prateeksurana.me/img/prototypes-constructor-function-visualization-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/prototypes-constructor-function-visualization-480.jpeg 480w, https://prateeksurana.me/img/prototypes-constructor-function-visualization-768.jpeg 768w, https://prateeksurana.me/img/prototypes-constructor-function-visualization-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/prototypes-constructor-function-visualization-768.jpeg" width="768" height="451" alt="Constructor function visualization" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Now that we know about the <code>prototype</code> property of functions let’s take a look at how it helps with creating objects with custom prototype via constructor functions:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Person</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">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">let</span> person1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Person</span><span class="token punctuation">(</span><span class="token string">"Prateek"</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>person1<span class="token punctuation">.</span>name<span class="token punctuation">)</span> <span class="token comment">// Prateek</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>Object<span class="token punctuation">.</span><span class="token function">getPrototypeOf</span><span class="token punctuation">(</span>person1<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// { constructor: function Person }</span></code></pre>
<p>In the above snippet, we have a regular function with a property <code>name</code> defined on its execution context, via the <code>this</code> keyword. When we invoke a function with the <code>new</code> keyword, apart from executing the function it does a bunch of other things:</p>
<ul>
<li>To begin with, it creates a new blank JavaScript object and sets the prototype of that object to the <code>Person</code> function’s <code>prototype</code> property.</li>
<li>Next, it points this newly created object to the <code>this</code> context of the function (i.e., all references to <code>this</code> inside the function now point to the newly created object, so in our case, the name property gets defined on that object).</li>
<li>Lastly, if no object is being returned from the function, it returns <code>this</code>.</li>
</ul>
<p>Now I guess you might have understood how we got the output in the logs for the above snippet.</p>
<p>Since the <code>name</code> property is defined on <code>this</code>, we get an object with the <code>name</code> property whose value is the argument that we to the function. Also, the newly created object's prototype points to our function's <code>prototype</code> property.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/prototypes-object-created-with-constructor-function-480.webp 480w, https://prateeksurana.me/img/prototypes-object-created-with-constructor-function-768.webp 768w, https://prateeksurana.me/img/prototypes-object-created-with-constructor-function-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/prototypes-object-created-with-constructor-function-480.jpeg 480w, https://prateeksurana.me/img/prototypes-object-created-with-constructor-function-768.jpeg 768w, https://prateeksurana.me/img/prototypes-object-created-with-constructor-function-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/prototypes-object-created-with-constructor-function-768.jpeg" width="768" height="597" alt="Object created with constructor function" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>You can further verify it by running the following code:</p>
<pre class="language-jsx"><code class="language-jsx">console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>Object<span class="token punctuation">.</span><span class="token function">getPrototypeOf</span><span class="token punctuation">(</span>person1<span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token class-name">Person</span><span class="token punctuation">.</span>prototype<span class="token punctuation">)</span> <span class="token comment">// true</span></code></pre>
<p>You can now also create properties on the function's <code>prototype</code> property and since it is the prototype shared with all the objects created with <code>Person</code> as the constructor function, it would also be available to all those objects.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token class-name">Person</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function-variable function">sayHello</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><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hello, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>name<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><br /><br />person1<span class="token punctuation">.</span><span class="token function">sayHello</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Hello, Prateek</span></code></pre>
<h2 class="relative">
<a id="prototype-chaining" href="https://prateeksurana.me/blog/how-javascript-classes-work-under-the-hood/#prototype-chaining" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Prototype chaining
</a>
</h2>
<p>Until now, we saw how prototypes in JavaScript work and how JavaScript searches for the property in the prototype if it cannot find that property in that object.</p>
<p>Now you might wonder what if the prototype had another prototype, and that prototype had another prototype, and so on... Will JavaScript keep looking for the property down that chain?</p>
<p>Well the answer is yes. Try running the below snippet in console to see how it works:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> car <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">wheels</span><span class="token operator">:</span> <span class="token number">4</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">let</span> tesla <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>car<span class="token punctuation">)</span><br />tesla<span class="token punctuation">.</span>mode <span class="token operator">=</span> <span class="token string">'electric'</span><br /><br /><span class="token keyword">let</span> modelS <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>tesla<span class="token punctuation">)</span><br />modelS<span class="token punctuation">.</span>type <span class="token operator">=</span> <span class="token string">'sedan'</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>modelS<span class="token punctuation">.</span>wheels<span class="token punctuation">)</span> <span class="token comment">// 4</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>modelS<span class="token punctuation">.</span>mode<span class="token punctuation">)</span> <span class="token comment">// electric</span></code></pre>
<p>So whenever you access a property on an object, JavaScript looks if that property exists on that object. If not, it looks for it in its prototype and keeps repeating the same behavior until it reaches the end of the prototype chain and returns <code>undefined</code> only if nothing is found at the end of the chain.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/prototypes-chaining-visualization-480.webp 480w, https://prateeksurana.me/img/prototypes-chaining-visualization-768.webp 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/prototypes-chaining-visualization-480.jpeg 480w, https://prateeksurana.me/img/prototypes-chaining-visualization-768.jpeg 768w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/prototypes-chaining-visualization-768.jpeg" width="768" height="990" alt="Prototype chaining" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Now that we have an idea of how prototype chaining works let's see how we can use it to implement inheritance with constructor functions.</p>
<p>Consider we have the following constructor function:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Human</span><span class="token punctuation">(</span><span class="token parameter">name<span class="token punctuation">,</span> age<span class="token punctuation">,</span> gender</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<br /> <span class="token keyword">this</span><span class="token punctuation">.</span>age <span class="token operator">=</span> age<br /> <span class="token keyword">this</span><span class="token punctuation">.</span>gender <span class="token operator">=</span> gender<br /><span class="token punctuation">}</span><br /><br /><span class="token class-name">Human</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function-variable function">introduce</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><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hey there! I'm </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>name<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></code></pre>
<p>Let’s assume we want to create another constructor function called <code>Developer</code> that inherits from the above function.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Developer</span><span class="token punctuation">(</span><span class="token parameter">name<span class="token punctuation">,</span> age<span class="token punctuation">,</span> gender<span class="token punctuation">,</span> expertise</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">Human</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> name<span class="token punctuation">,</span> age<span class="token punctuation">,</span> gender<span class="token punctuation">)</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>expertise <span class="token operator">=</span> expertise<br /><span class="token punctuation">}</span></code></pre>
<p>In the above snippet, we use JavaScript’s <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call"><code>call</code></a> method to bind the <code>Developer</code> function’s <code>this</code> context when calling the <code>Human</code> function. The <code>call</code> method accepts the <code>this</code> context as the first argument and passes the rest of the arguments to the function itself.</p>
<p>But just calling the constructor function isn't enough since you won't be able to access the prototype methods and variables. The <code>Developer</code> function's <code>prototype</code> property is still the default one which is just an object with the <code>constructor</code> property that references the function itself.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> john <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Developer</span><span class="token punctuation">(</span><span class="token string">"John"</span><span class="token punctuation">,</span> <span class="token number">23</span><span class="token punctuation">,</span> <span class="token string">"male"</span><span class="token punctuation">,</span> <span class="token string">"Frontend"</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>john<span class="token punctuation">.</span>age<span class="token punctuation">)</span> <span class="token comment">// 23</span><br />console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>john<span class="token punctuation">.</span>expertise<span class="token punctuation">)</span> <span class="token comment">// Frontend</span><br />john<span class="token punctuation">.</span><span class="token function">introduce</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// TypeError: john.introduce is not a function</span></code></pre>
<p>So to fix that, we'll be using our old friend <code>Object.create</code> to create a new object <code>Human.prototype</code> as its prototype, and set <code>Developer.prototype</code> to that value:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token class-name">Developer</span><span class="token punctuation">.</span>prototype <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token class-name">Human</span><span class="token punctuation">.</span>prototype<span class="token punctuation">)</span></code></pre>
<p>Although this introduces a minor issue that the <code>Developer</code> function's constructor property now points to the <code>Human</code> function because we overrode its prototype, it is not something that we would want.</p>
<pre class="language-jsx"><code class="language-jsx">console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token class-name">Developer</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>constructor <span class="token operator">===</span> Human<span class="token punctuation">)</span> <span class="token comment">// true</span></code></pre>
<p>To fix that we'll define the <code>constructor</code> property on <code>Developer.prototype</code> that points to the function again</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token class-name">Developer</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>constructor <span class="token operator">=</span> Developer</code></pre>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>The way we did it above fixes the issue, but we would still have one little problem. If you were to use the <code>prototype</code> inside a <code>for in</code> loop, the <code>constructor</code> property would also appear in that loop, which you might not want. To fix that, you can define the <code>constructor</code> as a non-enumerable property on <code>Developer.prototype</code> using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty"><code>Object.defineProperty</code></a>.</p>
</aside>
<p>You can now also define new properties on <code>Developer.prototype</code> and access them on the objects created via the <code>Developer</code> constructor function</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token class-name">Developer</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function-variable function">describeExpertise</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> <br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hello I'm </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> and I'm a </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>expertise<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></code></pre>
<p>Let's try creating a new object, with the <code>Developer</code> constructor function</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> jane <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Developer</span><span class="token punctuation">(</span><span class="token string">"Jane"</span><span class="token punctuation">,</span> <span class="token number">23</span><span class="token punctuation">,</span> <span class="token string">"female"</span><span class="token punctuation">,</span> <span class="token string">"Android Developer"</span><span class="token punctuation">)</span><br /><br />jane<span class="token punctuation">.</span><span class="token function">introduce</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Hey there! I'm Jane</span><br />jane<span class="token punctuation">.</span><span class="token function">describeExpertise</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Hello I'm Jane and I'm a Android Developer</span></code></pre>
<p>Again here’s a visualization to help you grasp what’s happening here:</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/prototypes-prototypal-inheritance-visualization-480.webp 480w, https://prateeksurana.me/img/prototypes-prototypal-inheritance-visualization-768.webp 768w, https://prateeksurana.me/img/prototypes-prototypal-inheritance-visualization-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/prototypes-prototypal-inheritance-visualization-480.jpeg 480w, https://prateeksurana.me/img/prototypes-prototypal-inheritance-visualization-768.jpeg 768w, https://prateeksurana.me/img/prototypes-prototypal-inheritance-visualization-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/prototypes-prototypal-inheritance-visualization-768.jpeg" width="768" height="1003" alt="Prototypal inheritance example" class="article-img" loading="lazy" decoding="async" />
</picture>
<h2 class="relative">
<a id="classes-in-javascript" href="https://prateeksurana.me/blog/how-javascript-classes-work-under-the-hood/#classes-in-javascript" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Classes in JavaScript
</a>
</h2>
<p>So now that we understand how prototype and prototype chaining work and how they allow for prototypal inheritance in JavaScript, let's look at how these concepts apply to JavaScript classes.</p>
<p>As I mentioned in the beginning of the article, the <code>class</code> keyword was officially added to JavaScript with ES6 in 2015. The classes created with the <code>class</code> keyword are primarily syntactic sugar over the prototypal inheritance we saw in the previous section. Still, they also have some syntax and semantics that are not shared with pre-ES6 class-like semantics.</p>
<p>For example, let's take the <code>Human</code> function that we created in the previous section, adding it here for reference:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Human</span><span class="token punctuation">(</span><span class="token parameter">name<span class="token punctuation">,</span> age<span class="token punctuation">,</span> gender</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<br /> <span class="token keyword">this</span><span class="token punctuation">.</span>age <span class="token operator">=</span> age<br /> <span class="token keyword">this</span><span class="token punctuation">.</span>gender <span class="token operator">=</span> gender<br /><span class="token punctuation">}</span><br /><br /><span class="token class-name">Human</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function-variable function">introduce</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><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hey there! I'm </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>name<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></code></pre>
<p>The equivalent class code for the above function would be</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">class</span> <span class="token class-name">Human</span> <span class="token punctuation">{</span><br /> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">name<span class="token punctuation">,</span> age<span class="token punctuation">,</span> gender</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<br /> <span class="token keyword">this</span><span class="token punctuation">.</span>age <span class="token operator">=</span> age<br /> <span class="token keyword">this</span><span class="token punctuation">.</span> gender <span class="token operator">=</span> gender<br /> <span class="token punctuation">}</span><br /><br /> <span class="token function">introduce</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 template-string"><span class="token template-punctuation string">`</span><span class="token string">Hey there! I'm </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>name<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> <br /><span class="token punctuation">}</span></code></pre>
<p>As you can see, the syntax with <code>class</code> is much easier to understand and is similar to what you might have seen in some other languages. It gets even better when it comes to inheritance. Remember the changes we had to make to the <code>Developer</code> function so that it could inherit the prototype from <code>Human</code>. Let's retake a look at it for reference:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Developer</span><span class="token punctuation">(</span><span class="token parameter">name<span class="token punctuation">,</span> age<span class="token punctuation">,</span> gender<span class="token punctuation">,</span> expertise</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">Human</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> name<span class="token punctuation">,</span> age<span class="token punctuation">,</span> gender<span class="token punctuation">)</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>expertise <span class="token operator">=</span> expertise<br /><span class="token punctuation">}</span><br /><br /><span class="token comment">// Inherit the prototype from human</span><br /><span class="token class-name">Developer</span><span class="token punctuation">.</span>prototype <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token class-name">Human</span><span class="token punctuation">.</span>prototype<span class="token punctuation">)</span><br /><span class="token comment">// Add the overrridden constructor property again</span><br /><span class="token class-name">Developer</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>constructor <span class="token operator">=</span> Developer<br /><br /><span class="token class-name">Developer</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function-variable function">describeExpertise</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><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hello I'm </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> and I'm a </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>expertise<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></code></pre>
<p>This is how it looks like with the class syntax:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">class</span> <span class="token class-name">Developer</span> <span class="token keyword">extends</span> <span class="token class-name">Human</span> <span class="token punctuation">{</span><br /> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">name<span class="token punctuation">,</span> age<span class="token punctuation">,</span> gender<span class="token punctuation">,</span> expertise</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">super</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> age<span class="token punctuation">,</span> gender<span class="token punctuation">)</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>expertise <span class="token operator">=</span> expertise<br /> <span class="token punctuation">}</span><br /><br /> <span class="token function">describeExpertise</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 template-string"><span class="token template-punctuation string">`</span><span class="token string">Hello I'm </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> and I'm a </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>expertise<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><br /><span class="token punctuation">}</span></code></pre>
<p>Creating objects from class is exactly similar to what we did in the previous section.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">let</span> dev <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Developer</span><span class="token punctuation">(</span><span class="token string">"Dev"</span><span class="token punctuation">,</span> <span class="token number">33</span><span class="token punctuation">,</span> <span class="token string">"male"</span><span class="token punctuation">,</span> <span class="token string">"iOS Developer"</span><span class="token punctuation">)</span><br /><br />dev<span class="token punctuation">.</span><span class="token function">introduce</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span> <span class="token comment">// Hey there! I'm Dev</span><br />dev<span class="token punctuation">.</span><span class="token function">describeExpertise</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span> <span class="token comment">// Hello I'm Dev and I'm a iOS Developer</span></code></pre>
<p>Apart from the things we saw above JavaScript classes have multiple other features like private fields, mixins, getters etc. but those would be out of scope of this article. I would recommend you to check out the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes">MDN docs for classes</a> if you're interested in learning more about them.</p>
<h2 class="relative">
<a id="conclusion" href="https://prateeksurana.me/blog/how-javascript-classes-work-under-the-hood/#conclusion" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Conclusion
</a>
</h2>
<p>To summarize again, in this article we learned what prototypes are and what are they used for, how you can create objects with a custom prototype with constructor functions, what is prototype chaining and how you can achieve prototypal inheritance with it, and finally, how do all these concepts apply to JavaScript classes.</p>
<p>If you made it till here, I hope you learned something new about JavaScript and understand how JavaScript classes work under the hood.</p>
<p>P.S. - The visualizations I created for this article were made with <a href="https://excalidraw.com/">Excalidraw</a> and were inspired by the mental models from <a href="https://twitter.com/dan_abramov">Dan Abramov</a> and <a href="https://maggieappleton.com/">Maggie Appleton</a>’s <a href="https://justjavascript.com/">Just JavaScript</a> course.</p>
Simplify immutable data structures in useReducer with ImmerLearn how you can simplify deeply nested state updates when using useReducer with Immer.2022-03-06T00:00:00Z2022-03-06T00:00:00Zhttps://prateeksurana.me/blog/simplify-immutable-data-structures-in-usereducer-with-immer/<p>When it comes to state management in React, <a href="https://reactjs.org/docs/hooks-reference.html#usereducer"><code>useReducer</code></a> offers a robust API that shines when you have a complex state with multiple sub-values and using <code>useState</code> might be complicated. But things can still get a bit nasty when dealing with deeply nested objects in within your reducer. So in this article, we’ll see how Immer solves those problems and how you can significantly simplify your reducers with it.</p>
<h2>The problem</h2>
<p>If you have used <code>useReducer</code> in React, then you know that you cannot mutate the current state, and you need to return a new object if you want to trigger a re-render with the updated values. If you’re curious, here’s an excerpt from the React docs that explains why it is that way:</p>
<blockquote>
<p>If you return the same value from a Reducer Hook as the current state, React will bail out without rendering the children or firing effects. (React uses the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description"><code>Object.is</code> comparison algorithm</a>.)</p>
</blockquote>
<p>It’s usually not a problem when your state is just one-level or even a two-level nested object. For instance:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">reducer</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">prevState<span class="token punctuation">,</span> action</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">switch</span> <span class="token punctuation">(</span>action<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">case</span> <span class="token string">'increment-count'</span><span class="token operator">:</span><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span>prevState<br /> <span class="token literal-property property">count</span><span class="token operator">:</span> state<span class="token punctuation">.</span>count <span class="token operator">+</span> <span class="token number">1</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">case</span> <span class="token string">'decrement-count'</span><span class="token operator">:</span><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span>prevState<br /> <span class="token literal-property property">count</span><span class="token operator">:</span> state<span class="token punctuation">.</span>count <span class="token operator">-</span> <span class="token number">1</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token operator">...</span><br /> <span class="token comment">// Omitted for brevity</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>But things start to get hard to read and error-prone as you try to modify deeply nested objects, check the below example for reference:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">reducer</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">prevState<span class="token punctuation">,</span> action</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">switch</span> <span class="token punctuation">(</span>action<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">case</span> <span class="token string">'update-username'</span><span class="token operator">:</span><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span>prevState<span class="token punctuation">,</span><br /> <span class="token literal-property property">project</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span>prevState<span class="token punctuation">.</span>project<span class="token punctuation">,</span><br /> <span class="token literal-property property">users</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span>prevState<span class="token punctuation">.</span>project<span class="token punctuation">.</span>users<span class="token punctuation">,</span><br /> <span class="token punctuation">[</span>payload<span class="token punctuation">.</span>userID<span class="token punctuation">]</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span>prevState<span class="token punctuation">.</span>project<span class="token punctuation">.</span>users<span class="token punctuation">[</span>payload<span class="token punctuation">.</span>userID<span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">username</span><span class="token operator">:</span> payload<span class="token punctuation">.</span>username<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><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token operator">...</span><br /> <span class="token comment">// Omitted for brevity</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>That’s where Immer comes in and saves the day. But before that:</p>
<h2>What is Immer anyways?</h2>
<p>Glad you asked. If you read their description on <a href="https://immerjs.github.io/immer/">their homepage</a>, it says:</p>
<blockquote>
<p>Immer (German for: always) is a tiny package that allows you to work with immutable state in a more convenient way.</p>
</blockquote>
<p>So that description does sound like something we need from the previous section. But how does it work?</p>
<p>Immer provides you with a <code>produce</code> function that takes the state you want to modify as the first argument, and in the second argument is a function that receives a <code>draft</code> as its argument. Then you can modify the <code>draft</code> in that method, and the <code>produce</code> function would output the next state with the changes you made to the <code>draft</code>. The main thing to note here is that <strong>you can apply straightforward mutations to the <code>draft</code>, and those mutations are recorded and used to produce the next state</strong>. Let’s see an example to understand how it works:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</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">'Arya Stark'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">pets</span><span class="token operator">:</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">'Nymeria'</span><span class="token punctuation">,</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'Direwolf'</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 /><br /><span class="token keyword">const</span> nextState <span class="token operator">=</span> <span class="token function">produce</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">draft</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> draft<span class="token punctuation">.</span>name <span class="token operator">=</span> <span class="token string">'Jon Snow'</span><span class="token punctuation">;</span><br /> draft<span class="token punctuation">.</span>pets<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>name <span class="token operator">=</span> <span class="token string">'Ghost'</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</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>state <span class="token operator">===</span> nextState<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token comment">// false</span><br /><br />console<span class="token punctuation">.</span><span class="token function">log</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>state<span class="token punctuation">.</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">'s pet is </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>state<span class="token punctuation">.</span>pets<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> <br />and </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>nextState<span class="token punctuation">.</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">'s pet is </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>nextState<span class="token punctuation">.</span>pets<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>name<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 comment">// Arya Stark's pet is Nymeria</span><br /><span class="token comment">// and Jon Snow's pet is Ghost </span></code></pre>
<p>As you can see in the above example, the <code>state</code> variable remains untouched yet the <code>nextState</code> reflects the changes we made to the <code>draftState</code>. Isn’t that magical 🧙♀️</p>
<p>If you’re interested in how the magic works behind the scenes, you should check out <a href="https://medium.com/hackernoon/introducing-immer-immutability-the-easy-way-9d73d8f71cb3">this blog by the creators of Immer</a>.</p>
<h3>Curried producers</h3>
<p>If you’re not familiar with the term, currying is a process of converting a function that takes multiple arguments into a sequence of functions with a single argument. Check out <a href="https://stackoverflow.com/a/36321/8252081">this explanation on StackOverflow</a> if the above sentence doesn’t make sense to you.</p>
<p>So apart from the behaviour of <code>produce</code> that we saw above, if you pass it a function as the first argument it creates a function that doesn’t apply <code>produce</code> yet to a specific state, but instead creates a function that will apply <code>produce</code> to any state that is passed to it in the future.</p>
<p>For example :</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</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">'Thor'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">abilities</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'Worthy for Mjölnir'</span><span class="token punctuation">,</span> <span class="token string">'God of Thunder'</span><span class="token punctuation">]</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">const</span> addAbility <span class="token operator">=</span> <span class="token function">produce</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">draft<span class="token punctuation">,</span> ability</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> draft<span class="token punctuation">.</span>abilities<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>ability<span class="token punctuation">)</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><br /><br /><span class="token keyword">const</span> nextState <span class="token operator">=</span> <span class="token function">addAbility</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> <span class="token string">'Virtually Immortal'</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>nextState<span class="token punctuation">.</span>abilities<span class="token punctuation">)</span> <br /><span class="token comment">// ['Worthy for Mjölnir', 'God of Thunder', 'Virtually Immortal']</span></code></pre>
<h2>useReducer with Immer</h2>
<p>Now that we have seen how Immer works, you might have an idea of how it will solve the problem that we saw in the beginning with <code>useReducer</code>.</p>
<p>The best way to use Immer with a <code>useReducer</code> is using curried <code>produce</code> that we saw in the previous section. So, for instance if you had a reducer like this.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span>state<span class="token punctuation">,</span> dispatch<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useReducer</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">state<span class="token punctuation">,</span> action</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">switch</span> <span class="token punctuation">(</span>action<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">case</span> <span class="token string">'update-email'</span><span class="token operator">:</span><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span>state<span class="token punctuation">,</span><br /> <span class="token literal-property property">users</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span>state<span class="token punctuation">.</span>users<span class="token punctuation">,</span><br /> <span class="token punctuation">[</span>action<span class="token punctuation">.</span>userID<span class="token punctuation">]</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token operator">...</span>state<span class="token punctuation">.</span>users<span class="token punctuation">[</span>action<span class="token punctuation">.</span>payload<span class="token punctuation">.</span>userID<span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">email</span><span class="token operator">:</span> action<span class="token punctuation">.</span>payload<span class="token punctuation">.</span>email<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 punctuation">}</span><br /> <span class="token operator">...</span><br /> <span class="token comment">// Omitted for brevity</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">,</span> initialState<span class="token punctuation">)</span><br /><br /><span class="token comment">// And then somewhere inside the component...</span><br /><span class="token function">dispatch</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <br /> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'update-email'</span><span class="token punctuation">,</span> <br /> <span class="token literal-property property">payload</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">userID</span><span class="token operator">:</span> <span class="token string">'john'</span><span class="token punctuation">,</span> <span class="token literal-property property">email</span><span class="token operator">:</span> <span class="token string">'john@doe.com'</span> <span class="token punctuation">}</span> <br /><span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre>
<p>This is how it would look like with a curried <code>produce</code>:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span>state<span class="token punctuation">,</span> dispatch<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useReducer</span><span class="token punctuation">(</span><br /> <span class="token function">produce</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">draft<span class="token punctuation">,</span> action</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">switch</span> <span class="token punctuation">(</span>action<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">case</span> <span class="token string">'update-email'</span><span class="token operator">:</span><br /> draft<span class="token punctuation">.</span>users<span class="token punctuation">[</span>action<span class="token punctuation">.</span>payload<span class="token punctuation">.</span>userID<span class="token punctuation">]</span><span class="token punctuation">.</span>email <span class="token operator">=</span> action<span class="token punctuation">.</span>payload<span class="token punctuation">.</span>email<span class="token punctuation">;</span><br /> <span class="token keyword">break</span><span class="token punctuation">;</span><br /> <span class="token operator">...</span><br /> <span class="token comment">// Omitted for brevity</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> initialState<br /><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Neat isn’t it ;)</p>
<h2>But what about TypeScript?</h2>
<p>Immer ships with basic type definitions out of the box, which can be picked by TypeScript without any further configuration. Also <code>produce</code> can automatically infer types based on the input provided:</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token keyword">interface</span> <span class="token class-name">Character</span> <span class="token punctuation">{</span><br /> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span><br /> family<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">const</span> character<span class="token operator">:</span> Character <span class="token operator">=</span> <span class="token punctuation">{</span><br /> name<span class="token operator">:</span> <span class="token string">'Arya'</span><span class="token punctuation">,</span><br /> family<span class="token operator">:</span> <span class="token string">'Stark'</span><br /><span class="token punctuation">}</span><br /><br /><span class="token comment">// Type of nextChacracter would also be State</span><br /><span class="token keyword">const</span> nextCharacter <span class="token operator">=</span> <span class="token function">produce</span><span class="token punctuation">(</span>character<span class="token punctuation">,</span> draft <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// ✅ You also get type safety here</span><br /> draft<span class="token punctuation">.</span>name <span class="token operator">=</span> <span class="token string">'Sansa'</span><br /> <span class="token comment">// ❌ Modifying any other property would result in a type error</span><br /> <span class="token comment">// draft.direwolf = 'Lady'</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre>
<p>When it comes to curried <code>produce</code> they can also infer the types as best as possible, but in cases where they cannot be inferred directly it is recommended to use generics instead:</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token comment">// Apart from the state, type of any additional arguments needs to be defined</span><br /><span class="token comment">// as a tuple. So in the case below since the curried produce accepts a string</span><br /><span class="token comment">// for the name of the character, we take it as the second argument.</span><br /><span class="token keyword">const</span> changeCharacterName <span class="token operator">=</span> <span class="token generic-function"><span class="token function">produce</span><span class="token generic class-name"><span class="token operator"><</span>Character<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>draft<span class="token punctuation">,</span> name<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> draft<span class="token punctuation">.</span>name <span class="token operator">=</span> name<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 keyword">const</span> nextCharacter <span class="token operator">=</span> <span class="token function">changeCharacterName</span><span class="token punctuation">(</span>character<span class="token punctuation">,</span> <span class="token string">"Jon"</span><span class="token punctuation">)</span> </code></pre>
<p>Lastly, when it comes to type-safety with <code>useReducer</code> I prefer using <a href="https://github.com/immerjs/use-immer"><code>useImmerReducer</code></a> because it works great with TypeScript. Under the hood it's just a <a href="https://github.com/immerjs/use-immer/blob/master/src/index.ts#L29-L41">small wrapper over <code>useReducer</code> with <code>produce</code></a> similar to what we saw in the previous section but it works pretty well with TypeScript. Here’s a small example that demonstrates how it works with TypeScript:</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token keyword">interface</span> <span class="token class-name">Todo</span> <span class="token punctuation">{</span><br /> id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span><br /> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span><br /> done<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">type</span> <span class="token class-name">State</span> <span class="token operator">=</span> <span class="token builtin">Array</span><span class="token operator"><</span>Todo<span class="token operator">></span><span class="token punctuation">;</span><br /><br /><span class="token keyword">type</span> <span class="token class-name">Action</span> <span class="token operator">=</span><br /> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">"ADD_TODO"</span><span class="token punctuation">;</span> text<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><br /> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">"TOGGLE_TODO"</span><span class="token punctuation">;</span> id<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><br /> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">"REMOVE_TODO"</span><span class="token punctuation">;</span> id<span class="token operator">:</span> <span class="token builtin">string</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">reducer</span> <span class="token operator">=</span> <span class="token punctuation">(</span>draft<span class="token operator">:</span> State<span class="token punctuation">,</span> action<span class="token operator">:</span> Action<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">switch</span> <span class="token punctuation">(</span>action<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">case</span> <span class="token string">"ADD_TODO"</span><span class="token operator">:</span><br /> draft<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span> id<span class="token operator">:</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> name<span class="token operator">:</span> action<span class="token punctuation">.</span>text<span class="token punctuation">,</span> done<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 keyword">break</span><span class="token punctuation">;</span><br /> <span class="token keyword">case</span> <span class="token string">"TOGGLE_TODO"</span><span class="token operator">:</span><br /> <span class="token keyword">const</span> todo <span class="token operator">=</span> draft<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">(</span>todo<span class="token punctuation">)</span> <span class="token operator">=></span> todo<span class="token punctuation">.</span>id <span class="token operator">===</span> action<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">break</span><span class="token punctuation">;</span><br /> <span class="token keyword">case</span> <span class="token string">"REMOVE_TODO"</span><span class="token operator">:</span><br /> draft<span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span>draft<span class="token punctuation">.</span><span class="token function">findIndex</span><span class="token punctuation">(</span><span class="token punctuation">(</span>todo<span class="token punctuation">)</span> <span class="token operator">=></span> todo<span class="token punctuation">.</span>id <span class="token operator">===</span> action<span class="token punctuation">.</span>id<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">break</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> <span class="token function-variable function">TodoList</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>todos<span class="token punctuation">,</span> dispatch<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token generic-function"><span class="token function">useImmerReducer</span><span class="token generic class-name"><span class="token operator"><</span>State<span class="token punctuation">,</span> Action<span class="token operator">></span></span></span><span class="token punctuation">(</span><br /> reducer<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 operator">...</span><br /> <span class="token comment">// Rest of rendering logic</span><br /><span class="token punctuation">}</span></code></pre>
<h2>Conclusion</h2>
<p>So to summarize in this short guide we saw:</p>
<ul>
<li>The problem that one faces with <code>useReducer</code> when dealing with nested data structures.</li>
<li>How Immer solves the problem by giving you the ability to apply straightforward mutations to objects.</li>
<li>Lastly we saw how you can use Immer with <code>useReducer</code> to simplify your state updates and how it nicely integrates with TypeScript.</li>
</ul>
<p>Keep in mind you don’t need to go around and transform all the reducer functions in your code to use Immer. If the object you are dealing with is not deeply nested, it’s completely fine to clone it via shallow copying the previous state.</p>
<p>But when you think that keeping track of state updates are getting out of hand, then it might be a good idea to use Immer and just forget about the complexity of copying over state.</p>
The future of rendering in ReactUnderstand what are the problems with current rendering patterns in React, and how the new rendering patterns introduced with React 18 and future versions of React aim to solve them.2022-10-06T00:00:00Z2023-10-01T00:00:00Zhttps://prateeksurana.me/blog/future-of-rendering-in-react/<p>The popularity of React as a UI building library has only been growing and rather accelerating over the past few years. At the time of writing this article, it has <a href="https://www.npmjs.com/package/react">14 million+ weekly npm downloads</a>, which I know isn’t a correct measure for the popularity of a library, but the React Devtools chrome extension alone also has more than <a href="https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en">3 million weekly active users</a>. However, the rendering patterns in React have almost been the same until React 18.</p>
<p>So in this article, we’ll be looking at React's current rendering patterns, their problems, and how the new patterns introduced with React 18 aim to fix those problems.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>More of a video person? I gave a talk at <a href="https://twitter.com/ReactBangalore">React Bangalore</a> on the same topic. Check it out here:</p>
<div class="video-container mb-8">
<iframe width="650" height="367" src="https://www.youtube.com/embed/6wsw6eKdIOM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
</aside>
<h2>Web vitals terminology</h2>
<p>Before we dive into the rendering patterns, let’s look at some web vital terminology that we’ll be using throughout this post:</p>
<ul>
<li><strong>Time To First Byte (TTFB)</strong> - The time it takes for the client to receive the first byte of content.</li>
<li><strong>First Paint (FP)</strong> - The time when the first pixel gets visible to the user.</li>
<li><strong>First Contentful Paint (FCP)</strong> - The time it takes for the first piece of content to be visible.</li>
<li><strong>Largest Contentful Paint (LCP)</strong> - The time it takes for the main content of the page to be loaded.</li>
<li><strong>Time To Interactive (TTI)</strong> -The time at which the page becomes interactive and reliably responds to user events.</li>
</ul>
<h2>The current rendering patterns</h2>
<p>Right now, the most common patterns we use in React are Client-side rendering and Server Side rendering and some advanced forms of server rendering offered by frameworks like Next.js, Static Site Generation, and Incremental Static Regeneration. We’ll look into each of those and dive into the newer patterns introduced with React 18 and available in the future.</p>
<h3>Client-Side Rendering (CSR)</h3>
<p>Client-side rendering, mostly with <a href="https://reactjs.org/docs/create-a-new-react-app.html">create-react-app</a> or other similar starters, was the default way for building React apps for some time before meta-frameworks like Next.js and Remix came along.</p>
<p>With CSR, the server only provides barebones HTML for every page containing the necessary script and link tags. Once the relevant JavaScript is downloaded to the browser. React renders the tree and generates all the DOM nodes. All the logic for routing and data fetching is handled by the client-side JavaScript as well.</p>
<p>To see how it works, let’s consider that we are rendering the below app:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// App.jsx</span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Layout</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">Navbar</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">Sidebar</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">RightPane</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">Post</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">Comments</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">RightPane</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">Layout</span></span><span class="token punctuation">></span></span></code></pre>
<p>This is what the render cycle of the above app will look like:</p>
<p><video src="https://prateeksurana.me/videos/future-of-rendering-csr-demo.webm" controls="" loop="" style="margin: 0px;">Looks like your browser doesn't support this video you can download the video <a href="https://prateeksurana.me/videos/future-of-rendering-csr-demo.webm">here</a>.</video></p>
<p>And hence this is the network graph:</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/future-of-rendering-csr-network-graph-480.webp 480w, https://prateeksurana.me/img/future-of-rendering-csr-network-graph-768.webp 768w, https://prateeksurana.me/img/future-of-rendering-csr-network-graph-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/future-of-rendering-csr-network-graph-480.jpeg 480w, https://prateeksurana.me/img/future-of-rendering-csr-network-graph-768.jpeg 768w, https://prateeksurana.me/img/future-of-rendering-csr-network-graph-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/future-of-rendering-csr-network-graph-768.jpeg" width="768" height="283" alt="Client side rendering network graph" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>So CSR apps have a fast time to first byte because they rely on mostly static assets. However, users must stare at a blank screen until the relevant JavaScript is downloaded. Even after that, most real-world apps would need to fetch data from an API to display relevant data to the users, which leads to a very slow LCP.</p>
<h4>Advantages of CSR</h4>
<ul>
<li>Since the Client-side rendering architecture comprises static files, it can be very easily served via a CDN.</li>
<li>All the rendering is done on the client hence CSR allows us to have navigations without full page refreshes, providing a good UX.</li>
<li>The time to the first byte is fast hence the browser can immediately start loading the fonts, CSS, and JavaScript.</li>
</ul>
<h4>Problems with CSR:</h4>
<ul>
<li>Since all the content is rendered on the client, <strong>performance</strong> takes a big hit because the users first need to download and process it to see the content on the page.</li>
<li>Client-side rendering applications usually <strong>fetch the data</strong> they need on the component mount, leading to a bad user experience since they would be greeted with a bunch of loaders on the initial page load. Also, this can get worse if child components have data fetching requirements, but they are not rendered until their parents have fetched all the data, which can lead to a cascade of loaders and a bad network waterfall.</li>
<li>Lastly, <strong>SEO</strong> is a problem with client-side rendering applications because web crawlers can easily read the server-rendered HTML, but they might not wait to download all the JavaScript bundles, execute them and wait for the client-side data fetching waterfalls to finish, which can lead to improper indexing.</li>
</ul>
<h3>Server-side rendering</h3>
<p>The way that server-side rendering works in React right now is:</p>
<ul>
<li>We fetch relevant data and run the client-side JavaScript on the server for the page via <a href="https://reactjs.org/docs/react-dom-server.html#rendertostring"><code>renderToString</code></a>, which gives us all the HTML necessary for displaying a page.</li>
<li>This HTML is then served to the client, leading to a fast First Contentful Paint.</li>
<li>But we’re not done yet; we still need to download and execute the client-side JavaScript to connect the JavaScript logic to the server-generated HTML to make the page interactive (this process is what we call “hydration”). If you are further interested in understanding why rehydration is necessary and how it works, check out <a href="https://www.joshwcomeau.com/react/the-perils-of-rehydration/">The Perils of Rehydration article by Josh</a>.</li>
</ul>
<p>To better understand how it works, let’s look at the lifecycle of the same app that we saw in the previous section with SSR:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// App.jsx</span><br /><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Layout</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">Navbar</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">Sidebar</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">RightPane</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">Post</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">Comments</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">RightPane</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">Layout</span></span><span class="token punctuation">></span></span></code></pre>
<p><video src="https://prateeksurana.me/videos/future-of-rendering-ssr-demo.webm" controls="" loop="">Looks like your browser doesn't support this video you can download the video <a href="https://prateeksurana.me/videos/future-of-rendering-ssr-demo.webm">here</a>.</video></p>
<p>And hence this leads to the following network graph:</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/future-of-rendering-ssr-network-graph-480.webp 480w, https://prateeksurana.me/img/future-of-rendering-ssr-network-graph-768.webp 768w, https://prateeksurana.me/img/future-of-rendering-ssr-network-graph-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/future-of-rendering-ssr-network-graph-480.jpeg 480w, https://prateeksurana.me/img/future-of-rendering-ssr-network-graph-768.jpeg 768w, https://prateeksurana.me/img/future-of-rendering-ssr-network-graph-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/future-of-rendering-ssr-network-graph-768.jpeg" width="768" height="314" alt="Server side rendering network graph" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>So with SSR, we get a good FCP and LCP, but the TTFB suffers because we have to fetch data on the server and then convert it to HTML string.</p>
<p>Now you might ask where Next.js’ SSG/ISR fits here. They also have to go through the same process we saw above. The only difference is that they don’t suffer from a slow Time To First byte because the HTML is either generated at build time or is generated and cached incrementally as requests come in.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/future-of-rendering-ssg-isr-network-graph-480.webp 480w, https://prateeksurana.me/img/future-of-rendering-ssg-isr-network-graph-768.webp 768w, https://prateeksurana.me/img/future-of-rendering-ssg-isr-network-graph-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/future-of-rendering-ssg-isr-network-graph-480.jpeg 480w, https://prateeksurana.me/img/future-of-rendering-ssg-isr-network-graph-768.jpeg 768w, https://prateeksurana.me/img/future-of-rendering-ssg-isr-network-graph-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/future-of-rendering-ssg-isr-network-graph-768.jpeg" width="768" height="314" alt="Static Site generation/Incremental Static regeneration network graph" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>But SSG/ISR is not a silver bullet; in my experience, they work best for public pages but for pages that change based on the user’s logged-in status or some other cookies stored on the browser, you would have to use SSR.</p>
<h4>Advantages of SSR</h4>
<ul>
<li>Unlike CSR, SEO is much better since all the HTML is pre-generated from the server and web crawlers have no problem crawling through that.</li>
<li>The FCP and LCP are pretty fast. Hence the user has something to see instead of looking at a blank screen like in the case of CSR apps.</li>
</ul>
<h4>Problems with SSR</h4>
<ul>
<li>
<p>Since we are rendering the page on the server first with every request and have to wait for the data requirements for the page, it can lead to a <strong>slow TTFB,</strong> which can keep the users waiting to look at the browser spinner. This can happen for multiple reasons, including unoptimized server code or many simultaneous server requests.</p>
<p>Although would also like to add that frameworks like Next.js somewhat solve this problem by allowing you to generate pages ahead of time and caching them on the server with techniques like SSG (Static site generation) and ISR (Incremental static site generation).</p>
</li>
<li>
<p>Lastly, even though the initial load is fast, the users still have to pay to download all the JavaScript for the page and process it so that the page can be rehydrated and become interactive.</p>
</li>
</ul>
<h2>The new rendering patterns</h2>
<p>In the previous section, we saw that what are the current rendering patterns in React and what are the problems with them. To summarize:</p>
<ul>
<li>In CSR apps, the users must download all the necessary JavaScript and execute it to view/interact with the page.</li>
<li>With SSR, we solve some of these problems by generating the HTML on the server. Yet it is not optimal since first we have to wait on the server to fetch all data and generate the HTML. Then the client has to download the JavaScript for the whole page. Lastly, since hydration is a single pass in React, we have to execute the JavaScript to connect the server-generated HTML and the JavaScript logic so that the page can be interactive. So the primary issue is we have to wait for each step to finish before we can start with the next one.</li>
</ul>
<p>The React team is working on some new patterns that aim to solve these problems.</p>
<h3>Streaming SSR</h3>
<p>One cool thing about browsers is that they can receive HTML via HTTP streams. Streaming allows the web server to send data to a client over a single HTTP connection that can remain open indefinitely. So you can load data on the browsers over a network in multiple chunks, which are loaded out of order parallel to rendering.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>If you are curious and want to see how streaming works in practice, you can also check the <a href="https://shopify.dev/custom-storefronts/hydrogen/framework/streaming-ssr">Streaming SSR post</a> by the Hydrogen team.</p>
</aside>
<h4>Streaming before React 18</h4>
<p>Streaming rendering isn’t something brand new in React 18. In fact, it has existed since React 16. React 16 had a method called <a href="https://reactjs.org/docs/react-dom-server.html#rendertonodestream"><code>renderToNodeStream</code></a> which, unlike <a href="https://reactjs.org/docs/react-dom-server.html#rendertostring"><code>renderToString</code></a>, rendered the frontend as an HTTP stream to the browser.</p>
<p>This allowed you to send down HTML in chunks parallel to rendering it, enabling a faster time to first byte and largest contentful paint for your users since the initial markup arrives in the browser sooner.</p>
<p>Max Stoiber illustrated the difference nicely in <a href="https://mxstbr.com/thoughts/streaming-ssr/">his article about how they used it at Spectrum</a>.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/future-of-rendering-max-stoiber-streaming-netwrok-graph-480.webp 480w, https://prateeksurana.me/img/future-of-rendering-max-stoiber-streaming-netwrok-graph-768.webp 768w, https://prateeksurana.me/img/future-of-rendering-max-stoiber-streaming-netwrok-graph-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/future-of-rendering-max-stoiber-streaming-netwrok-graph-480.jpeg 480w, https://prateeksurana.me/img/future-of-rendering-max-stoiber-streaming-netwrok-graph-768.jpeg 768w, https://prateeksurana.me/img/future-of-rendering-max-stoiber-streaming-netwrok-graph-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/future-of-rendering-max-stoiber-streaming-netwrok-graph-768.jpeg" width="768" height="516" alt="Max Stoiber's illustration of how renderToNodeStream is different from renderToString" class="article-img" loading="lazy" decoding="async" />
</picture>
<h4>Streaming SSR with React 18</h4>
<p>React 18 deprecates the <code>renderToNodeStream</code> API in favor of a newer API called <a href="https://reactjs.org/docs/react-dom-server.html#rendertopipeablestream"><code>renderToPipeableStream</code></a>, which unlocks some new features with Suspense that allow you to break down your app into smaller independent units that can go through the steps that we saw with SSR independently. This is possible because of two major features added with Suspense:</p>
<ul>
<li>Streaming HTML on the server</li>
<li>Selective hydration on the client</li>
</ul>
<p><strong>Streaming HTML on the server</strong></p>
<p>As mentioned previously, the SSR before React 18 was an all or nothing approach; first, you needed to fetch any data requirements that the page had, generate the HTML, and then send it to the client. This is no longer the case, thanks to <a href="https://developer.mozilla.org/en-US/docs/Web/API/Streams_API">HTTP streaming</a>.</p>
<p>The way it will work with React 18 is that you can wrap the components that might take a longer time to load and are not immediately required on screen in Suspense. Again to understand how it works, let’s assume that the Comments API is slow, so we wrap the Comments component in Suspense.</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">Layout</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">NavBar</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">Sidebar</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">RightPane</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">Post</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">Suspense</span></span> <span class="token attr-name">fallback</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</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 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">Comments</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">Suspense</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">RightPane</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">Layout</span></span><span class="token punctuation">></span></span></code></pre>
<p>This way, comments are not there in the initial HTML, and the user gets the fallback spinner in its place initially:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>main</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>nav</span><span class="token punctuation">></span></span><br /> <span class="token comment"><!--NavBar --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Home<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>nav</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>aside</span><span class="token punctuation">></span></span><br /> <span class="token comment"><!-- Sidebar --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/profile<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Profile<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>aside</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>article</span><span class="token punctuation">></span></span><br /> <span class="token comment"><!-- Post --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>Hello world<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>article</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>section</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>comments-spinner<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token comment"><!-- Spinner --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>400</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>spinner.gif<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Loading...<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>section</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>main</span><span class="token punctuation">></span></span></code></pre>
<p>Finally, when the data is ready for Comments on the server, React will send minimal HTML to the same stream with an inline script tag to put the HTML in the right place:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">hidden</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>comments<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token comment"><!-- Comments --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>First comment<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>Second comment<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /><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 tag"><span class="token tag"><span class="token punctuation"><</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br /> <span class="token comment">// This implementation is slightly simplified</span><br /> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'sections-spinner'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">replaceChildren</span><span class="token punctuation">(</span><br /> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'comments'</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>Hence this solves the first problem since we now don’t have to wait for all the data to be fetched on the server and the browser can start rendering the rest of the app, even if some parts are not ready.</p>
<p><strong>Selective hydration</strong></p>
<p>Even though the HTML got streamed, the page won’t be interactive unless the whole JavaScript for the page is downloaded. That’s where selective hydration comes in.</p>
<p>One way to avoid large bundles on a page during client-side rendering was code-splitting via <code>React.lazy</code>. It specifies that a particular piece of your app doesn’t need to load synchronously, and your bundler would have split it off into a separate script tag.</p>
<p>The limitation with <code>React.lazy</code> was that it doesn’t work with server-side rendering, but now with React 18, <code><Suspense></code> apart from allowing you to stream the HTML, it also lets you unblock hydration for the rest of the app.</p>
<p>So now, <code>React.lazy</code> works out of the box on the server. When you wrap your lazy component in <code><Suspense></code>, you not only tell React that you want it to be streamed but also allow the rest of hydrated even if the component wrapped in <code><Suspense></code> is still being streamed. This also solves the second problem that we saw in traditional server-side rendering. You no longer have to wait for all the JavaScript to be downloaded before you start hydrating.</p>
<p>Again, let’s look at the lifecycle of the app with Comments wrapped in Suspense, and using the new Suspense Architecture</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">Layout</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">NavBar</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">Sidebar</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">RightPane</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">Post</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">Suspense</span></span> <span class="token attr-name">fallback</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</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 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">Comments</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">Suspense</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">RightPane</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">Layout</span></span><span class="token punctuation">></span></span></code></pre>
<p><video src="https://prateeksurana.me/videos/future-of-rendering-streaming-ssr-with-suspense-demo.webm" controls="" loop="">Looks like your browser doesn't support this video you can download the video <a href="https://prateeksurana.me/videos/future-of-rendering-streaming-ssr-with-suspense-demo.webm">here</a>.</video></p>
<p>This leads to a network graph that looks something like this:</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/future-of-rendering-streaming-ssr-with-suspense-network-graph-480.webp 480w, https://prateeksurana.me/img/future-of-rendering-streaming-ssr-with-suspense-network-graph-768.webp 768w, https://prateeksurana.me/img/future-of-rendering-streaming-ssr-with-suspense-network-graph-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/future-of-rendering-streaming-ssr-with-suspense-network-graph-480.jpeg 480w, https://prateeksurana.me/img/future-of-rendering-streaming-ssr-with-suspense-network-graph-768.jpeg 768w, https://prateeksurana.me/img/future-of-rendering-streaming-ssr-with-suspense-network-graph-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/future-of-rendering-streaming-ssr-with-suspense-network-graph-768.jpeg" width="768" height="422" alt="Streaming SSR with Suspense network graph" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Again this is a very contrived example, but what it is trying to demonstrate is that with Suspense a lot of stuff that was happening serially now happens in parallel.</p>
<p>Which helps us in not only getting a faster time to first byte since the HTML was streamed but also, the users don’t have to wait for all the JavaScript to be downloaded to be able to start interacting with the app. Other benefits about streaming rendering that I could not include in this network graph is that it also helps load other assets (CSS, JavaScript, fonts, etc.) as soon as the page starts streaming, helping parallelizing even more requests, as <a href="https://youtu.be/95B8mnhzoCM?t=1260">Ryan mentioned in his talk</a>.</p>
<p>Another cool thing is that if you have multiple components wrapped in Suspense and haven’t been hydrated on the client yet, but the user starts interacting with one of them, React will prioritize hydrating that component first. You can check this out and all the things discussed above in more detail in the <a href="https://github.com/reactwg/react-18/discussions/37">New Suspense Architecture discussion</a>.</p>
<h3>Server components (Alpha)</h3>
<p>In the last section, we saw how we could improve the server-side rendering performance by breaking our app into smaller units and streaming and hydrating them separately. But what if there was a way where you didn’t need to hydrate parts of your application at all?</p>
<p>Well, this is where the new Server Components RFC comes in which is meant to compliment server-side rendering, allowing you to have components that only render on the server and have no interactivity.</p>
<p>To give a brief overview, the way they work is that you can create non-interactive server components with <code>.server.js/jsx/ts/tsx</code> extensions, and they can then seamlessly integrate and pass props to client components (with <code>.client.js/jsx/ts/tsx</code> extensions) which can handle the interactive parts of the page. Here is a more detailed list of the features that they offer:</p>
<ul>
<li>
<p><strong>Zero client-side bundle:</strong> Server components are only rendered on the server and don’t need to be hydrated. They allow us to render static content on the server while incurring zero effect on the client-side bundle size. This can be particularly useful if you’re using a heavy library and has no interactivity, and it can be completely rendered on the server with no effect on the client-side bundle. A great example of this would be the Notes preview <a href="https://github.com/josephsavona/rfcs/blob/server-components/text/0000-server-components.md#zero-bundle-size-components">from the RFC</a>:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// NoteWithMarkdown.js</span><br /><span class="token comment">// NOTE: *before* Server Components</span><br /><br /><span class="token keyword">import</span> marked <span class="token keyword">from</span> <span class="token string">'marked'</span><span class="token punctuation">;</span> <span class="token comment">// 35.9K (11.2K gzipped)</span><br /><span class="token keyword">import</span> sanitizeHtml <span class="token keyword">from</span> <span class="token string">'sanitize-html'</span><span class="token punctuation">;</span> <span class="token comment">// 206K (63.3K gzipped)</span><br /><br /><span class="token keyword">function</span> <span class="token function">NoteWithMarkdown</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>text<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> html <span class="token operator">=</span> <span class="token function">sanitizeHtml</span><span class="token punctuation">(</span><span class="token function">marked</span><span class="token punctuation">(</span>text<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 comment">/* render */</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// NoteWithMarkdown.server.js - Server Component === zero bundle size</span><br /><br /><span class="token keyword">import</span> marked <span class="token keyword">from</span> <span class="token string">'marked'</span><span class="token punctuation">;</span> <span class="token comment">// zero bundle size</span><br /><span class="token keyword">import</span> sanitizeHtml <span class="token keyword">from</span> <span class="token string">'sanitize-html'</span><span class="token punctuation">;</span> <span class="token comment">// zero bundle size</span><br /><br /><span class="token keyword">function</span> <span class="token function">NoteWithMarkdown</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>text<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// same as before</span><br /><span class="token punctuation">}</span></code></pre>
</li>
<li>
<p><strong>Server components don’t have interactivity but can compose with client components for it</strong>: Since they only render on the server, they are just React component that takes in props and renders a view. Hence they can’t have stuff like state, effects, and event handlers like we have with regular client components.</p>
<p>Although they can import client components that can have interactivity and be hydrated when rendered on the client, as we saw with normal SSR. Client components similar to their server counterparts are defined with <code>.client.jsx</code> or <code>.client.tsx</code> suffix.</p>
<p>This composability allows the developers to save significant bundle size on pages, like detail pages with mostly static content with few interactive elements. For example:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// Post.server.js</span><br /><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> parseISO<span class="token punctuation">,</span> format <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'date-fns'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> marked <span class="token keyword">from</span> <span class="token string">'marked'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> sanitizeHtml <span class="token keyword">from</span> <span class="token string">'sanitize-html'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">import</span> Comments <span class="token keyword">from</span> <span class="token string">'../Comments.server.jsx'</span><br /><span class="token comment">// Importing a client component</span><br /><span class="token keyword">import</span> AddComment <span class="token keyword">from</span> <span class="token string">'../AddComment.client.jsx'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">Post</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> content<span class="token punctuation">,</span> created_at<span class="token punctuation">,</span> title<span class="token punctuation">,</span> slug <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> html <span class="token operator">=</span> <span class="token function">sanitizeHtml</span><span class="token punctuation">(</span><span class="token function">marked</span><span class="token punctuation">(</span>content<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> formattedDate <span class="token operator">=</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token function">parseISO</span><span class="token punctuation">(</span>created_at<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">'dd/MM/yyyy'</span><span class="token punctuation">)</span><br /><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>main</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>h1</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>h1</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><span class="token punctuation">></span></span><span class="token plain-text">Posted on </span><span class="token punctuation">{</span>formattedDate<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 plain-text"><br /> </span><span class="token punctuation">{</span>content<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">AddComment</span></span> <span class="token attr-name">slug</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>slug<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">Comments</span></span> <span class="token attr-name">slug</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>slug<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>main</span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><br /><span class="token punctuation">}</span></code></pre>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// AddComment.client.js</span><br /><br /><span class="token keyword">function</span> <span class="token function">AddComment</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> hasUpvoted<span class="token punctuation">,</span> postSlug <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /><br /> <span class="token keyword">const</span> <span class="token punctuation">[</span>comment<span class="token punctuation">,</span> setComment<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 /> <br /> <span class="token keyword">function</span> <span class="token function">handleCommentChange</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">setComment</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span> <br /><br /> <span class="token keyword">function</span> <span class="token function">handleSubmit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// ..post the comment</span><br /> <span class="token punctuation">}</span> <br /><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>form</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>textarea</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>comment<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>handleCommentChange<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>comment<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">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"><br /> Comment<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 tag"><span class="token tag"><span class="token punctuation"></</span>form</span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><br /><span class="token punctuation">}</span></code></pre>
<p>The above snippets are just a contrived example of how server components compose with client components. Let’s break it down:</p>
<ul>
<li>We have the <code>Post</code> server component, which has mostly static data, including the post title, content, and the date it was published.</li>
<li>Since server components can’t have any interactivity, we have imported a client component called <code>AddComment</code>, which enables the user to add a comment.</li>
</ul>
<p>The thing I love the most here is that all the date and markdown parsing libraries we imported in the server component are never downloaded on the client. The only JavaScript we download on the client is for the <code>AddComment</code> component.</p>
</li>
<li>
<p><strong>Server components can access the backend directly</strong>: Since they are only rendered on the server, you can use them to access databases, and other backend-only data sources, directly from your components, like this:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// Post.server.js</span><br /><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> parseISO<span class="token punctuation">,</span> format <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'date-fns'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> marked <span class="token keyword">from</span> <span class="token string">'marked'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> sanitizeHtml <span class="token keyword">from</span> <span class="token string">'sanitize-html'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">import</span> db <span class="token keyword">from</span> <span class="token string">'db.server'</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// Importing a client component</span><br /><span class="token keyword">import</span> Upvote <span class="token keyword">from</span> <span class="token string">'../Upvote.client.js'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">Post</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> slug <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// Reading the data directly from the database</span><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> content<span class="token punctuation">,</span> created_at<span class="token punctuation">,</span> title <span class="token punctuation">}</span> <span class="token operator">=</span> db<span class="token punctuation">.</span>posts<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>slug<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> html <span class="token operator">=</span> <span class="token function">sanitizeHtml</span><span class="token punctuation">(</span><span class="token function">marked</span><span class="token punctuation">(</span>content<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> formattedDate <span class="token operator">=</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token function">parseISO</span><span class="token punctuation">(</span>created_at<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">'dd/MM/yyyy'</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 tag"><span class="token tag"><span class="token punctuation"><</span>main</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>h1</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>h1</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><span class="token punctuation">></span></span><span class="token plain-text">Posted on </span><span class="token punctuation">{</span>formattedDate<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 plain-text"><br /> </span><span class="token punctuation">{</span>content<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">AddComment</span></span> <span class="token attr-name">slug</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>slug<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">Comments</span></span> <span class="token attr-name">slug</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>slug<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>main</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>Now you might argue that you could do this in traditional server-side rendering. For instance, Next.js allows you to access the server data directly in <code>getServerSideProps</code> and <code>getStaticProps</code>. And you won’t be wrong, but the difference is traditional SSR is an all-or-nothing approach and can only be done on a top-level page, but server components allow you to do that on a per-component basis.</p>
</li>
<li>
<p><strong>Automatic code-splitting</strong>: <a href="https://reactjs.org/docs/code-splitting.html">Code-splitting</a> is a concept that allows you to break your application into smaller chunks, sending less code to the client. The most common way you code split your app is per route. That’s also how frameworks like Next.js split the bundles for you by default.</p>
<p>Apart from automatic code-splitting, React also allows you to lazily load different modules at runtime with <code>React.lazy</code> API. Here is again a great example <a href="https://github.com/josephsavona/rfcs/blob/server-components/text/0000-server-components.md#automatic-code-splitting">from the RFC</a> on where this might be particularly useful:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// PhotoRenderer.js</span><br /><span class="token comment">// NOTE: *before* Server Components</span><br /><br /><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// one of these will start loading *when rendered on the client*:</span><br /><span class="token keyword">const</span> OldPhotoRenderer <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">lazy</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">import</span><span class="token punctuation">(</span><span class="token string">'./OldPhotoRenderer.js'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> NewPhotoRenderer <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">lazy</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">import</span><span class="token punctuation">(</span><span class="token string">'./NewPhotoRenderer.js'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">Photo</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">// Switch on feature flags, logged in/out, type of content, etc:</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>FeatureFlags<span class="token punctuation">.</span>useNewPhotoRenderer<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">NewPhotoRenderer</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 keyword">else</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">OldPhotoRenderer</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></code></pre>
<p>This technique does improve performance by dynamically importing only the component you need at runtime, but it does have some of its own caveats. For instance, this approach delays when the application can start loading the code, negating the benefit of loading less code in the first place.</p>
<p>As we saw earlier on how client components compose with the server components, they solve this problem by treating all client component imports as potential code split points and allowing developers to choose what they want to render much earlier on the server, which allows the client to download it earlier. Here is again the same <code>PhotoRenderer</code> example from the RFC with server components:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// PhotoRenderer.server.js - Server Component</span><br /><br /><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span><br /><br /><span class="token comment">// one of these will start loading *once rendered and streamed to the client*:</span><br /><span class="token keyword">import</span> OldPhotoRenderer <span class="token keyword">from</span> <span class="token string">'./OldPhotoRenderer.client.js'</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> NewPhotoRenderer <span class="token keyword">from</span> <span class="token string">'./NewPhotoRenderer.client.js'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">Photo</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">// Switch on feature flags, logged in/out, type of content, etc:</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>FeatureFlags<span class="token punctuation">.</span>useNewPhotoRenderer<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">NewPhotoRenderer</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 keyword">else</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">OldPhotoRenderer</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></code></pre>
</li>
<li>
<p><strong>Server components can be reloaded while preserving client state</strong>: We can refetch the server tree anytime from the client to get the updated state from the server without blowing up the local client state, focus, and even ongoing animations.</p>
<p>This is possible because the description of the UI received is data and not plain HTML, which allows React to merge data into existing components allowing the client state to not be blown away.</p>
<p>Although <a href="https://github.com/josephsavona/rfcs/blob/server-components/text/0000-server-components.md#update-refetch-sequence">based on the RFC as of now</a>, the entire subtree needs to be prefetched, given some starting server components and props.</p>
</li>
<li>
<p><strong>Server components integrate with Suspense out of the box:</strong> Server components are can be streamed progressively with <code><Suspense></code> as we saw in the previous section, which allows you to craft intentional loading states and quickly show important content while waiting for the remainder of the page to load.</p>
</li>
</ul>
<p>Now let’s check out how the app we have been looking at throughout this article would look with React Server Components. This time the Sidebar and the Post are server components, and the Navbar and the Comments section are client components. We have also wrapped the Post in Suspense as well.</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">Layout</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">NavBar</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">SidebarServerComponent</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">RightPane</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">Suspense</span></span> <span class="token attr-name">fallback</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</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 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">PostServerComponent</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">Suspense</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">Suspense</span></span> <span class="token attr-name">fallback</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</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 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">Comments</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">Suspense</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">RightPane</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">Layout</span></span><span class="token punctuation">></span></span></code></pre>
<p><video src="https://prateeksurana.me/videos/future-of-rendering-server-components-demo.webm" controls="" loop="">Looks like your browser doesn't support this video you can download the video <a href="https://prateeksurana.me/videos/future-of-rendering-server-components-demo.webm">here</a>.</video></p>
<p>The network graph for this would be very similar to the streaming rendering with Suspense one with way less JavaScript.</p>
<p>So server components are even a further step to fix the problems that we started with, and they not only help us download way less JavaScript but also significantly improve the developer experience.</p>
<p>The React team also mentioned in the <a href="https://github.com/josephsavona/rfcs/blob/server-components/text/0000-server-components.md#is-this-in-production-at-facebook">RFC FAQs</a> that they ran an experiment with a small number of users on a single page at Facebook and they have with encouraging results with ~30% product code size reduction.</p>
<h2>When can you start using these features?</h2>
<h3>Update</h3>
<p>React Server Components are stable now and can be used in production with the Next.js app directory with v13.4 and later. You can read the<a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components"> Next.js docs</a> for more info and also checkout my new blog where I <a href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/">compare building a Twitter Clone with Next.js and React Server Components and Remix</a>.</p>
<h3>Original answer</h3>
<p>Well, Not yet.</p>
<p>You can try some demos that I have linked in the next section, but at the time of writing this article, <a href="https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html">Server Components are still in alpha</a>, and Suspense for data fetching, which is needed for the Streaming SSR with new Suspense architecture, <a href="https://github.com/facebook/react/issues/13206">still isn’t official yet and will be released with a minor update of React 18</a>.</p>
<p>And the React team also mentioned that all this stuff would be pretty complex so, they expect the initial adoption would be through frameworks. So you should keep an eye out for the <a href="https://nextjs.org/blog/layouts-rfc">Next.js Layouts RFC</a>, which they are saying will be the biggest update to Next.js since its inception, and it will be built on top of the new React 18 features we discussed in this article.</p>
<h2>Demos</h2>
<p>You can check out the demos by the React team and the Next.js teams here:</p>
<ul>
<li><a href="https://codesandbox.io/s/kind-sammet-j56ro">Streaming SSR with the new Suspense Architecture demo</a></li>
<li><a href="https://github.com/reactjs/server-components-demo">Server Components Demo</a></li>
<li><a href="https://github.com/vercel/next-react-server-components">Next.js Server Components Demo</a></li>
</ul>
<h2>References</h2>
<p>Here are some of the references I used while writing this article:</p>
<ul>
<li>Most of the content for this article was inspired by the <a href="https://github.com/reactwg/react-18/discussions/37">New Suspense Architecture Discussion</a> and the <a href="https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html">Server Components RFC and Talk</a></li>
<li><a href="https://www.youtube.com/watch?v=PN1HgvAOmi8">Advanced Rendering Patterns: Lydia Hallie</a></li>
<li><a href="https://web.dev/rendering-on-the-web/">Rendering on the web</a></li>
</ul>
Fine-tuning refs with useImperativeHandleUnderstand what refs in React are, how you can use them to manipulate DOM nodes and how you can customize the exposed refs with useImperativeHandle2023-03-11T00:00:00Z2023-03-11T00:00:00Zhttps://prateeksurana.me/blog/fine-tuning-refs-with-useimperativehandle/<p>React is a powerful UI library that provides developers with a great developer experience (DX) through its declarative approach to building user interfaces. This approach abstracts away the complexity of manipulating DOM nodes, making it easier to build UIs. However, there are times when you need to access the DOM nodes managed by React, such as when you need to focus a node or measure its size. Unfortunately, there is no built-in way to do this with React.</p>
<p>This is where refs come in. The <code>useRef</code> API provided by React allows you to pass refs to a DOM node, which gives you access to that node. You can then perform actions or manipulate the node via the native JavaScript DOM API. Additionally, React provides the <code>useImperativeHandle</code> API, which lets you customize or expose only a subset of methods for that node.</p>
<p>So, in this article, we will be looking at how refs allow you to manipulate DOM nodes, how you can fine-tune them with <code>useImperativeHandle</code>, and some gotchas that you need to take care of when working with this hook.</p>
<p>But before we begin:</p>
<h2 class="relative">
<a id="what-are-refs-anyways" href="https://prateeksurana.me/blog/fine-tuning-refs-with-useimperativehandle/#what-are-refs-anyways" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
What are refs anyways?
</a>
</h2>
<p>If you’re familiar with refs in React, feel free to skip this section. But if you need a refresher or have no idea what they are, continue reading.</p>
<p>Refs are kind similar to a state in React in the sense that you want your component to remember a piece of information across re-renders. The most important thing that separates refs from the state is that, <strong>unlike state, they do not trigger a re-render when updated, and unlike state, they are also mutable.</strong></p>
<p>You can use the <a href="https://beta.reactjs.org/reference/react/useRef"><code>useRef</code></a> hook and pass it an initial value that you want to reference.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> <span class="token punctuation">{</span> useRef <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span><br /><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 keyword">const</span> ref <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 comment">// ^ { current: 0 }</span><br /><span class="token punctuation">}</span></code></pre>
<p>The hook returns an object with a <code>current</code> property holding the initial value. Unlike state, this value is meant to be mutable, enabling you to save, read, and modify it across renders. Hence, Refs provide an alternative to React's one-way data flow since updates on refs won't cause your component to re-render.</p>
<p>The most simple use case where you might need refs is when you want to store an interval ID for a <code>setTimeout()</code> or <code>setInterval()</code> so as to clear the interval when needed.</p>
<iframe src="https://codesandbox.io/embed/ref-demo-0q01ij?fontsize=14&hidenavigation=1&theme=dark&codemirror=1&hidedevtools=1&view=split&editorsize=65" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="ref-demo" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p>Keep in mind to <strong>not to use refs during rendering</strong> (using them to display data on screen or deriving some value from them that ends up affecting the data displayed to the user) because changing them doesn’t trigger a re-render and you might end up showing incorrect data.</p>
<p>Although the most common use of refs is getting access to DOM nodes. As we discussed at the beginning of this article React takes care of manipulating the DOM for you to match the render output of your components, but sometimes you need to do something that React can't, like focusing on an input or calculating the size of a node. In these cases, you can use refs to access the DOM node and use the JavaScript DOM APIs on it.</p>
<p>Here is the simplest example that demonstrates how you can use refs to get access to DOM nodes:</p>
<iframe src="https://codesandbox.io/embed/focus-input-with-ref-5cp5rn?fontsize=14&hidenavigation=1&theme=dark&codemirror=1&hidedevtools=1&view=split" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="ref-demo" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p>In the above example, when the button is clicked, the <code>inputRef</code> is used to access the input's DOM node, and we use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus"><code>focus()</code></a> instance method to focus the input.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>Conceptually you can think of passing a ref hook to React elements as functions that are called after a component is rendered and these functions get DOM node passed as the argument. So passing a ref from the <code>useRef</code> hook to a React element is just syntactic sugar for:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</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><span class="token punctuation">(</span><span class="token parameter">node</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> node<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><br /><span class="token punctuation">/></span></span></code></pre>
<p>Check out <a href="https://tkdodo.eu/blog/avoiding-use-effect-with-callback-refs">TKDodo’s blog</a> for more info about how this pattern can be useful you need to do something with DOM nodes on mount without relying on the <code>useRef</code> and <code>useEffect</code> hooks.</p>
</aside>
<p>Sometimes, the DOM node that you want to access may not be readily available inside your component but is located somewhere inside a React component. In that case, you can use the <a href="https://beta.reactjs.org/reference/react/forwardRef"><code>forwardRef</code></a> API to forward the DOM node to the relevant HTML element.</p>
<p>For instance in the above example if the input we wanted to use was a React component we can expose the ref from that component like this:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> <span class="token punctuation">{</span> useRef<span class="token punctuation">,</span> forwardRef <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Form</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> inputRef <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 /><br /> <span class="token keyword">function</span> <span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> inputRef<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">focus</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">return</span> <span class="token punctuation">(</span><br /> <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">MyInput</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>inputRef<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">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>handleClick<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> Focus the input<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 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><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">const</span> MyInput <span class="token operator">=</span> <span class="token function">forwardRef</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token function">MyInput</span><span class="token punctuation">(</span><span class="token parameter">props<span class="token punctuation">,</span> ref</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>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>ref<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>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>Manipulating DOM nodes with refs allows you to use them as an escape hatch, allowing you to do things like focus a node, scroll a node into view, etc., for which there is not a built-in way to do with React. But keep in mind to use them with a grain of salt, using them only for non-destructive actions like focusing, scrolling, etc., and avoid modifying DOM nodes directly managed by React because it can lead to inconsistencies and <a href="https://beta.reactjs.org/learn/manipulating-the-dom-with-refs#best-practices-for-dom-manipulation-with-refs">can even cause runtime errors</a>.</p>
</aside>
<h2 class="relative">
<a id="enter-useimperativehandle" href="https://prateeksurana.me/blog/fine-tuning-refs-with-useimperativehandle/#enter-useimperativehandle" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Enter useImperativeHandle
</a>
</h2>
<p>In the previous section, we learned about refs, how to use them to persist values across renders, and how to access DOM nodes using refs. React also provides another hook called <a href="https://beta.reactjs.org/reference/react/useImperativeHandle"><code>useImperativeHandle</code></a> that allows you to customize the ref handle exposed by your React components.</p>
<p>But why would you want to do that in the first place? Well, let’s take an example to understand the need for this hook.</p>
<p>The most common use case would be if you’re building a component for a library and you only want to expose a subset of the available DOM methods to the consumers of your component.</p>
<p>For example, you are building an Input component, and you allow the parent to pass the ref to the underlying DOM element, but you only want to expose the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select"><code>select</code></a> method on the input.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> <span class="token punctuation">{</span> useRef<span class="token punctuation">,</span> useImperativeHandle<span class="token punctuation">,</span> forwardRef <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">const</span> CustomInput <span class="token operator">=</span> <span class="token function">forwardRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">props<span class="token punctuation">,</span> ref</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> inputRef <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 /><br /> <span class="token function">useImperativeHandle</span><span class="token punctuation">(</span>ref<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 function-variable function">select</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 /> inputRef<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">select</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><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 tag"><span class="token tag"><span class="token punctuation"><</span>input</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>inputRef<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><span class="token punctuation">;</span><br /><br /><span class="token comment">// Some other component that uses our CustomInput component</span><br /><span class="token keyword">const</span> <span class="token function-variable function">App</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> inputRef <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 /><br /> <span class="token keyword">const</span> <span class="token function-variable function">focusInput</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 /> inputRef<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// ^ This will throw an error ❌</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">const</span> <span class="token function-variable function">selectText</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 /> inputRef<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">select</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 /><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><span class="token class-name">CustomInput</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>inputRef<span class="token punctuation">}</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>refs are awesome<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">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>focusInput<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">Focus Input</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>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>selectText<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">Select text in input</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>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><br /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> App<span class="token punctuation">;</span></code></pre>
<p>Try it out yourself <a href="https://codesandbox.io/s/refs-with-input-ufc73c?file=/src/App.js">on this sandbox</a></p>
<p>The <code>useImperativeHandle</code> hook takes in two parameters: the forwarded ref passed from the parent and a function that takes no arguments and returns the ref handle you want to expose (usually, it would be an object with the methods you want to expose). It also takes in an optional third argument which is a dependency array that lists all the reactive values that your second <code>createHandle</code> argument might use, causing it to re-execute whenever any dependency changes and assign the newly created handle to the ref.</p>
<p>Also the object you expose with the <code>createHandle</code> argument doesn’t need to have a one-to-one mapping with the DOM elements methods. You can expose different methods that do specific tasks on the DOM node. Take this <a href="https://github.com/getsentry/sentry/blob/master/static/app/components/autoSelectText.tsx">AutoSelect component from Sentry</a> for instance:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">import</span> <span class="token punctuation">{</span> useImperativeHandle<span class="token punctuation">,</span> useRef<span class="token punctuation">,</span> forwardRef <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">function</span> <span class="token function">selectText</span><span class="token punctuation">(</span><span class="token parameter">node</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>node <span class="token keyword">instanceof</span> <span class="token class-name">HTMLInputElement</span> <span class="token operator">&&</span> node<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">"text"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> node<span class="token punctuation">.</span><span class="token function">select</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>node <span class="token keyword">instanceof</span> <span class="token class-name">Node</span> <span class="token operator">&&</span> window<span class="token punctuation">.</span>getSelection<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> range <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createRange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> range<span class="token punctuation">.</span><span class="token function">selectNode</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> selection <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">getSelection</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>selection<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> selection<span class="token punctuation">.</span><span class="token function">removeAllRanges</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> selection<span class="token punctuation">.</span><span class="token function">addRange</span><span class="token punctuation">(</span>range<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 /><br /><span class="token keyword">const</span> AutoSelectText <span class="token operator">=</span> <span class="token function">forwardRef</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> children<span class="token punctuation">,</span> className<span class="token punctuation">,</span> <span class="token operator">...</span>props <span class="token punctuation">}</span><span class="token punctuation">,</span> ref</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> element <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 /><br /> <span class="token keyword">function</span> <span class="token function">handleClick</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><span class="token operator">!</span>element<span class="token punctuation">.</span>current<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 punctuation">}</span><br /> <span class="token function">selectText</span><span class="token punctuation">(</span>element<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token function">useImperativeHandle</span><span class="token punctuation">(</span>ref<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 function-variable function">selectText</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">selectText</span><span class="token punctuation">(</span>element<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><br /><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 spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<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>handleClick<span class="token punctuation">}</span></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>select-text<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> <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>element<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>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><span class="token punctuation">;</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> AutoSelectText<span class="token punctuation">;</span><br /></code></pre>
<p>This component selects the text inside the component when it’s clicked. It also exposes a custom method <code>selectText</code> on the ref via <code>useImperativeHandle</code>, which allows the parent component to select the text inside the component.</p>
<p>Now the parent component can access the <code>selectText</code> method of the component by passing the ref to the component:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">ParentComponent</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> childRef <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 /><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><span class="token class-name">AutoSelectText</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>childRef<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">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> childRef<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">selectText</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 /> Select Text<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 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><br /></code></pre>
<p><a href="https://codesandbox.io/s/auto-select-text-hi1o7u">Checkout this sandbox</a> to try it out for yourself.</p>
<p>Since you can expose whatever you want from the custom handle, you can do all kinds of stuff that might involve multiple DOM refs or even refs that are passed to the child components. The <a href="https://beta.reactjs.org/reference/react/useImperativeHandle#exposing-your-own-imperative-methods">new React docs have the best example for this</a>, where the component exposes a <code>scrollAndFocusAddComment</code> method via an imperative handle that lets the parent component scroll the list of comments and focus on an input field.</p>
<h2 class="relative">
<a id="beyond-dom-nodes" href="https://prateeksurana.me/blog/fine-tuning-refs-with-useimperativehandle/#beyond-dom-nodes" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Beyond DOM nodes
</a>
</h2>
<p>The recommended usage for <code>useImperativeHandle</code> is to expose/customize the imperative methods available on child components' DOM nodes. But its technically not limited to that, and you can also modify the state of the child component from the parent component.</p>
<p>For instance, you have a carousel component that has previous and next buttons to navigate between different items. Imagine a scenario where you want to go to a different slide from the parent component but you want to keep the component uncontrolled? Well <code>useImperativeHandle</code> can help you out here:</p>
<iframe src="https://codesandbox.io/embed/carousel-with-useimperative-85g409?fontsize=14&hidenavigation=1&theme=dark&codemirror=1&hidedevtools=1&view=split&module=%2Fsrc%2FApp.jsx" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="ref-demo" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p>But keep in mind this is not a recommended behavior. You should ideally <strong>use refs only for imperative behaviors that can’t be achieved via props since it goes against React’s core fundamentals of one-way data flow and can also get bug prone and difficult to test</strong>.</p>
<p>Hence, in this case, it would have been better if the current slide was controlled by the parent instead and was passed to the Carousel component as a prop.</p>
<iframe src="https://codesandbox.io/embed/carousel-controlled-4ffew3?fontsize=14&hidenavigation=1&theme=dark&codemirror=1&hidedevtools=1&view=split&module=%2Fsrc%2FApp.jsx" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="ref-demo" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<h2 class="relative">
<a id="conclusion" href="https://prateeksurana.me/blog/fine-tuning-refs-with-useimperativehandle/#conclusion" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Conclusion
</a>
</h2>
<p>In conclusion, we saw what refs are, how you can access DOM nodes with them, and how the <code>useImperativeHandle</code> hook acts as another powerful tool that allows you to customize the ref handle exposed by your React components. With this hook, you can expose only a subset of methods for a given node or customize the methods you want to expose, making it easier for the consumer components by giving consumer components access to the imperative behaviours they need.</p>
<p>Lastly we also saw how with React being a declarative library, it’s not recommended to use refs for imperative behaviors that can be achieved via props, even though it's technically possible to do so with <code>useImperativeHandle</code>.</p>
Next.js 13 vs Remix: An In-depth case studyA detailed comparison of the features Next.js 13 and Remix Run by building a Twitter Clone app in both frameworks.2023-09-29T00:00:00Z2023-09-29T00:00:00Zhttps://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/<p>When it comes to building web applications, React has been at the forefront for a while now, and its adoption continues to grow. Among the most common approaches to building web applications with React, Next.js stands out as one of the most preferred options.</p>
<p>Next.js has also been in the limelight since last year when they released their biggest update to the framework ever with <a href="https://nextjs.org/blog/next-13">the app router</a>. This introduces a new routing architecture that uses nested layouts and is closely integrated with React Server Components and Suspense.</p>
<p>But Next.js wasn’t the first React framework to implement this layout-based routing. Almost a year before Next.js publicly launched the app router, another framework called Remix launched it with its <a href="https://twitter.com/remix_run/status/1462900248016130051">public v1</a>. Remix is built by the people behind <a href="https://reactrouter.com/en/main">React Router</a>, the most popular client-side router for React applications.</p>
<p>The idea behind Remix was simple, it is an edge-first full-stack framework that encouraged building websites with the standard web APIs like <code>Request</code>, <code>Response</code>, <code>FormData</code>, etc., and had features that allowed creating nested layouts that load data in parallel, handle race conditions for you, and make you build your websites in such a way that they work even before JavaScript has started to load. Their MO was that <a href="https://twitter.com/remix_run/status/1584617827038035968">you get better at web fundamentals as you get better at Remix</a>.</p>
<p>I really admired the philosophy behind Remix and was really excited about the direction Next.js was moving forward with React Server Components. So I thought what better way would be to learn about both of them than to build a complete full-stack app. Therefore, I created one of my favorite websites X (formerly Twitter), incorporating the majority of the core functionalities from both frameworks. This blog post focuses on the lessons I learned, the aspects that one framework should adopt from the other, and my personal experiences and opinions while developing the app in both frameworks.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>TLDR; Both the Next.js and the Remix apps are deployed on Vercel, and you can test them out on these URLs respectively <a href="https://twitter-rsc.vercel.app/">https://twitter-rsc.vercel.app/</a> and <a href="https://twitter-remix-run.vercel.app/">https://twitter-remix-run.vercel.app/</a>.</p>
<p>Apart from the frameworks, the tech stack I used in both apps was:</p>
<ul>
<li><a href="https://tailwindcss.com/">Tailwind CSS</a> for styling</li>
<li><a href="https://turbo.build/repo">Turborepo</a> for managing the monorepo</li>
<li><a href="https://www.prisma.io/">Prisma ORM</a> for handling the database</li>
</ul>
<p>You can also find the code for both of them in <a href="https://github.com/prateek3255/twitter-clone">this monorepo on GitHub</a>.</p>
</aside>
<p>We will be comparing them in different sections, which include Layouts, Data Fetching, Streaming, Data Mutations, Infinite Loading, and some other features.</p>
<h2 class="relative">
<a id="layout" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#layout" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Layout
</a>
</h2>
<p>When it comes to layout, I like how both frameworks went pretty much the same route by allowing you to create shared nested layouts that persist between navigations.</p>
<p>The majority of web apps we build today, in one way or another, have a layout that is shared by multiple URLs grouped together. Whether it be a sidebar in a documentation or tabs in an analytics dashboard, shared layouts are everywhere, and Twitter Clone was not any different. In fact, a few of the pages had a situation where one layout was nested in another. The user profile page had a sidebar that persists across almost all routes and also had tabs where each tab had its own URL.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/twitter-clone-user-profile-page-480.webp 480w, https://prateeksurana.me/img/twitter-clone-user-profile-page-768.webp 768w, https://prateeksurana.me/img/twitter-clone-user-profile-page-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/twitter-clone-user-profile-page-480.jpeg 480w, https://prateeksurana.me/img/twitter-clone-user-profile-page-768.jpeg 768w, https://prateeksurana.me/img/twitter-clone-user-profile-page-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/twitter-clone-user-profile-page-768.jpeg" width="768" height="404" alt="Twitter Clone User Profile Page" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Now if you have built layouts like this with Next.js 12 or earlier, you know how complicated and messy they used to be where you had to <a href="https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts#per-page-layouts">create functions on your components</a> and wrap them in those functions in <code>_app.tsx</code>. This got further complicated if the layout required some data that was supposed to be fetched on the server. You had to duplicate the data fetching logic required by the layout in all your page’s <code>getServerSideProps</code> that shared those layouts.</p>
<p>But now, with both Remix and Next.js 13, you can rely on the framework's file-system based router that handles creating layouts for you.</p>
<h3 class="relative">
<a id="remix-layout" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#remix-layout" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Remix
</a>
</h3>
<p>With Remix, in their new v2 version, you can use the dot delimiter to create a slash (<code>/</code>) in the URL. For example, a file called <code>app/routes/invoice.new.tsx</code> would be matched to the route <code>/invoice/new</code>, and a route named <code>app/routes/invoice/$id.tsx</code> would be matched to a route <code>/invoice/{id}</code>, where <code>id</code> represents the invoice ID.</p>
<p>If your invoice URLs share a common layout, you can create an <code>invoice.tsx</code> file that contains the layout. In this file, you can add the <a href="https://remix.run/docs/en/main/file-conventions/routes#nested-layouts-without-nested-urls"><code><Outlet /></code></a> component where the pages that share the layout are supposed to be, which will result in both the <code>/invoices/new</code> and <code>/invoices/{id}</code> pages sharing that layout.</p>
<p>There also might be some cases where you need a common layout but don’t have a shared URL structure. Remix has a solution for that as well if you create a route with the prefix <code>_</code>, that route is not included in the URL. These routes are called <a href="https://remix.run/docs/en/main/file-conventions/routes#nested-layouts-without-nested-urls">Pathless Routes</a>.</p>
<p>All of these features combined enable you to compose and create powerful nested layouts, such as the one for <a href="https://twitter-rsc.vercel.app/wolverine">user profile</a> in the Twitter Clone app.</p>
<p>Apart from the sidebar shared by almost all pages. The user profile page also required a separate layout because it included tabs for tweets, replies, and likes, which would have been separate pages and hence standalone URLs of their own. This is what the file structure for the Remix app looks like:</p>
<pre class="language-jsx"><code class="language-jsx">app<span class="token operator">/</span><br /> routes<span class="token operator">/</span><br /> _base<span class="token punctuation">.</span>tsx<br /> _base<span class="token punctuation">.</span>_index<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span><br /> _base<span class="token punctuation">.</span>$username<span class="token punctuation">.</span>tsx<br /> _base<span class="token punctuation">.</span>$username<span class="token punctuation">.</span>_index<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span><span class="token punctuation">{</span>username<span class="token punctuation">}</span><br /> _base<span class="token punctuation">.</span>$username<span class="token punctuation">.</span>replies<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span><span class="token punctuation">{</span>username<span class="token punctuation">}</span><span class="token operator">/</span>replies<br /> _base<span class="token punctuation">.</span>$username<span class="token punctuation">.</span>likes<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span><span class="token punctuation">{</span>username<span class="token punctuation">}</span><span class="token operator">/</span>likes<br /> _base<span class="token punctuation">.</span>status<span class="token punctuation">.</span>$id<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span>status<span class="token operator">/</span><span class="token punctuation">{</span>id<span class="token punctuation">}</span><br /> _auth<span class="token punctuation">.</span>tsx<br /> _auth<span class="token punctuation">.</span>signin<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span>signin<br /> _auth<span class="token punctuation">.</span>signup<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span>signup</code></pre>
<p>Here <code>_base.tsx</code> is the main layout which contains the sidebar shared by most pages. Then there’s the <code>_base.$username.tsx</code> layout, which is a nested layout within the base layout and contains the profile header and the tabs for tweets, replies, and likes. The <code>._index.tsx</code> represents <code>/</code> URL for the given layout.</p>
<p>The following is a representation of these routes work for the user profile page in the app:</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/remix-layout-for-user-profile-page-480.webp 480w, https://prateeksurana.me/img/remix-layout-for-user-profile-page-768.webp 768w, https://prateeksurana.me/img/remix-layout-for-user-profile-page-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/remix-layout-for-user-profile-page-480.jpeg 480w, https://prateeksurana.me/img/remix-layout-for-user-profile-page-768.jpeg 768w, https://prateeksurana.me/img/remix-layout-for-user-profile-page-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/remix-layout-for-user-profile-page-768.jpeg" width="768" height="727" alt="Remix Layout for User Profile Page" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>You can also check the <a href="https://github.com/prateek3255/twitter-clone/tree/main/apps/remix/app/routes">code for the routes on GitHub</a> and learn more about the route <a href="https://remix.run/docs/en/main/file-conventions/routes">file naming conventions in the Remix docs</a> and via this <a href="https://interactive-remix-routing-v2.netlify.app/">awesome visualization</a>.</p>
<h3 class="relative">
<a id="nextjs-layout" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#nextjs-layout" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Next.js
</a>
</h3>
<p>The layout system is pretty much similar in the app directory of Next.js 13 as well; the main difference is you use directories to represent URLs and files inside the directories, like <code>layout.tsx</code> for layouts and <code>page.tsx</code> to make that route publicly accessible and use React’s <code>children</code> prop in layouts to populate the child layouts or pages.</p>
<p>In fact, Next.js 13 goes even a step further, allowing you to create separate files that define the loading state with <code>loading.tsx</code> and error states with <code>error.tsx</code> for every route segment. We will discuss these in more detail in the upcoming sections.</p>
<p>Creating layouts that don’t share a common URL is also pretty similar to Remix, and the only difference is instead of creating a file starting with <code>_</code>, we create a directory with the folder name in parenthesis, like <code>(folderName)</code>. These directories are called <a href="https://nextjs.org/docs/app/building-your-application/routing/route-groups">route groups in Next.js</a>. Dynamic segments of the URL are created by wrapping the folder’s name in square brackets, like <code>[id]</code> or <code>[username]</code>.</p>
<p>This is what the file structure looks like for the Next.js 13 Twitter clone routes:</p>
<pre class="language-jsx"><code class="language-jsx">app<span class="token operator">/</span><br /> <span class="token punctuation">(</span>base<span class="token punctuation">)</span><span class="token operator">/</span><br /> <span class="token punctuation">[</span>username<span class="token punctuation">]</span><span class="token operator">/</span><br /> likes<span class="token operator">/</span><br /> page<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span><span class="token punctuation">{</span>username<span class="token punctuation">}</span><span class="token operator">/</span>likes<br /> replies<span class="token operator">/</span><br /> page<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span><span class="token punctuation">{</span>username<span class="token punctuation">}</span><span class="token operator">/</span>replies<br /> layout<span class="token punctuation">.</span>tsx<br /> page<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span><span class="token punctuation">{</span>username<span class="token punctuation">}</span><br /> status<span class="token operator">/</span><span class="token punctuation">[</span>id<span class="token punctuation">]</span><span class="token operator">/</span><br /> page<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span>status<span class="token operator">/</span><span class="token punctuation">{</span>id<span class="token punctuation">}</span><br /> layout<span class="token punctuation">.</span>tsx<br /> page<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span><br /> <span class="token punctuation">(</span>auth<span class="token punctuation">)</span><br /> signin<span class="token operator">/</span><br /> page<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span>signin<br /> signup<span class="token operator">/</span><br /> page<span class="token punctuation">.</span>tsx <span class="token operator">--</span><span class="token operator">></span> <span class="token operator">/</span>signup</code></pre>
<p>And the following image is how these routes render the user profile page:</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/nextjs-layout-for-user-profile-page-480.webp 480w, https://prateeksurana.me/img/nextjs-layout-for-user-profile-page-768.webp 768w, https://prateeksurana.me/img/nextjs-layout-for-user-profile-page-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/nextjs-layout-for-user-profile-page-480.jpeg 480w, https://prateeksurana.me/img/nextjs-layout-for-user-profile-page-768.jpeg 768w, https://prateeksurana.me/img/nextjs-layout-for-user-profile-page-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/nextjs-layout-for-user-profile-page-768.jpeg" width="768" height="727" alt="Next.js Layout for User Profile Page" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>You can also <a href="https://github.com/prateek3255/twitter-clone/tree/main/apps/nextjs/app">check out the code on GitHub</a> and read more about <a href="https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts">layouts and pages in the Next.js docs</a>.</p>
<!-- ### Bottom line -->
<h3 class="relative">
<a id="bottom-line-layout" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#bottom-line-layout" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Bottom line
</a>
</h3>
<p>Now if we compare the routing mechanisms of both these frameworks, I really like the Remix one, because of how intuitive it is, and you can tell what route the file/layout represents just by looking at it. Whereas with Next.js, you end up with a Spaghetti of <code>page.tsx</code> and <code>layout.tsx</code>, and you have to look through your directory structure to figure out what URL a given page would be rendered on.</p>
<p>But having said that, I also understand why Next.js did that because it's not just page and layouts that live in those directories but also other stuff like <code>notFound.tsx</code> , <code>loading.tsx</code> , <code>error.tsx</code> etc., which help you define your loading/error states for each of your route segments. Also another benefit is that you can colocate your components with your routes.</p>
<p>Either way, I love that both of the frameworks have chosen pretty much the same direction for file-system-based routing, and it feels like the right way to go.</p>
<!-- ## Data Fetching -->
<h2 class="relative">
<a id="data-fetching" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#data-fetching" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Data Fetching
</a>
</h2>
<p>Data fetching is a crucial part of modern web applications. At the beginning, most React apps used to be client side rendered where the server just sends an empty <code>index.html</code> file with the relevant JavaScript bundle in a <code><script /></code> tag. This resulted in blank pages initially while the browser downloads and executes the JavaScript, React initializes and starts fetching data for rendering your components. This would significantly impact performance on low-powered devices or devices with poor internet connections.</p>
<p>Next.js and Gatsby primarily changed that when they simplified fetching data on the server and/or at the build time for React applications allowing pre-rendering the initial HTML. So now the users have the initial UI ready as your website first loads. Although they still have to wait for the JavaScript to be downloaded and <a href="https://react.dev/reference/react-dom/client/hydrateRoot">React to be hydrated</a> before they can start interacting.</p>
<p>Now Next.js 13 and Remix both take this one step further. Next.js with React Server components and Remix with their loaders and parallel data fetching.</p>
<!-- ### Remix -->
<h3 class="relative">
<a id="remix-data-fetching" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#remix-data-fetching" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Remix
</a>
</h3>
<p>When it comes to Remix, the way you fetch data is in loaders where each route can define a <a href="https://remix.run/docs/en/main/route/loader"><code>loader</code></a> function that provides relevant data to the route when rendering. Loaders are only run on the server.</p>
<p>Here is an example of a loader from the Remix Twitter Clone, which is used in the <a href="https://github.com/prateek3255/twitter-clone/blob/e8e676379e4c5442f5db1a3dcc6bd865abe4a0a3/apps/remix/app/routes/_base.tsx#L19-L22"><code>_base.tsx</code> layout</a>:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">loader</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> request <span class="token punctuation">}</span><span class="token operator">:</span> LoaderFunctionArgs</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> currentLoggedInUser <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getCurrentLoggedInUser</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">user</span><span class="token operator">:</span> currentLoggedInUser <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">200</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 /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">RootLayout</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> user <span class="token punctuation">}</span> <span class="token operator">=</span> useLoaderData<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>typeof</span> <span class="token attr-name">loader</span><span class="token punctuation">></span></span><span class="token plain-text">();<br /> ....<br />}</span></code></pre>
<p>The loaders get the Fetch <a href="https://developer.mozilla.org/en-US/docs/Web/API/Request"><code>Request</code></a> object as an argument, which allows them to read things like <code>headers</code>, <code>cookies</code>, etc., and the return type of loaders is always the Fetch <a href="https://developer.mozilla.org/en-US/docs/Web/API/Response"><code>Response</code></a>. Remix provides some wrappers on top of the Response object, like <a href="https://remix.run/docs/en/main/utils/json"><code>json</code></a>, <a href="https://remix.run/docs/en/main/utils/redirect"><code>redirect</code></a>, etc., that let you return specific types of responses with relevant status codes. You can then use the loader data in your components with the <a href="https://remix.run/docs/en/main/hooks/use-loader-data"><code>useLoaderData</code></a> hook.</p>
<p>With Remix, since you can define loaders in each part of the route segment, including layouts, it is able to load the data for all the route segments in parallel instead of fetching data in a waterfall if fetching data on the browser. This is best illustrated with this illustration on <a href="https://remix.run/">their landing page</a>:</p>
<p><video src="https://prateeksurana.me/videos/remix-parallel-loaders-demo.webm" controls="" loop="">Looks like your browser doesn't support this video you can download the video <a href="https://prateeksurana.me/videos/remix-parallel-loaders-demo.webm">here</a>.</video></p>
<p>And this is how the Network graph looks for the Remix Twitter Clone as well:</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/remix-network-graph-480.webp 480w, https://prateeksurana.me/img/remix-network-graph-768.webp 768w, https://prateeksurana.me/img/remix-network-graph-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/remix-network-graph-480.jpeg 480w, https://prateeksurana.me/img/remix-network-graph-768.jpeg 768w, https://prateeksurana.me/img/remix-network-graph-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/remix-network-graph-768.jpeg" width="768" height="241" alt="Remix Parallel Loaders Network Graph for the User Profile page on the Twitter Clone app" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Secondly, loaders are not just used for rendering pages on the server. Since their response is just an HTTP Fetch Response, Remix also calls the loaders via <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API"><code>fetch</code></a> in the browser, on navigations, or for revalidations.</p>
<!-- ### Next.js -->
<h3 class="relative">
<a id="nextjs-data-fetching" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#nextjs-data-fetching" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Next.js
</a>
</h3>
<p>With the introduction of the app directory in Next.js 13, Next.js has pivoted away from defining the server-only data fetching logic that is present in <code>getServerSideProps</code> / <code>getStaticProps</code> in only the page files to <a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components">React Server components (RSCs)</a>.</p>
<p>RSCs are a broad topic, and there are a lot of other problems they solve apart from just data fetching on the server that deserves a separate blog of its own (I actually covered this in much more detail in my <a href="https://prateeksurana.me/blog/future-of-rendering-in-react/">Future of Rendering in React blog</a>).</p>
<p>In a nutshell, Server Components are a new paradigm in React. They are components that only render on the server and, unlike traditional server-side rendering in React, are never hydrated on the client. They have a lot of benefits which include:</p>
<ul>
<li><strong>Data fetching and security:</strong> Since Server components only ever run on the server, you can include server-only secrets and API calls directly in your React Server Components without worrying about ever exposing them to the client.</li>
<li><strong>Deterministic Bundle Sizes</strong>: The dependencies that would have earlier impacted the client's JavaScript bundle sizes would now never be downloaded to the client if they are only ever used inside a Server Component. (A good example of this would be a markdown parser whose JavaScript earlier would have needed to be downloaded to hydrate the page)</li>
</ul>
<p><a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components">and many more</a>.</p>
<p>For the parts that need interactivity, you need to create <a href="https://nextjs.org/docs/app/building-your-application/rendering/client-components">Client Components</a>. Unlike their name, Client Components are rendered on the server as well, but they follow the usual Server Side rendering pipeline where they have to be hydrated by downloading and executing the relevant JavaScript for them on the client side.</p>
<p>Server Components are not a silver bullet and have some limitations, which include:</p>
<ul>
<li>Since they only run on the server and are never hydrated, they cannot contain any interactive pieces of UI, so things like <code>useState</code>, <code>useEffect</code>, event handlers, and browser-only APIs won’t work. Instead, you are now supposed to use Client Components wherever you need interactivity in your component tree.</li>
<li>You cannot import and use a Server Component inside a Client Component. Since Server Components can only be rendered on the server, we need to know all the Server Components in the tree on the server itself. Although there are ways you can <a href="https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#interleaving-server-and-client-components">interleave them together</a>.</li>
</ul>
<p>Inside the app directory, all components by default are Server Components, and if you want to add interactivity, you need to add client components inside the component tree. Client Components are created by adding the <code>'use client'</code> directive at the top of the file. Also, Server and Client Components can never be in the same file.</p>
<p>Again here is an example of the same <a href="https://github.com/prateek3255/twitter-clone/blob/main/apps/nextjs/app/(base)/layout.tsx">base layout</a> that we saw in Remix as a Server Component in Next.js:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">BaseLayout</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span><br /> children<span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">children</span><span class="token operator">:</span> React<span class="token punctuation">.</span>ReactNode<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getCurrentLoggedInUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> isLoggedIn <span class="token operator">=</span> <span class="token operator">!</span><span class="token operator">!</span>user<span class="token punctuation">;</span><br /><br /> <span class="token keyword">return</span> <span class="token punctuation">(</span><br /> <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>Since they are rendered on the server, Server Components can return Promises. This allows you just to await data inside your components and then use that data while rendering.</p>
<p>Next.js also provides you with helpers like <a href="https://nextjs.org/docs/app/api-reference/functions/headers"><code>headers</code></a>, <a href="https://nextjs.org/docs/app/api-reference/functions/cookies"><code>cookies</code></a>, <a href="https://nextjs.org/docs/app/api-reference/functions/redirect"><code>redirect</code></a>, <a href="https://nextjs.org/docs/app/api-reference/functions/revalidatePath"><code>revalidatePath</code></a>, etc., that allow you to access request data on the server side and take server-only actions. The <code>getCurrentLoggedInUser</code> method here actually uses the cookies to fetch the details of the currently logged-in user from the database.</p>
<p>This is a really game-changing feature where possibilities are limitless because now, you can not only read data from your database in a declarative way directly inside your React components but also do it at any level of the component tree instead of just route segments as long as you are doing it inside a Server Component.</p>
<p>The recommended way to compose your applications in Next.js 13 is by keeping the Client Components at the leaves of your component tree, only wherever you need interactivity, state, or browser-only APIs. Here is how the composition is distributed for the user profile page in the Next.js Twitter Clone:</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/nextjs-server-and-client-component-480.webp 480w, https://prateeksurana.me/img/nextjs-server-and-client-component-768.webp 768w, https://prateeksurana.me/img/nextjs-server-and-client-component-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/nextjs-server-and-client-component-480.jpeg 480w, https://prateeksurana.me/img/nextjs-server-and-client-component-768.jpeg 768w, https://prateeksurana.me/img/nextjs-server-and-client-component-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/nextjs-server-and-client-component-768.jpeg" width="768" height="727" alt="Next.js Server and Client Components Composition for the User Profile page" class="article-img" loading="lazy" decoding="async" />
</picture>
<p><a href="https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns">Next.js rendering docs</a> also has this table that can help you decide when to use Client or Server Components.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/nextjs-server-and-client-components-comparision-table-480.webp 480w, https://prateeksurana.me/img/nextjs-server-and-client-components-comparision-table-768.webp 768w, https://prateeksurana.me/img/nextjs-server-and-client-components-comparision-table-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/nextjs-server-and-client-components-comparision-table-480.jpeg 480w, https://prateeksurana.me/img/nextjs-server-and-client-components-comparision-table-768.jpeg 768w, https://prateeksurana.me/img/nextjs-server-and-client-components-comparision-table-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/nextjs-server-and-client-components-comparision-table-768.jpeg" width="768" height="640" alt="Next.js Server and Client Components Composition Comparison table" class="article-img" loading="lazy" decoding="async" />
</picture>
<!-- ### Bottom line -->
<h3 class="relative">
<a id="bottom-line-data-fetching" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#bottom-line-data-fetching" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Bottom line
</a>
</h3>
<p>Although I appreciate the way Remix has built a powerful API using loaders, which allows for parallel data fetching for child routes and easy revalidations, and I also like the fact that loaders always return a Fetch Response, but React Server Components still feel like the right to go.</p>
<p>In addition to other benefits, such as deterministic bundle size, React Server Components also provide a great developer experience (DX). They allow you to compose your component tree in a way that fetches data exactly where it belongs, rather than only in route segments.</p>
<p>Remix has also acknowledged the benefits RSCs bring to the table, and they, too, plan to <a href="https://twitter.com/remix_run/status/1661017634480201734">integrate React Server Components in the future</a>.</p>
<p>One another caveat with loaders is that you define your loaders in the same file that your components go to. While the compiler does a good job in segregating between client and server bundles, but can still lead to accidental exposure of server-only secret or shipping server-only bundles to the client, Remix also has a whole doc with the <a href="https://remix.run/docs/en/main/guides/constraints">gotchas that you need to be aware of when importing modules in your Route segments</a>. And yes, Next.js prior to the app directory also suffers from the same caveats with <code>getServerSideProps</code>.</p>
<p>Lastly, Server Components are also suffer from some problems of their own. By default, if you just fetch data in your Server Components, data will be fetched sequentially in a waterfall along the Server Component tree. Although there are ways to parallelize it, <a href="https://nextjs.org/docs/app/building-your-application/data-fetching/patterns#parallel-data-fetching">the solutions are far from perfect</a>.</p>
<!-- ## Streaming -->
<h2 class="relative">
<a id="streaming" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#streaming" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Streaming
</a>
</h2>
<p>With React 18, you can use Streaming and Suspense, which allows you to progressively render and incrementally stream rendered units of the UI to the client.</p>
<p>Streaming enables you to display loading states for layout parts and route segments that have blocking data requirements. Rather than delaying the page load until all data is ready on the server, the server can initially return a loading state for the dependent part and later replace it with the actual data once it is fetched from the server. This illustration from the <a href="https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming">Next.js streaming docs</a> explains it really well:</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/streaming-with-suspense-480.webp 480w, https://prateeksurana.me/img/streaming-with-suspense-768.webp 768w, https://prateeksurana.me/img/streaming-with-suspense-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/streaming-with-suspense-480.jpeg 480w, https://prateeksurana.me/img/streaming-with-suspense-768.jpeg 768w, https://prateeksurana.me/img/streaming-with-suspense-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/streaming-with-suspense-768.jpeg" width="768" height="331" alt="Streaming with Suspense" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Both Remix and Next.js 13 have good support for Streaming with Suspense.</p>
<!-- ### Remix -->
<h3 class="relative">
<a id="remix-streaming" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#remix-streaming" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Remix
</a>
</h3>
<p>With Remix, you can simply use the <a href="https://remix.run/docs/en/main/utils/defer"><code>defer</code></a> wrapper and return promises instead of resolved values for the items you want to stream from your loaders. Then in the component, you can use the <a href="https://remix.run/docs/en/main/components/await"><code>Await</code></a> component to handle deferred loader promises and wrap it in a Suspense boundary to display a loading indicator until the promise is resolved.</p>
<p>Here is a simplified version of how I used it in the Twitter Clone app, where I <a href="https://github.com/prateek3255/twitter-clone/blob/main/apps/remix/app/routes/_base.%24username._index.tsx">stream the first page of the infinite tweets from the server</a>:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">loader</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> request<span class="token punctuation">,</span> params <span class="token punctuation">}</span><span class="token operator">:</span> LoaderFunctionArgs</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> username <span class="token operator">=</span> params<span class="token punctuation">.</span>username <span class="token keyword">as</span> string<span class="token punctuation">;</span><br /><br /> <span class="token keyword">return</span> <span class="token function">defer</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">tweets</span><span class="token operator">:</span> <span class="token function">getTweetsByUsername</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> username<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">currentLoggedInUser</span><span class="token operator">:</span> <span class="token keyword">await</span> <span class="token function">getCurrentLoggedInUser</span><span class="token punctuation">(</span>request<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><br /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function-variable function">UserTweets</span> <span class="token operator">=</span> <span class="token punctuation">(</span><br /> <span class="token parameter"><span class="token literal-property property">props</span><span class="token operator">:</span> SuspendedInfiniteTweetsProps</span><br /><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> data <span class="token operator">=</span> useLoaderData<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>typeof</span> <span class="token attr-name">loader</span><span class="token punctuation">></span></span><span class="token plain-text">();<br /> console.log(currentLoggedInUser.name)<br /><br /> return (<br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">Suspense</span></span> <span class="token attr-name">fallback</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</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 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">Await</span></span><br /> <span class="token attr-name">resolve</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>props<span class="token punctuation">.</span>tweets<span class="token punctuation">}</span></span><br /> <span class="token attr-name">errorElement</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span><span class="token plain-text">Something went wrong!</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></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><span class="token punctuation">(</span><span class="token parameter">initialTweets</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><br /> <span class="token punctuation">{</span><span class="token comment">/* Render the Tweets */</span><span class="token punctuation">}</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><span class="token class-name">Await</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">Suspense</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> );<br />}</span></code></pre>
<p>In the above example, also notice how the <code>getCurrentLoggedInUser</code> is awaited, so it would not be streamed, and you would be directly able to consume it like we were able to with normal loader responses.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>If you’re curious how streaming actually looks like in the network tab, check out the video of an artificially slowed streaming response:</p>
<p><video src="https://prateeksurana.me/videos/remix-streaming-demo.webm" controls="" loop="">Looks like your browser doesn't support this video you can download the video <a href="https://prateeksurana.me/videos/remix-streaming-demo.webm">here</a>.</video></p>
<p>As you can see, as soon as the page is loaded, you see a Spinner for the deferred initial tweets of the user, but as soon as they are loaded, they are replaced by the actual tweets from the server itself.</p>
<p>In the request timing breakdown, we see that the time to first byte (represented by the green line) happens in a second, where the user would see the page loaded with the spinner for the first page of tweets. The time taken by content download to finish (the blue line) is about 2 seconds and it is the time when the streamed data is loaded on the server and is updated in the final HTML output.</p>
<p>Without streaming the request would have taken 3 seconds to finish showing the user a blank page for that entire duration.</p>
</aside>
<!-- ### Next.js -->
<h3 class="relative">
<a id="nextjs-streaming" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#nextjs-streaming" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Next.js
</a>
</h3>
<p>Streaming is even more straightforward with Next.js 13. As we discussed earlier in the layouts section, you can directly create a <code>loading.tsx</code> in your route segment directories to get instant loading states for the route segments underneath that directory.</p>
<p>Under the hood, Next.js wraps the page in the Route Segment within a Suspense boundary with the fallback you specified in <code>loading.tsx</code>. This is best illustrated in the following image from the <a href="https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming#instant-loading-states">Next.js docs</a>.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/nextjs-streaming-with-suspense-480.webp 480w, https://prateeksurana.me/img/nextjs-streaming-with-suspense-768.webp 768w, https://prateeksurana.me/img/nextjs-streaming-with-suspense-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/nextjs-streaming-with-suspense-480.jpeg 480w, https://prateeksurana.me/img/nextjs-streaming-with-suspense-768.jpeg 768w, https://prateeksurana.me/img/nextjs-streaming-with-suspense-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/nextjs-streaming-with-suspense-768.jpeg" width="768" height="367" alt="Next.js Streaming with Suspense" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Apart from this, if you want to Suspend something that is not a route segment, you can still do it by wrapping <code>Suspense</code> over an async component that has some data fetching requirements.</p>
<p>For instance, a good use for it in the Twitter Clone app <a href="https://github.com/prateek3255/twitter-clone/blob/main/apps/nextjs/app/(base)/page.tsx">was on the home page</a>, where I wanted to suspend the initial tweets, but I didn’t want the user to wait for the “Create a Tweet” CTA in the header to be blocked because of it.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">Home</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getCurrentLoggedInUser</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><br /> <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><span class="token comment">/** Header stuff */</span><span class="token punctuation">}</span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span>user <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">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>hidden sm:flex p-4 border-b border-solid border-gray-700<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">Image</span></span><br /> <span class="token attr-name">src</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>profileImage <span class="token operator">??</span> <span class="token constant">DEFAULT_PROFILE_IMAGE</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>rounded-full object-contain max-h-[48px]<span class="token punctuation">"</span></span><br /> <span class="token attr-name">width</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token number">48</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">height</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token number">48</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">alt</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</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>user<span class="token punctuation">.</span>username<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">'s avatar</span><span class="token template-punctuation string">`</span></span><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">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>flex-1 ml-3 mt-2<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">CreateTweetHomePage</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 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><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">Suspense</span></span> <span class="token attr-name">fallback</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</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 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">HomeTweets</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">Suspense</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><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">HomeTweets</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> initialTweets <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getHomeTweets</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><br /> <span class="token comment">/** Render initial infinite Tweets */</span><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<!-- ### Bottom line -->
<h3 class="relative">
<a id="bottom-line-streaming" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#bottom-line-streaming" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Bottom line
</a>
</h3>
<p>Streaming with Suspense is a great feature in React that offers a great UX for the users by significantly reducing the <a href="https://web.dev/ttfb/">time to first byte</a> and showing an instant loading state while still keeping all the data fetching on the server.</p>
<p>Also, I really like how straightforward it is with React Server Components, where you can just wrap the component with blocking data fetching requirements in a Suspense boundary.</p>
<!-- ## Data mutations -->
<h2 class="relative">
<a id="data-mutations" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#data-mutations" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Data mutations
</a>
</h2>
<p>When it comes to mutations, we probably are all used to handling it ourselves by making API requests to a backend server and then updating the local state to reflect the changes, or even using libraries like React Query that help with handling most of the stuff for you. Both of the frameworks want to change that by making actions part of their core features.</p>
<p>In Remix, these are taken care of by actions, and at the time of writing this blog, <a href="https://nextjs.org/blog/next-13-4">Next.js also added Server actions with 13.4</a>, but they are still in alpha.</p>
<!-- ### Remix -->
<h3 class="relative">
<a id="remix-data-mutations" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#remix-data-mutations" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Remix
</a>
</h3>
<p>In Remix, mutations are handled by actions, and they are one of the core features of Remix. Actions are defined in the route file by exporting a function called <a href="https://remix.run/docs/en/main/route/action"><code>action</code></a>. Similar to a loader, an action is also a server-only function from which you return a Fetch Response, but unlike a loader, it handles non-<code>GET</code> requests for the route (<code>POST</code>, <code>PUT</code> , <code>PATCH</code>, <code>DELETE</code>).</p>
<p>The primary way you interact with actions in remix is via HTML forms. Remember earlier when I mentioned that as you get better at the web, you get better at Remix? Well, this is the part where it is most evident. Remix encourages you to keep every part of your application where the user takes an action to be an HTML form. Yes, even the like button is a form.</p>
<p>Whenever the user triggers a form submission, it calls the action on the closest route in the context (you can modify that by specifying the URL you want to post your form to with the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attributes_for_form_submission"><code>action</code> attribute of forms</a>). Once the action is executed, Remix refetches all the loaders for that route via the browser fetch request and refreshes your UI, which ensures that your UI always stays in sync with your database. This is called the <a href="https://remix.run/docs/en/main/discussion/data-flow">“full-stack data flow” of Remix</a>.</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/remix-full-stack-data-flow-480.webp 480w, https://prateeksurana.me/img/remix-full-stack-data-flow-768.webp 768w, https://prateeksurana.me/img/remix-full-stack-data-flow-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/remix-full-stack-data-flow-480.jpeg 480w, https://prateeksurana.me/img/remix-full-stack-data-flow-768.jpeg 768w, https://prateeksurana.me/img/remix-full-stack-data-flow-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/remix-full-stack-data-flow-768.jpeg" width="768" height="531" alt="Remix Full Stack Data Flow" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>Let’s see how it works with some examples from the Twitter Clone. This is what the code for the Sign in page looks like</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">action</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> request <span class="token punctuation">}</span><span class="token operator">:</span> ActionFunctionArgs</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> form <span class="token operator">=</span> <span class="token keyword">await</span> request<span class="token punctuation">.</span><span class="token function">formData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> usernameOrEmail <span class="token operator">=</span> form<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"usernameOrEmail"</span><span class="token punctuation">)</span><span class="token operator">?.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token string">""</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> password <span class="token operator">=</span> form<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"password"</span><span class="token punctuation">)</span><span class="token operator">?.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token string">""</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">const</span> isUsername <span class="token operator">=</span> <span class="token operator">!</span><span class="token function">isEmail</span><span class="token punctuation">(</span>usernameOrEmail<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Find an account</span><br /> <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> prisma<span class="token punctuation">.</span>user<span class="token punctuation">.</span><span class="token function">findFirst</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">where</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">[</span>isUsername <span class="token operator">?</span> <span class="token string">"username"</span> <span class="token operator">:</span> <span class="token string">"email"</span><span class="token punctuation">]</span><span class="token operator">:</span> usernameOrEmail<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 /><br /> <span class="token keyword">const</span> fields <span class="token operator">=</span> <span class="token punctuation">{</span><br /> usernameOrEmail<span class="token punctuation">,</span><br /> password<span class="token punctuation">,</span><br /> <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>user<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> fields<span class="token punctuation">,</span><br /> <span class="token literal-property property">fieldErrors</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">usernameOrEmail</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">No account found with the given </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><br /> isUsername <span class="token operator">?</span> <span class="token string">"username"</span> <span class="token operator">:</span> <span class="token string">"email"</span><br /> <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 literal-property property">password</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><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">400</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> isPasswordCorrect <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">comparePassword</span><span class="token punctuation">(</span>password<span class="token punctuation">,</span> user<span class="token punctuation">.</span>passwordHash<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>isPasswordCorrect<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> fields<span class="token punctuation">,</span><br /> <span class="token literal-property property">fieldErrors</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">usernameOrEmail</span><span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">password</span><span class="token operator">:</span> <span class="token string">"Incorrect password"</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><br /> <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">400</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token keyword">return</span> <span class="token function">createUserSession</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span>id<span class="token punctuation">,</span> <span class="token string">"/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Signin</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> actionData <span class="token operator">=</span> useActionData<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>typeof</span> <span class="token attr-name">action</span><span class="token punctuation">></span></span><span class="token plain-text">();<br /> const navigation = useNavigation();<br /> return (<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 plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h1</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>font-bold text-3xl text-white mb-7<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">Sign in to Twitter</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</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 attr-name">method</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>post<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>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>flex flex-col gap-4 mb-8<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">FloatingInput</span></span><br /> <span class="token attr-name">autoFocus</span><br /> <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Username or Email<span class="token punctuation">"</span></span><br /> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>usernameOrEmail<span class="token punctuation">"</span></span><br /> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>usernameOrEmail<span class="token punctuation">"</span></span><br /> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>john@doe.com<span class="token punctuation">"</span></span><br /> <span class="token attr-name">defaultValue</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>actionData<span class="token operator">?.</span>fields<span class="token operator">?.</span>usernameOrEmail <span class="token operator">??</span> <span class="token string">""</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">error</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>actionData<span class="token operator">?.</span>fieldErrors<span class="token operator">?.</span>usernameOrEmail <span class="token operator">??</span> <span class="token keyword">undefined</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">aria-invalid</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token function">Boolean</span><span class="token punctuation">(</span>actionData<span class="token operator">?.</span>fieldErrors<span class="token operator">?.</span>usernameOrEmail<span class="token punctuation">)</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">aria-errormessage</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>actionData<span class="token operator">?.</span>fieldErrors<span class="token operator">?.</span>usernameOrEmail <span class="token operator">??</span> <span class="token keyword">undefined</span><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><span class="token class-name">FloatingInput</span></span><br /> <span class="token attr-name">required</span><br /> <span class="token attr-name">label</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><br /> <span class="token attr-name">id</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><br /> <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><br /> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>********<span class="token punctuation">"</span></span><br /> <span class="token attr-name">type</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><br /> <span class="token attr-name">defaultValue</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>actionData<span class="token operator">?.</span>fields<span class="token operator">?.</span>password <span class="token operator">??</span> <span class="token string">""</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">error</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>actionData<span class="token operator">?.</span>fieldErrors<span class="token operator">?.</span>password <span class="token operator">??</span> <span class="token keyword">undefined</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">aria-invalid</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token function">Boolean</span><span class="token punctuation">(</span>actionData<span class="token operator">?.</span>fieldErrors<span class="token operator">?.</span>password<span class="token punctuation">)</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">aria-errormessage</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>actionData<span class="token operator">?.</span>fieldErrors<span class="token operator">?.</span>password <span class="token operator">??</span> <span class="token keyword">undefined</span><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 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">ButtonOrLink</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 attr-name">size</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>large<span class="token punctuation">"</span></span> <span class="token attr-name">stretch</span> <span class="token attr-name">disabled</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>navigation<span class="token punctuation">.</span>state <span class="token operator">===</span> <span class="token string">"submitting"</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"><br /> Sign In<br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">ButtonOrLink</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><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 plain-text"><br /> );<br />}</span></code></pre>
<p>For form submissions that require a URL change, Remix provides a <a href="https://remix.run/docs/en/main/components/form"><code>Form</code></a> component which is a progressively enhanced wrapper over the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form">native HTML <code>form</code> element</a>. You can then use the <a href="https://remix.run/docs/en/main/hooks/use-navigation"><code>useNavigation</code></a> hook that provides you with information about a pending page navigation which you can use to give feedback to users about loading states. For the Sign in page, we are using it to disable the button while the form is <code>submitting.</code></p>
<p>Similar to <code>useLoaderData</code> we saw in loaders, Remix also provides <a href="https://remix.run/docs/en/main/hooks/use-action-data"><code>useActionData</code></a>, which acts as a bridge between server and client, providing feedback for notifying the users of any submission errors.</p>
<p>Also, notice we are not using any state for managing the inputs. Instead, we rely on the browser’s default behavior to serialize all the form fields in the body and “POST” it to the server when the form is submitted. In the <code>action</code>, we can then read the <code>formData</code> via the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Request/formData">Fetch Request’s <code>formData</code> method</a>.</p>
<p>But we don’t want to do navigations with form submissions every time. Hence Remix also offers another utility for interacting with forms without causing navigation called <a href="https://remix.run/docs/en/main/hooks/use-fetcher"><code>fetcher</code></a>. Almost all the remaining forms in the Twitter Clone are fetcher forms. Let’s take <a href="https://github.com/prateek3255/twitter-clone/blob/main/apps/remix/app/routes/_base.status.%24id.tsx">liking a tweet on the status page</a> as an example:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">TweetStatus</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> tweet<span class="token punctuation">,</span> user<span class="token punctuation">,</span> replies <span class="token punctuation">}</span> <span class="token operator">=</span> useLoaderData<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>typeof</span> <span class="token attr-name">loader</span><span class="token punctuation">></span></span><span class="token plain-text">();<br /> const fetcher = useFetcher();<br /> const isLoading = fetcher.state !== "idle";<br /><br /> return (<br /> </span><span class="token punctuation">{</span><span class="token comment">/** Rest of UI on page ,omitted for brevity **/</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>fetcher.Form</span> <span class="token attr-name">method</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>post<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> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>hidden<span class="token punctuation">"</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>tweetId<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>originalTweetId<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">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>hidden<span class="token punctuation">"</span></span><br /> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>hasLiked<span class="token punctuation">"</span></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><span class="token punctuation">(</span><span class="token operator">!</span>tweet<span class="token punctuation">.</span>hasLiked<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</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 plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">TweetAction</span></span><br /> <span class="token attr-name">size</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>normal<span class="token punctuation">"</span></span><br /> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>like<span class="token punctuation">"</span></span><br /> <span class="token attr-name">active</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>tweet<span class="token punctuation">.</span>hasLiked<span class="token punctuation">}</span></span><br /> <span class="token attr-name">disabled</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>isLoading<span class="token punctuation">}</span></span><br /> <span class="token attr-name">submit</span><br /> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>_action<span class="token punctuation">"</span></span><br /> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>toggle_tweet_like<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>fetcher.Form</span><span class="token punctuation">></span></span><span class="token plain-text"><br /> </span><span class="token punctuation">{</span><span class="token comment">/** Rest of UI on page ,omitted for brevity **/</span><span class="token punctuation">}</span><span class="token plain-text"><br /> );<br />}<br /><br />export const action = async ({ request }: ActionFunctionArgs) => </span><span class="token punctuation">{</span><br /> <span class="token keyword">const</span> formData <span class="token operator">=</span> <span class="token keyword">await</span> request<span class="token punctuation">.</span><span class="token function">formData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> action <span class="token operator">=</span> formData<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"_action"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> userId <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getUserSession</span><span class="token punctuation">(</span>request<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>userId<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token function">redirect</span><span class="token punctuation">(</span><span class="token string">"/signin"</span><span class="token punctuation">,</span> <span class="token number">302</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token keyword">switch</span> <span class="token punctuation">(</span>action<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">case</span> <span class="token string">"toggle_tweet_like"</span><span class="token operator">:</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> tweetId <span class="token operator">=</span> formData<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"tweetId"</span><span class="token punctuation">)</span> <span class="token keyword">as</span> string<span class="token punctuation">;</span><br /> <span class="token keyword">const</span> hasLiked <span class="token operator">=</span> formData<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"hasLiked"</span><span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token string">"true"</span><span class="token punctuation">;</span><br /> <span class="token keyword">await</span> <span class="token function">toggleTweetLike</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> request<span class="token punctuation">,</span><br /> tweetId<span class="token punctuation">,</span><br /> hasLiked<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 keyword">break</span><span class="token punctuation">;</span><br /> <span class="token keyword">case</span> <span class="token string">"toggle_tweet_retweet"</span><span class="token operator">:</span><br /> <span class="token punctuation">{</span><br /> <span class="token comment">/** Handle tweet retweet **/</span> <br /> <span class="token punctuation">}</span><br /> <span class="token keyword">break</span><span class="token punctuation">;</span><br /> <span class="token keyword">case</span> <span class="token string">"reply_to_tweet"</span><span class="token operator">:</span><br /> <span class="token punctuation">{</span><br /> <span class="token comment">/** Handle tweet reply **/</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">break</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token keyword">return</span> <span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">success</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 plain-text">;<br /> </span></code></pre>
<p>Notice how we use <code>hidden</code> type inputs in the form for passing relevant data like the <code>tweetId</code> and <code>hasLiked</code> to the server. We are also setting the name of the button to <code>_action</code> and the value to <code>toggle_tweet_like</code>, this allows us on the server to identify what type of action was triggered, which is useful when you have multiple forms on your page.</p>
<p>Now as we saw in the full stack data flow, Remix will automatically run all the loaders on the page via the browser <code>fetch</code>, updating the UI on the page that was reading the data from the relevant loaders. So the tweet like count and the button state, automatically gets updated. Check out this video to see how it works:</p>
<p><video src="https://prateeksurana.me/videos/tweet-like-remix.webm" controls="" loop="">Looks like your browser doesn't support this video you can download the video <a href="https://prateeksurana.me/videos/tweet-like-remix.webm">here</a>.</video></p>
<p>My favorite part is since Remix forces you to use HTML forms everywhere and since browsers by default, serialize the form inputs, and send data to the server automatically, users can start interacting with the page before the JavaScript even loads. You can verify this by disabling JavaScript and then taking actions on almost any page in the app.</p>
<p>For instance here is an example from Sign in page where the form errors are shown even without JavaScript:</p>
<p><video src="https://prateeksurana.me/videos/remix-form-errors-without-js.webm" controls="" loop="">Looks like your browser doesn't support this video you can download the video <a href="https://prateeksurana.me/videos/remix-form-errors-without-js.webm">here</a>.</video></p>
<p>And another example of following a user from the user profile page:</p>
<p><video src="https://prateeksurana.me/videos/remix-follow-user-without-js.webm" controls="" loop="">Looks like your browser doesn't support this video you can download the video <a href="https://prateeksurana.me/videos/remix-follow-user-without-js.webm">here</a>.</video></p>
<!-- ### Next.js -->
<h3 class="relative">
<a id="nextjs-data-mutations" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#nextjs-data-mutations" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Next.js
</a>
</h3>
<p>Prior to Next.js 13.4, the only way you could create and take actions on the server was by creating <a href="https://nextjs.org/docs/pages/building-your-application/routing/api-routes">API routes</a>. Any file created under <code>pages/api</code> was treated as an API endpoint instead of a normal page.</p>
<p>While it was a great solution for one-off API routes where you need to do some processing on the server, they were not a complete solution since you were on your own when it came to calling the API on the client side and making any revalidations.</p>
<p>That’s one of the reasons solutions like <a href="https://trpc.io/">trpc</a> became so popular, where they leveraged the Next.js’ API route system with <a href="https://tanstack.com/query/v3/">React Query</a> to handle API requests and mutations from the client side.</p>
<p>Next.js 13.4 introduced server actions, and at the time of writing this article, they are still in an experimental phase.</p>
<p>With server actions, you don’t need to create API endpoints. Instead, you can just create asynchronous server functions that can be directly called from your components, and have access to all the Next.js server-only utilities like <code>cookies</code>, <code>revalidate</code>, <code>redirect</code>, etc.</p>
<p>This tweet by Lee Robinson summarizes it really well as to how much less code you have to write with Server Actions:</p>
<div style="display:flex; justify-content:center;">
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">How is this not illegal <a href="https://t.co/TieLpPotYS">pic.twitter.com/TieLpPotYS</a></p>— Lee Robinson (@leeerob) <a href="https://twitter.com/leeerob/status/1659407393095135232?ref_src=twsrc%5Etfw">May 19, 2023</a></blockquote> <script async="" defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</div>
<p>If you are in a Server Component, you can define a Server Action inside a component by placing <code>'use server'</code> on the first line and then either use it with a form by directly passing it to the <code>action</code> prop of <code>form</code> or by passing it to a client component. (When action prop is used in Server Components, the form works without JavaScript)</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">Page</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">createTodo</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">formData</span><span class="token operator">:</span> FormData</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token string">'use server'</span><br /> <span class="token comment">// This will be executed on the server</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>form</span> <span class="token attr-name">action</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>createTodo<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">...</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>form</span><span class="token punctuation">></span></span><br /> <span class="token comment">// or</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">ClientComponent</span></span> <span class="token attr-name">createTodo</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>createTodo<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br /><span class="token punctuation">}</span></code></pre>
<p>You can also create a separate file with the <code>'use server'</code> directive at at top of the file, and all the functions exported from that file can be used as server actions and can be directly imported into client components.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token string">'use server'</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">doStuff</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// This will be executed on the server</span><br /><span class="token punctuation">}</span></code></pre>
<pre class="language-jsx"><code class="language-jsx"><span class="token string">'use client'</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> doStuff <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./actions'</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Button</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>form</span> <span class="token attr-name">action</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>doStuff<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">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">Do stuff</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>form</span><span class="token punctuation">></span></span><br /> <span class="token punctuation">)</span><br /><span class="token punctuation">}</span></code></pre>
<p>When using the <code>action</code> attribute on Client Components, the action will be placed in a queue until the form has hydrated. The <code><form></code> is prioritized with <a href="https://www.patterns.dev/posts/react-selective-hydration">Selective Hydration</a>, so it happens as soon as possible.</p>
<p>Let’s look at some examples from the Next.js Twitter Clone. This is how the code for <a href="https://github.com/prateek3255/twitter-clone/blob/main/apps/nextjs/app/(auth)/signin/page.tsx">Sign in page</a> looks like:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Signin</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span><br /> searchParams<span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">searchParams</span><span class="token operator">:</span> Record<span class="token operator"><</span>string<span class="token punctuation">,</span> string<span class="token operator">></span><span class="token punctuation">;</span><br /><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">signin</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">formData</span><span class="token operator">:</span> FormData</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token string">"use server"</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> auth <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">usernameOrEmail</span><span class="token operator">:</span> formData<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"usernameOrEmail"</span><span class="token punctuation">)</span><span class="token operator">?.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</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> formData<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"password"</span><span class="token punctuation">)</span><span class="token operator">?.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</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><br /> <span class="token keyword">const</span> isUsername <span class="token operator">=</span> <span class="token operator">!</span><span class="token function">isEmail</span><span class="token punctuation">(</span>auth<span class="token punctuation">.</span>usernameOrEmail<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Find an account</span><br /> <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> prisma<span class="token punctuation">.</span>user<span class="token punctuation">.</span><span class="token function">findFirst</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">where</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">[</span>isUsername <span class="token operator">?</span> <span class="token string">"username"</span> <span class="token operator">:</span> <span class="token string">"email"</span><span class="token punctuation">]</span><span class="token operator">:</span> auth<span class="token punctuation">.</span>usernameOrEmail<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 /><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">const</span> error <span class="token operator">=</span> <span class="token function">encodeValueAndErrors</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">fieldErrors</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">usernameOrEmail</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">No account found with the given </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><br /> isUsername <span class="token operator">?</span> <span class="token string">"username"</span> <span class="token operator">:</span> <span class="token string">"email"</span><br /> <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><br /> <span class="token literal-property property">fieldValues</span><span class="token operator">:</span> auth<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 function">redirect</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/signin?</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>error<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><br /><br /> <span class="token comment">// Compare password</span><br /> <span class="token keyword">const</span> isPasswordCorrect <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">comparePassword</span><span class="token punctuation">(</span><br /> auth<span class="token punctuation">.</span>password<span class="token punctuation">,</span><br /> user<span class="token punctuation">.</span>passwordHash<br /> <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>isPasswordCorrect<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> error <span class="token operator">=</span> <span class="token function">encodeValueAndErrors</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">fieldErrors</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">password</span><span class="token operator">:</span> <span class="token string">"Incorrect password"</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">fieldValues</span><span class="token operator">:</span> auth<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 function">redirect</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/signin?</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>error<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><br /><br /> <span class="token comment">// Set auth cookie</span><br /> <span class="token function">setAuthCookie</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">userId</span><span class="token operator">:</span> user<span class="token punctuation">.</span>id<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 function">redirect</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 punctuation">;</span><br /><br /> <span class="token keyword">const</span> <span class="token punctuation">{</span> fieldErrors<span class="token punctuation">,</span> fieldValues <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">decodeValueAndErrors</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">fieldErrors</span><span class="token operator">:</span> searchParams<span class="token punctuation">.</span>fieldErrors<span class="token punctuation">,</span><br /> <span class="token literal-property property">fieldValues</span><span class="token operator">:</span> searchParams<span class="token punctuation">.</span>fieldValues<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 keyword">return</span> <span class="token punctuation">(</span><br /> <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>h1</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>font-bold text-3xl text-white mb-7<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">Sign in to Twitter</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</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 attr-name">action</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>signin<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>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>flex flex-col gap-4 mb-8<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">FloatingInput</span></span><br /> <span class="token attr-name">autoFocus</span><br /> <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Username or Email<span class="token punctuation">"</span></span><br /> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>usernameOrEmail<span class="token punctuation">"</span></span><br /> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>usernameOrEmail<span class="token punctuation">"</span></span><br /> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>john@doe.com<span class="token punctuation">"</span></span><br /> <span class="token attr-name">defaultValue</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>fieldValues<span class="token operator">?.</span>usernameOrEmail<span class="token punctuation">}</span></span><br /> <span class="token attr-name">error</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>fieldErrors<span class="token operator">?.</span>usernameOrEmail<span class="token punctuation">}</span></span><br /> <span class="token attr-name">aria-invalid</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token function">Boolean</span><span class="token punctuation">(</span>fieldErrors<span class="token operator">?.</span>usernameOrEmail<span class="token punctuation">)</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">aria-errormessage</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>fieldErrors<span class="token operator">?.</span>usernameOrEmail <span class="token operator">??</span> <span class="token keyword">undefined</span><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><span class="token class-name">FloatingInput</span></span><br /> <span class="token attr-name">required</span><br /> <span class="token attr-name">label</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><br /> <span class="token attr-name">id</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><br /> <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><br /> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>********<span class="token punctuation">"</span></span><br /> <span class="token attr-name">type</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><br /> <span class="token attr-name">defaultValue</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>fieldValues<span class="token operator">?.</span>password<span class="token punctuation">}</span></span><br /> <span class="token attr-name">error</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>fieldErrors<span class="token operator">?.</span>password<span class="token punctuation">}</span></span><br /> <span class="token attr-name">aria-invalid</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token function">Boolean</span><span class="token punctuation">(</span>fieldErrors<span class="token operator">?.</span>password<span class="token punctuation">)</span><span class="token punctuation">}</span></span><br /> <span class="token attr-name">aria-errormessage</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>fieldErrors<span class="token operator">?.</span>password <span class="token operator">??</span> <span class="token keyword">undefined</span><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 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">SubmitButton</span></span><span class="token punctuation">></span></span><span class="token plain-text">Sign In</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">SubmitButton</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 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><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Although, as of now, there is no declarative way like Remix’s <code>useActionData</code> to read the response from the server action, and for the Sign in page, I wanted a way for the user to show the error without JavaScript, so I used search parameters to encode and decode field values and errors.</p>
<p>The <code>SubmitButton</code> here is a client component that uses an experimental hook called <code>useFormStatus</code> to show the disabled state while the form is submitting.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token string">"use client"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> experimental_useFormStatus <span class="token keyword">as</span> useFormStatus <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react-dom"</span><span class="token punctuation">;</span><br /><span class="token keyword">import</span> <span class="token punctuation">{</span> ButtonOrLink <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"components/ButtonOrLink"</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">SubmitButton</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> <span class="token punctuation">{</span> children<span class="token operator">?</span><span class="token operator">:</span> React<span class="token punctuation">.</span>ReactNode<span class="token punctuation">;</span> <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> pending <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useFormStatus</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><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">ButtonOrLink</span></span><br /> <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><br /> <span class="token attr-name">size</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>large<span class="token punctuation">"</span></span><br /> <span class="token attr-name">disabled</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>pending<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>children <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">ButtonOrLink</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>On the client side, you can also use the <code>startTransition</code> API to execute server actions that do server mutations (call <code>revalidatePath</code>, <code>redirect</code>, or <code>revalidateTag</code> ) and execute the server action directly on button click, for instance, check out how it is implemented for the Follow button:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token punctuation">[</span>isPending<span class="token punctuation">,</span> startTransition<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useTransition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token operator"><</span>ButtonOrLink<br /> disabled<span class="token operator">=</span><span class="token punctuation">{</span>isPending<span class="token punctuation">}</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> <span class="token punctuation">{</span><br /> <span class="token function">startTransition</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">await</span> <span class="token function">toggleFollowUser</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">userId</span><span class="token operator">:</span> profileUserId<span class="token punctuation">,</span> <span class="token literal-property property">isFollowing</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><br /> <span class="token punctuation">}</span><span class="token punctuation">}</span><br /> variant<span class="token operator">=</span><span class="token string">"secondary"</span><br /><span class="token operator">></span><br /> Follow<br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">ButtonOrLink</span></span><span class="token punctuation">></span></span></code></pre>
<p>Similar to Remix, you can directly revalidate paths from server actions which would cause the server components to be invalidated, and the UI would reflect the updates automatically. Although unlike Remix you have to manually call the <code>revalidatePath</code> to refresh the data for that particular path.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">toggleFollowUser</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span><br /> userId<span class="token punctuation">,</span><br /> isFollowing<span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">userId</span><span class="token operator">:</span> string<span class="token punctuation">;</span><br /> <span class="token literal-property property">isFollowing</span><span class="token operator">:</span> boolean<span class="token punctuation">;</span><br /><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">/* Updating the value in DB, omitted for brevity */</span><br /> <span class="token function">revalidatePath</span><span class="token punctuation">(</span><span class="token string">"/[username]"</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>Here is a demo of how the following state is automatically updated on the profile page with the <code>revalidatePath</code> when the user clicks on the follow button:</p>
<p><video src="https://prateeksurana.me/videos/nextjs-unfollow-demo.webm" controls="" loop="">Looks like your browser doesn't support this video you can download the video <a href="https://prateeksurana.me/videos/nextjs-unfollow-demo.webm">here</a>.</video></p>
<!-- ### Bottom Line -->
<h3 class="relative">
<a id="bottom-line-data-mutations" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#bottom-line-data-mutations" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Bottom Line
</a>
</h3>
<p>To be fair, I really like Remix’s approach of actions of how it completes its full-stack data flow by automatically refetching the loaders and updating the UI, also making your app work even before the JavaScript has loaded, improving not only the user experience but also the developer experience significantly.</p>
<p>But actions also have a caveat, which is the same that we saw with loaders, that they can only be defined in route segments. If you want to reuse an action at multiple places, have to do it by specifying the URL of the action in the <code>form</code>'s <code>action</code> attribute. This can get confusing as your app grows because you have to find the file that executes the action based on the value provided in the <code>action</code> prop. For an example you can check out how I had to use it for create tweet action, where it is being used at two places, one on the <a href="https://github.com/prateek3255/twitter-clone/blob/main/apps/remix/app/routes/_base._index.tsx">home page</a> and the other with the <a href="https://github.com/prateek3255/twitter-clone/blob/main/apps/remix/app/routes/resource.create-tweet.tsx">tweet modal</a>.</p>
<p>Next.js’ server actions solve the above problems by allowing you just to create functions that can be called from anywhere inside your app just by importing them. However, at the moment, they lack the good form support and automatic revalidation that Remix has and also feel pretty unstable and under-documented at the moment. I had to <a href="https://github.com/vercel/next.js/discussions/categories/app-router?discussions_q=author%3Aprateek3255+category%3A%22App+Router%22">open a couple of discussions in Next.js</a> to understand how the API works.</p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>Next.js recently released significant updates to server actions with <a href="https://nextjs.org/blog/next-13-5">v13.5</a>. So some of the APIs used above, like using <code>startTransition</code> for server mutations, don’t seem to be documented anymore. They have also added better support for forms now. I’ll be updating the app and the blog with the latest API soon.</p>
<p>For reference, you can find the old Server Actions API that I used while building the Twitter Clone on <a href="https://web.archive.org/web/20230617214943/https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions">Web Archive of Next.js docs</a>.</p>
</aside>
<!-- ## Infinite Loading -->
<h2 class="relative">
<a id="infinite-loading" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#infinite-loading" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Infinite Loading
</a>
</h2>
<p>Infinite loading with infinite scrolling was an interesting problem because neither of the frameworks has first-class support for it, yet it was a very important part of the Twitter Clone app since it was there on almost every page.</p>
<p>As you need to handle infinite loading on the client side, I had to manage the state for them on the client itself via a <code>useReducer</code>. I had a pretty interesting experience adding it to both of them, so I thought this deserved a separate section of it.</p>
<!-- ### Remix -->
<h3 class="relative">
<a id="remix-infinite-loading" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#remix-infinite-loading" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Remix
</a>
</h3>
<p>The infinite scroll implementation was heavily inspired by this article called <a href="https://www.epicweb.dev/full-stack-components">Full Stack Components</a> by Kent C Dodds. It was in this article that I learned about <a href="https://remix.run/docs/en/main/guides/resource-routes">resource routes</a> and how powerful they are in Remix.</p>
<p>The concept is that you create a route similar to a normal route module, but if you don’t export a default component from that route, you can still use the loaders and actions defined in that route via <code>GET</code> and <code>POST</code> requests. They almost act like Next.js’ version of API routes.</p>
<p>So for Remix, I created a new route called <code>routes/resource-infinite-tweets.tsx</code>, which had a named export for <code>InfiniteTweets</code>. Since this was not a default export, Remix won’t render any UI for this route. This named export was used in all the components that had infinite loading tweets.</p>
<p>I won’t go into much detail about how the component worked; you can check out the <a href="https://github.com/prateek3255/twitter-clone/blob/main/apps/remix/app/routes/resource.infinite-tweets.tsx">relevant code for it on GitHub</a>. In short, I used the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API"><code>IntersectionObserver</code></a> API to detect the end of the page and triggered a request to fetch the next page of tweets, which were then added to the reducer. All the other states including like/retweet/reply counts, are also stored in the reducer.</p>
<p>Let’s take an example of one of the pages this component was used in: <a href="https://twitter-remix-run.vercel.app/wolverine">the user tweets page</a>. The first page of the tweets is loaded on the server and is streamed to the client, as we saw in the <a href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#remix-streaming">streaming section</a>. But for the next page onwards, we use the loader defined in the <code>resource-infinite-tweets.tsx</code>, which looks something like this:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">loader</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> request <span class="token punctuation">}</span><span class="token operator">:</span> LoaderFunctionArgs</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> cursor <span class="token operator">=</span> <span class="token function">getSearchParam</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>url<span class="token punctuation">,</span> <span class="token string">"cursor"</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> type <span class="token operator">=</span> <span class="token function">getSearchParam</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>url<span class="token punctuation">,</span> <span class="token string">"type"</span><span class="token punctuation">)</span> <span class="token keyword">as</span> InfiniteTweetType<span class="token punctuation">;</span><br /> <span class="token keyword">const</span> username <span class="token operator">=</span> <span class="token function">getSearchParam</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>url<span class="token punctuation">,</span> <span class="token string">"username"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> tweetId <span class="token operator">=</span> <span class="token function">getSearchParam</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>url<span class="token punctuation">,</span> <span class="token string">"tweetId"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">let</span> <span class="token literal-property property">tweets</span><span class="token operator">:</span> Array<span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">TweetWithMeta</span></span><span class="token punctuation">></span></span><span class="token plain-text"> = [];<br /> switch (type) </span><span class="token punctuation">{</span><br /> <span class="token keyword">case</span> <span class="token string">"user_tweets"</span><span class="token operator">:</span><br /> tweets <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getTweetsByUsername</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> username <span class="token keyword">as</span> string<span class="token punctuation">,</span> cursor<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">break</span><span class="token punctuation">;</span><br /> <span class="token keyword">case</span> <span class="token string">"home_timeline"</span><span class="token operator">:</span><br /> tweets <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getHomeTweets</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> cursor<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">break</span><span class="token punctuation">;</span><br /> <span class="token keyword">case</span> <span class="token string">"tweet_replies"</span><span class="token operator">:</span><br /> tweets <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getTweetReplies</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> tweetId <span class="token keyword">as</span> string<span class="token punctuation">,</span> cursor<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">break</span><span class="token punctuation">;</span><br /> <span class="token keyword">case</span> <span class="token string">"user_replies"</span><span class="token operator">:</span><br /> tweets <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getUserReplies</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> username <span class="token keyword">as</span> string<span class="token punctuation">,</span> cursor<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">break</span><span class="token punctuation">;</span><br /> <span class="token keyword">case</span> <span class="token string">"user_likes"</span><span class="token operator">:</span><br /> tweets <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getUserLikes</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> username <span class="token keyword">as</span> string<span class="token punctuation">,</span> cursor<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">break</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token plain-text"><br /><br /> return json(<br /> </span><span class="token punctuation">{</span><br /> tweets<span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token plain-text">,<br /> 200<br /> );<br />};</span></code></pre>
<p>Now to trigger the loader, we use the <code>fetcher</code> that we saw in the data mutations section. It also has a method called <a href="https://remix.run/docs/en/main/hooks/use-fetcher#fetchersubmitformdata-options"><code>submit</code></a> that allows us to programmatically trigger a <code>GET</code> request to the loader, which fetches the next batch of tweets.</p>
<pre class="language-jsx"><code class="language-jsx">React<span class="token punctuation">.</span><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>isLoading <span class="token operator">||</span> isLastPage <span class="token operator">||</span> <span class="token operator">!</span>isVisible <span class="token operator">||</span> <span class="token operator">!</span>shouldFetch<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 punctuation">}</span><br /> fetcher<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span><br /> <span class="token punctuation">{</span><br /> type<span class="token punctuation">,</span><br /> <span class="token literal-property property">cursor</span><span class="token operator">:</span> lastTweetId<span class="token punctuation">,</span><br /> <span class="token operator">...</span>rest<span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token punctuation">{</span><br /> <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">"GET"</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">action</span><span class="token operator">:</span> <span class="token string">"/resource/infinite-tweets"</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 function">setShouldFetch</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><br /> isVisible<span class="token punctuation">,</span><br /> lastTweetId<span class="token punctuation">,</span><br /> isLoading<span class="token punctuation">,</span><br /> isLastPage<span class="token punctuation">,</span><br /> type<span class="token punctuation">,</span><br /> shouldFetch<span class="token punctuation">,</span><br /> rest<span class="token punctuation">,</span><br /> fetcher<br /> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This effect is triggered when specific conditions are met that indicate that the page needs to be fetched. The data is then made available in <code>fetcher.data</code> which is added to the reducer in another effect.</p>
<pre class="language-jsx"><code class="language-jsx">React<span class="token punctuation">.</span><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>fetcher<span class="token punctuation">.</span>data <span class="token operator">&&</span> Array<span class="token punctuation">.</span><span class="token function">isArray</span><span class="token punctuation">(</span>fetcher<span class="token punctuation">.</span>data<span class="token punctuation">.</span>tweets<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">dispatch</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"add_tweets"</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">newTweets</span><span class="token operator">:</span> <span class="token function">mapToTweet</span><span class="token punctuation">(</span>fetcher<span class="token punctuation">.</span>data<span class="token punctuation">.</span>tweets<span class="token punctuation">,</span> isLoggedIn<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">setShouldFetch</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> <span class="token punctuation">[</span>fetcher<span class="token punctuation">.</span>data<span class="token punctuation">,</span> isLoggedIn<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This route module has an action as well, which handles all the liking/retweeting/replies for the tweets, which use the same code with <code>fetcher.Form</code> that we saw in the <a href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#remix-data-mutations">data mutations</a> section.</p>
<!-- ### Next.js -->
<h3 class="relative">
<a id="nextjs-infinite-loading" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#nextjs-infinite-loading" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Next.js
</a>
</h3>
<p>The implementation in Next.js is also very similar to Remix, the main differences are <code>InfiniteTweets</code> is a client component, and we use server actions for loading the next set of pages.</p>
<p>Similar to Remix, the first page of the tweets is streamed from the server. The <code>loading.tsx</code> file that we saw in the streaming section comes in really handy here. We just add this file in <a href="https://github.com/prateek3255/twitter-clone/tree/main/apps/nextjs/app/(base)/%5Busername%5D">all the tabs for the profile page</a>, and Next.js takes care of the pages in <code>Suspense</code> boundaries.</p>
<p>Here is what the code for the user tweets page looks like:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">Profile</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span><br /> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span> username <span class="token punctuation">}</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">username</span><span class="token operator">:</span> string <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><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>tweets<span class="token punctuation">,</span> currentLoggedInUser<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span><br /> <span class="token function">getTweetsByUsername</span><span class="token punctuation">(</span>username<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token function">getCurrentLoggedInUser</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 keyword">const</span> <span class="token function-variable function">fetchNextUserTweetsPage</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">cursor</span><span class="token operator">:</span> string</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token string">"use server"</span><span class="token punctuation">;</span><br /> <span class="token keyword">const</span> tweets <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getTweetsByUsername</span><span class="token punctuation">(</span>username<span class="token punctuation">,</span> cursor<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> tweets<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 punctuation">(</span><br /> <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><span class="token comment">/** Tweets */</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 plain-text"><br /> </span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">InfiniteTweets</span></span><br /> <span class="token attr-name">initialTweets</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>tweets<span class="token punctuation">}</span></span><br /> <span class="token attr-name">currentLoggedInUser</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><br /> currentLoggedInUser<br /> <span class="token operator">?</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">id</span><span class="token operator">:</span> currentLoggedInUser<span class="token punctuation">.</span>id<span class="token punctuation">,</span><br /> <span class="token literal-property property">username</span><span class="token operator">:</span> currentLoggedInUser<span class="token punctuation">.</span>username<span class="token punctuation">,</span><br /> <span class="token literal-property property">name</span><span class="token operator">:</span> currentLoggedInUser<span class="token punctuation">.</span>name <span class="token operator">??</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">profileImage</span><span class="token operator">:</span> currentLoggedInUser<span class="token punctuation">.</span>profileImage<span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><br /> <span class="token operator">:</span> <span class="token keyword">undefined</span><br /> <span class="token punctuation">}</span></span><br /> <span class="token attr-name">fetchNextPage</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>fetchNextUserTweetsPage<span class="token punctuation">}</span></span><br /> <span class="token attr-name">isUserProfile</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 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><br /> <span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Notice how we are creating a server action called <code>fetchNextUserTweetsPage</code> and passing it to the <code>InfiniteTweets</code> component. This component then fetches the next page of the tweets by calling the action passed via this prop.</p>
<pre class="language-jsx"><code class="language-jsx">React<span class="token punctuation">.</span><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">const</span> <span class="token function-variable function">updateTweets</span> <span class="token operator">=</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">if</span> <span class="token punctuation">(</span>isLoading <span class="token operator">||</span> isLastPage<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 punctuation">}</span><br /> <span class="token function">setIsLoading</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> nextTweets <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetchNextPage</span><span class="token punctuation">(</span>lastTweetId<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">setIsLoading</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">dispatch</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"add_tweets"</span><span class="token punctuation">,</span><br /> <span class="token function">newTweetsRemixToTweet</span><span class="token punctuation">(</span>nextTweets<span class="token punctuation">,</span> isLoggedIn<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><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>isVisible<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token function">updateTweets</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><span class="token punctuation">,</span> <span class="token punctuation">[</span>isVisible<span class="token punctuation">,</span> lastTweetId<span class="token punctuation">,</span> isLoading<span class="token punctuation">,</span> isLastPage<span class="token punctuation">,</span> fetchNextPage<span class="token punctuation">,</span> isLoggedIn<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Then similar to Remix, we add the data to the reducer, which renders the next set of tweets on the page.</p>
<!-- ### Bottom line -->
<h3 class="relative">
<a id="bottom-line-infinite-loading" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#bottom-line-infinite-loading" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Bottom line
</a>
</h3>
<p>Infinite loading was the only part in both frameworks where I had to manage the state of the tweets on the client side.</p>
<p>This is the part where the composability aspect of Server Actions really shines in Next.js 13. I just had to fetch the first page in the the Server Component that would be streamed and then create a Server Action for fetching the next pages in the component itself which was directly passed to the client component.</p>
<p>In Remix, although the fetcher and resource route did make fetching data easier but then we also had to create a separate loader for each route to streamed the first page for the infinite tweets.</p>
<p>Either way, the solution was not perfect in both of them and in general, I would have preferred a solution that libraries like React Query offer with their <a href="https://tanstack.com/query/latest/docs/react/reference/useInfiniteQuery?from=reactQueryV3&original=https%3A%2F%2Ftanstack.com%2Fquery%2Fv3%2Fdocs%2Freference%2FuseInfiniteQuery"><code>useInfiniteQuery</code></a> hook, which helps you manage invalidations and optimistic updates on the client side really well for infinite queries like this.</p>
<!-- ## Other features -->
<h2 class="relative">
<a id="other-features" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#other-features" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Other features
</a>
</h2>
<p>Both of these frameworks have a bunch of other useful features as which include:</p>
<!-- ### Routing -->
<h3 class="relative">
<a id="routing" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#routing" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Routing
</a>
</h3>
<p>Both Remix and Next.js have a very powerful client-side router, which instead of doing a full page reload and doing a round trip to the server to fetch the full document, they just update the UI, re-rendering only the route segments that change.</p>
<p>Next.js detects the routes that need to be prefetched in the background for all the <code><Link/></code> tags visible in the viewport. For dynamic routes, the shared layout down until the first <code>loading.tsx</code> file is <a href="https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#how-routing-and-navigation-works">prefetched and cached for <code>30s</code></a>. This allows you to show an instant loading state as soon as the user clicks on the route.</p>
<p><video src="https://prateeksurana.me/videos/nextjs-routing-demo.webm" controls="" loop="">Looks like your browser doesn't support this video you can download the video <a href="https://prateeksurana.me/videos/nextjs-routing-demo.webm">here</a>.</video></p>
<p>Remix goes one step further with the <a href="https://remix.run/docs/en/main/components/link#prefetch"><code>prefetch</code></a> attribute, allowing you to specify different values based on your use case. My favorite one is <code>intent</code> which fetches not only all the JavaScript bundles but also all the data necessary for the next route via <code><link rel="prefetch"></code> tags when you hover on the link. This allows you to render the next page almost instantaneously.</p>
<p><video src="https://prateeksurana.me/videos/remix-routing-demo.webm" controls="" loop="">Looks like your browser doesn't support this video you can download the video <a href="https://prateeksurana.me/videos/remix-routing-demo.webm">here</a>.</video></p>
<!-- ### Error Handling -->
<h3 class="relative">
<a id="error-handling" href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/#error-handling" class="header-anchor">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="absolute top-2 -left-7 opacity-0 icon" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
Error Handling
</a>
</h3>
<p>Both of the frameworks have first-class support for handling both expected and unexpected errors globally and inside every route segment.</p>
<p>In Remix, similar to <code>loader</code> and <code>action</code>, you can export <a href="https://remix.run/docs/en/main/guides/errors#root-error-boundary"><code>ErrorBoundary</code></a> which renders the error state for that route segment. It handles both unexpected errors that might occur on the server or in the browser and expected errors like 404. To catch an expected error, you can throw a Response from your loaders. For an example, check out the <a href="https://github.com/prateek3255/twitter-clone/blob/main/apps/remix/app/routes/_base.%24username.tsx#L169">404 state for the user profile page</a>.</p>
<p>In Next.js, again, you have separate files in each route segment for rendering the error state for that route. <a href="https://nextjs.org/docs/app/api-reference/file-conventions/error"><code>error.tsx</code></a> is meant to specifically handle any browser or server errors that occur in the route segment. It wraps an <code>ErrorBoundary</code> over your route segment, similar to how we saw the <code>Suspense</code> boundary with <code>loading.tsx</code>. Again this is nicely illustrated via this image from the <a href="https://nextjs.org/docs/app/building-your-application/routing/error-handling#how-errorjs-works">Next.js docs</a>:</p>
<picture>
<source type="image/webp" srcset="https://prateeksurana.me/img/nextjs-error-boundary-480.webp 480w, https://prateeksurana.me/img/nextjs-error-boundary-768.webp 768w, https://prateeksurana.me/img/nextjs-error-boundary-1200.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<source type="image/jpeg" srcset="https://prateeksurana.me/img/nextjs-error-boundary-480.jpeg 480w, https://prateeksurana.me/img/nextjs-error-boundary-768.jpeg 768w, https://prateeksurana.me/img/nextjs-error-boundary-1200.jpeg 1200w" sizes="(max-width: 600px) 480px, (max-width: 1024px) 768px, 1200px" />
<img src="https://prateeksurana.me/img/nextjs-error-boundary-768.jpeg" width="768" height="432" alt="Next.js Error Boundary" class="article-img" loading="lazy" decoding="async" />
</picture>
<p>For handling 404s, Next.js has a specific file called <a href="https://nextjs.org/docs/app/api-reference/file-conventions/not-found"><code>not-found.tsx</code></a> for every route segment. These are triggered by returning the <a href="https://nextjs.org/docs/app/api-reference/functions/not-found"><code>notFound</code></a> utility function within your server components. Again you can check out the <a href="https://github.com/prateek3255/twitter-clone/blob/main/apps/nextjs/app/(base)/not-found.tsx"><code>not-found.tsx</code></a> file for the user profile page of the Next.js Twitter Clone.</p>
<h3>Caching</h3>
<p>For the Twitter Clone app, there wasn’t much use of caching and static rendering since all the route segments needed to use the user’s cookies to fetch data related to the user.</p>
<p>Next.js has improved the caching support significantly, where they have different layers of caching that allow you to cache not only the rendered routes but also the responses for <code>fetch</code> requests on the edge. You can read about it in much more detail in <a href="https://nextjs.org/docs/app/building-your-application/caching">Next.js’ Caching docs.</a></p>
<aside class="bg-gray-200 text-black sm:text-xl text-base relative mt-7 callout sm:px-8 sm:py-2 px-6 py-1">
<p>For the Twitter Clone, we do use the <a href="https://nextjs.org/docs/app/building-your-application/caching#request-memoization">request memoization</a>, which memoizes functions that request the same data in multiple places in the Server React component tree while only executing it once.</p>
</aside>
<p>Remix doesn’t have any opinion about caching, and since it simply speaks HTTP, you can just simply use <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control"><code>Cache-Control</code> headers</a> to cache responses on the edge and in the browser, or you can use other server side caching solutions like <a href="https://redis.io/">Redis</a>.</p>
<h2>Conclusion</h2>
<p>If you made it this far, then I hope you enjoyed reading the blog, you had some interesting takeaways for either of the frameworks that help making you a better decision when building your next full-stack app.</p>
<p>In conclusion, building complex full-stack web applications in React has never been this faster and easier, thanks to both of these frameworks. As for me, I really loved how Remix has built a framework that leverages the fundamental Web APIs and offers you a simple yet powerful way of building modern web apps. Meanwhile, the app directory in Next.js just blows my mind with how React Server Components and Server Actions allow you to compose and create full-stack components while shipping a deterministic bundle size to the browser. Really excited about what the future holds for both frameworks.</p>