<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Jan Hesters on Medium]]></title>
        <description><![CDATA[Stories by Jan Hesters on Medium]]></description>
        <link>https://medium.com/@jan.hesters?source=rss-bf465a0206e8------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*16fSViYbMW8dxpWUrOr81A.png</url>
            <title>Stories by Jan Hesters on Medium</title>
            <link>https://medium.com/@jan.hesters?source=rss-bf465a0206e8------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 03:19:19 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@jan.hesters/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[How to Set Up Next.js 15 for Production in 2024]]></title>
            <link>https://medium.com/@jan.hesters/how-to-set-up-next-js-15-for-production-in-2024-347f542922b4?source=rss-bf465a0206e8------2</link>
            <guid isPermaLink="false">https://medium.com/p/347f542922b4</guid>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[nextjs-15]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Jan Hesters]]></dc:creator>
            <pubDate>Tue, 19 Nov 2024 10:49:17 GMT</pubDate>
            <atom:updated>2024-11-19T10:49:17.101Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BmlDEinxnjuXGKnaNYOwVA.jpeg" /></figure><p>I’ve worked on Next.js applications that scaled to over 100k monthly active users and landing pages with millions of monthly visitors. In this article, I’ll share all the lessons I’ve learned through countless iterations.</p><p>Whether you’re part of a small team of one, two, or three developers, or you work on a massive Next.js project with multiple teams on the same codebase, it’s critical to get the foundation of your app right from the start. Even if you’re working on an existing project, you will discover some hard-earned insights that you can apply to your app today.</p><p>Let me take you by the hand and show you all the steps and packages that you want to set up and explain to you why they’re useful, so your app scales smoothly.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FbNSqh5rVqFE%3Fstart%3D3%26feature%3Doembed%26start%3D3&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DbNSqh5rVqFE&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FbNSqh5rVqFE%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/3029944b2bfc1454067d12f6327a4e56/href">https://medium.com/media/3029944b2bfc1454067d12f6327a4e56/href</a></iframe><h3>Initialize Your Project</h3><p>Begin by creating a new Next.js project.</p><pre>npx create-next-app@latest</pre><p>It may ask you if it can install the latest create-next-app version, just hit yes.</p><pre>Need to install the following packages:<br>create-next-app@15.0.0<br>Ok to proceed? (y)</pre><p>And then configure your project by hitting yeson everything (TypeScript, Tailwind, app router).</p><pre>✔ **What is your project named?** … reactsquad-production<br>✔ **Would you like to use** **TypeScript****?** … No / Yes<br>Yes<br>✔ **Would you like to use** **ESLint****?** … No / Yes<br>Yes<br>✔ **Would you like to use** **Tailwind CSS****?** … No / Yes<br>Yes<br>✔ **Would you like to use** **`src/` directory****?** … No / Yes<br>Yes<br>✔ **Would you like to use** **App Router****? (recommended)** … No / Yes<br>Yes<br>✔ **Would you like to customize the default** **import alias** **(@/*)?** … No / No</pre><p>Then change into the directory of your project and open it in your favorite editor.</p><pre>$ cd nextjs-for-production<br>~/dev/nextjs-for-production (main) 🤯<br>$ cursor .</pre><h3>Run The Development Server</h3><p>You want to verify that your setup worked.</p><ol><li>Run npm run dev to start the development server.</li><li>Visit http://localhost:3000 to view your application.</li></ol><h3>Type Checks With TypeScript</h3><p>Your project has TypeScript already configured, but you also want to add an explicit command to your package.json that checks all your files for type errors.</p><pre>&quot;type-check&quot;: &quot;tsc -b&quot;</pre><p>You’re going to use this command later together with other automated static analysis checks.</p><h3>Code Formatting</h3><p>When you collaborate with a large team on a project, it’s important to unify the way that everyone writes code. Discuss choices like using semicolons, quote styles, and tabs versus spaces with your team.</p><p>Then use tools to enforce your style guide and format code automatically.</p><p>There are two tools for that: <a href="https://prettier.io/">Prettier</a> and <a href="https://eslint.org/">ESLint</a>.</p><h3>Prettier</h3><p>Prettier is an opinionated code formatter that eliminates style discussions during code reviews.</p><p>Install Prettier together with its Tailwind plugin.</p><pre>npm install --save-dev prettier prettier-plugin-tailwindcss</pre><p>Create aprettier.config.js file with your preferred rules.</p><pre>module.exports = {<br>  arrowParens: &#39;avoid&#39;,<br>  bracketSameLine: false,<br>  bracketSpacing: true,<br>  htmlWhitespaceSensitivity: &#39;css&#39;,<br>  insertPragma: false,<br>  jsxSingleQuote: false,<br>  plugins: [&#39;prettier-plugin-tailwindcss&#39;],<br>  printWidth: 80,<br>  proseWrap: &#39;always&#39;,<br>  quoteProps: &#39;as-needed&#39;,<br>  requirePragma: false,<br>  semi: true,<br>  singleQuote: true,<br>  tabWidth: 2,<br>  trailingComma: &#39;all&#39;,<br>  useTabs: false,<br>};</pre><p>Add a formatting script to your package.json.</p><pre>&quot;format&quot;: &quot;prettier --write .&quot;,</pre><p>Run the formatter to apply your style.</p><pre>$ npm run format<br><br>&gt; nextjs-for-production@0.1.0 format<br>&gt; prettier --write .<br><br>next.config.mjs 4ms (unchanged)<br>package-lock.json 54ms (unchanged)<br>package.json 1ms (unchanged)<br>postcss.config.mjs 2ms (unchanged)<br>README.md 20ms (unchanged)<br>src/app/globals.css 17ms (unchanged)<br>src/app/layout.tsx 30ms (unchanged)<br>src/app/page.tsx 11ms (unchanged)<br>tailwind.config.ts 2ms (unchanged)<br>tsconfig.json 2ms (unchanged)</pre><p>Your files are now “prettier”, but you also want to use ESLint.</p><h3>ESLint</h3><p>ESLint can scan your code for both stylistic and logical issues. Install ESLint and its plugins, like unicorn, playwright and import sort.</p><pre>npm install --save-dev @typescript-eslint/parser eslint-plugin-unicorn eslint-plugin-import eslint-plugin-playwright eslint-config-prettier eslint-plugin-prettier eslint-plugin-simple-import-sort</pre><p>Update your.eslintrc.json</p><pre>{<br>  &quot;extends&quot;: [<br>    &quot;next/core-web-vitals&quot;,<br>    &quot;plugin:unicorn/recommended&quot;,<br>    &quot;plugin:import/recommended&quot;,<br>    &quot;plugin:playwright/recommended&quot;,<br>    &quot;plugin:prettier/recommended&quot;<br>  ],<br>  &quot;plugins&quot;: [&quot;simple-import-sort&quot;],<br>  &quot;rules&quot;: {<br>    &quot;simple-import-sort/exports&quot;: &quot;error&quot;,<br>    &quot;simple-import-sort/imports&quot;: &quot;error&quot;,<br>    &quot;unicorn/no-array-callback-reference&quot;: &quot;off&quot;,<br>    &quot;unicorn/no-array-for-each&quot;: &quot;off&quot;,<br>    &quot;unicorn/no-array-reduce&quot;: &quot;off&quot;,<br>    &quot;unicorn/prevent-abbreviations&quot;: [<br>      &quot;error&quot;,<br>      {<br>        &quot;allowList&quot;: {<br>          &quot;e2e&quot;: true<br>        },<br>        &quot;replacements&quot;: {<br>          &quot;props&quot;: false,<br>          &quot;ref&quot;: false,<br>          &quot;params&quot;: false<br>        }<br>      }<br>    ]<br>  },<br>  &quot;overrides&quot;: [<br>    {<br>      &quot;files&quot;: [&quot;*.js&quot;],<br>      &quot;rules&quot;: {<br>        &quot;unicorn/prefer-module&quot;: &quot;off&quot;<br>      }<br>    }<br>  ]<br>}</pre><p>This tutorial’s plugins offer different functions. For detailed descriptions, visit their respective GitHub pages.</p><p>But in short, they enforce coding standards, organize imports, and ensure correct use of modern JavaScript features. Since ESLint and Prettier can conflict, this setup makes them work together smoothly. The plugins also help prevent bugs and keep styles consistent, especially with tools like Vitest and Playwright.</p><p>Add a linting script to your package.json.</p><pre>&quot;lint:fix&quot;: &quot;next lint --fix&quot;,</pre><p>Run it to format all files according to your new rules.</p><pre>$ npm run lint:fix<br><br>&gt; nextjs-for-production@0.1.0 lint:fix<br>&gt; next lint --fix<br><br>✔ No ESLint warnings or errors</pre><p>If you get a TypeScript version warning, you can ignore that.</p><p><strong>Note:</strong> As of this article’s writing, ESLint 9 is available, but this tutorial uses ESLint 8 because many plugins do <strong><em>not</em></strong> yet support the latest version.</p><h3>Commitlint</h3><p>When collaborating with a large team, it’s also helpful to enforce consistent commit messages to keep the project history clear. By choosing the right standards, you can automate changelog and release generation with correct semantic versioning.</p><p>Install <a href="https://commitlint.js.org/">Commitlint</a> and its necessary configurations. This includes <a href="https://typicode.github.io/husky/">Husky</a>, which helps manage Git hooks.</p><pre>npm install --save-dev @commitlint/cli@latest @commitlint/config-conventional@latest husky@latest</pre><p>Initialize Husky in your project to set up the basic configuration.</p><pre>npx husky-init &amp;&amp; npm install</pre><p>Add hooks to automate linting and type checking before each commit, and customize your commit message workflow.</p><pre>npx husky add .husky/pre-commit &#39;npm run lint &amp;&amp; npm run type-check&#39;<br>npx husky add .husky/prepare-commit-msg &#39;exec &lt; /dev/tty &amp;&amp; npx cz --hook || true&#39;</pre><p>The pre-commit hook runs after git commit, but before the commit message is finalized and runs linting and type-checking on your code.</p><p>The prepare-commit-msg hook runs after git commit is initiated but before the commit message editor opens. It runs commitizen CLI to let you craft conventional commit messages. You&#39;ll learn how to use this hook in a bit.</p><p>Remove the line that says npm test from .husky/_/pre-commit.</p><p>Make sure these scripts are executable.</p><pre>chmod a+x .husky/pre-commit<br>chmod a+x .husky/prepare-commit-msg</pre><p>Here, chmod stands for &quot;change mode.&quot; This command allows you to change the access permissions or modes of a file or directory in Unix and Unix-like operating systems. The argument a+x adds execute permissions for all users.</p><p>Install <a href="https://commitizen-tools.github.io/commitizen/">Commitizen</a>, which provides a CLI for crafting conventional commit messages.</p><pre>npm install --save-dev commitizen cz-conventional-changelog</pre><p>Configure Commitizen in your package.json to use the conventional changelog standard.</p><pre>&quot;config&quot;: {<br>  &quot;commitizen&quot;: {<br>    &quot;path&quot;: &quot;cz-conventional-changelog&quot;<br>  }<br>},</pre><p>The conventional changeloge standard is a specification for adding human and machine-readable meaning to commit messages. It is designed to automate the producing of changelogs and releases based on the Git history of your project.</p><p>A future article will explain this standard in detail. It’s included in this tutorial because getting it right from the start is important. You can use it and benefit from it without needing an in-depth understanding.</p><p>Create your commitlint.config.cjs file with rules that suit your team&#39;s needs. This setup ensures your commit messages are consistent and relevant to the changes made.</p><pre>const config = {<br>  extends: [&#39;@commitlint/config-conventional&#39;],<br>  rules: {<br>    &#39;references-empty&#39;: [1, &#39;never&#39;],<br>    &#39;footer-max-line-length&#39;: [0, &#39;always&#39;],<br>    &#39;body-max-line-length&#39;: [0, &#39;always&#39;],<br>  },<br>};<br><br>module.exports = config;</pre><p>Run the following command to start crafting your commit messages using a guided CLI.</p><pre>$ git add --all<br>$ npx cz<br>cz-cli@4.3.0, cz-conventional-changelog@3.3.0<br><br>? Select the type of change that you&#39;re<br>committing: (Use arrow keys)<br>❯ feat:     A new feature<br>  fix:      A bug fix<br>  docs:     Documentation only changes<br>  style:    Changes that do not affect the<br>meaning of the code (white-space, formatting,<br>missing semi-colons, etc)</pre><p>The cz command asks a series of questions and then writes your commit message for you.</p><pre>? Select the type of change that you\\&#39;re committing: feat:     A new feature<br>? What is the scope of this change (e.g. component or file name): (press enter to skip)<br>? Write a short, imperative tense description of the change (max 63 chars):<br> (61) set up TS type checks, ESLint, Prettier, Commitlint and Husky<br>? Provide a longer description of the change: (press enter to skip)<br> Sets up package.json script to do TS type checks. Sets up Prettier and ESLint with good rules. Configures Husky and sets up Commitizen using the conventional commit standard.<br>? Are there any breaking changes? No<br>? Does this change affect any open issues? No<br>[main eb69ccd] feat(set up static analysis checks): set up TS type checks, ESLint, Prettier, Commitlint and Husky<br> 7 files changed, 3108 insertions(+), 159 deletions(-)<br> create mode 100755 .husky/pre-commit<br> create mode 100755 .husky/prepare-commit-msg<br> create mode 100644 commitlint.config.cjs</pre><p>As you answer all questions, the Husky hooks will automatically run TypeScript type checks and linting.</p><h3>Folder structure</h3><p>There are usually two popular ways to organize your code: grouping by type or grouping by feature.</p><p>Group by type looks like this:</p><pre>.<br>├── components<br>│ ├── todos<br>│ └── user<br>├── reducers<br>│ ├── todos<br>│ └── user<br>└── tests<br>    ├── todos<br>    └── user</pre><p>And grouping by feature looks like this:</p><pre>.<br>├── todos<br>│ ├── component<br>│ ├── reducer<br>│ └── test<br>└── user<br>    ├── component<br>    ├── reducer<br>    └── test</pre><p>Grouping files by feature in a project organizes all related components, reducers, and tests together, making it easier to manage and modify each feature. It has the following benefits:</p><ul><li><strong>Scalability</strong>: Large applications are easier to scale and maintain because each feature acts like a mini-application. You avoid scrolling up and down in your file list to find all the files you need.</li><li><strong>Collaboration</strong>: Developers can focus on specific features without disrupting others’ work.</li><li><strong>Onboarding</strong>: New developers understand the project structure faster, as all files for a feature are in one place.</li><li><strong>Refactoring</strong>: Updating a feature is streamlined since all its elements are grouped together.</li><li><strong>Modularity</strong>: Features can be reused, shared, or turned into standalone packages more easily.</li><li><strong>Micro-frontends</strong>: If your application ever grows to 30 or more engineers, it is easy to refactor to micro-frontends.</li></ul><p>Here is a more concrete example.</p><pre>src/<br>├── app/<br>│   ├── ...<br>│   ├── (group)/<br>│   │   ├── about/<br>│   │   │   └── page.tsx<br>│   │   ├── settings/<br>│   │   │   └── page.tsx<br>│   │   └── layout.tsx<br>│   ├── dashboard/<br>│   │   ├── page.tsx<br>│   │   └── layout.tsx<br>│   ├── layout.tsx<br>│   └── page.tsx<br>├── components/<br>│   ├── ...<br>│   └── header/<br>│       ├── header-component.tsx<br>│       ├── header-component.test.ts<br>│       └── header.module.css<br>├── features/<br>│   ├── ...<br>│   ├── todos/<br>│   │   ├── ...<br>│   │   ├── todos-component.tsx<br>│   │   ├── todos-component.test.ts<br>│   │   ├── todos-container.ts<br>│   │   ├── todos-reducer.ts<br>│   │   ├── todos-reducer.test.ts<br>│   │   └── todos-styles.ts<br>│   └── user/<br>│       ├── ...<br>│       ├── user-reducer.ts<br>│       └── user-reducer.test.ts<br>├── hocs/<br>│   ├── ...<br>│   └── with-layout.tsx<br>├── hooks/<br>├── redux/<br>│   ├── root-reducer.ts<br>│   ├── root-saga.ts<br>│   └── store.ts<br>├── ...<br>└── middleware.ts</pre><p>This example shows you how you can organize your src/ directory. Generally, everything is grouped by feature. Tests files live next to their respective implementation files. But anything that is shared by multiple features is then grouped in general folders, such as components/ or HOCs/ or hooks/. Shared setup for state management - in this example for Redux - lives in a redux/ folder.</p><p>Some people also like to group by feature within the app/ folder. Here is how that would look like.</p><pre>src/<br>├── app/<br>│   ├── ...<br>│   ├── (group)/<br>│   ├── dashboard/<br>│   │   ├── components/<br>│   │   │   ├── dashboard-header.tsx<br>│   │   │   ├── dashboard-header.test.ts<br>│   │   │   ├── dashboard-widgets.tsx<br>│   │   │   └── dashboard-widgets.test.ts<br>│   │   ├── services/<br>│   │   │   ├── fetch-data.ts<br>│   │   │   ├── fetch-data.test.ts<br>│   │   │   ├── auth-service.ts<br>│   │   │   └── auth-service.test.ts<br>│   │   ├── page.tsx<br>│   │   └── layout.ts<br>│   ├── layout.tsx<br>│   └── page.tsx<br>├── ...<br>└── middleware.ts</pre><p>In this tutorial, you’re going to group files by feature in the first way.</p><h3>Vitest</h3><p>If you want to avoid bugs and prevent regressions, you need to write tests. <a href="https://vitest.dev/">Vitest</a> is a great choice because it has the same API as the most popular framework, which is <a href="https://jestjs.io/">Jest</a>, but it runs faster.</p><p>Install Vitest.</p><pre>npm install -D vitest</pre><p>And configure a testing command in your package.json.</p><pre>&quot;test&quot;: &quot;vitest --reporter=verbose&quot;,</pre><p>Then create anexample.test.ts file and write a short test to check to check Vitest works.</p><pre>import { describe, expect, test } from &#39;vitest&#39;;<br><br>describe(&#39;example&#39;, () =&gt; {<br>  test(&#39;given a passing test: passes&#39;, () =&gt; {<br>    expect(1).toStrictEqual(1);<br>  });<br>});</pre><p>It should pass.</p><pre>$ npm test<br><br>&gt; nextjs-for-production@0.1.0 test<br>&gt; vitest --reporter=verbose<br><br><br> DEV  v2.0.5 /Users/jan/dev/nextjs-for-production<br><br> ✓ src/example.test.ts (1)<br>   ✓ example (1)<br>     ✓ given a passing test: passes<br><br> Test Files  1 passed (1)<br>      Tests  1 passed (1)<br>   Start at  14:06:24<br>   Duration  203ms (transform 20ms, setup 0ms, collect 17ms, tests 1ms, environment 0ms, prepare 56ms)<br><br><br> PASS  Waiting for file changes...<br>       press h to show help, press q to quit</pre><p>If you want to learn how you can write better tests, check out <a href="https://janhesters.com/blog/12-keys-write-senior-level-tests">this article</a> that teaches you 12 evergreen testing principles that every senior developer should know.</p><h3>React Testing Library</h3><p>You also want to write tests for your React components. For that, you can use the <a href="https://testing-library.com/docs/react-testing-library/intro/">React Testing Library</a>, often abbreviated as RTL.</p><pre>npm install --save-dev @testing-library/react @testing-library/dom @testing-library/jest-dom @testing-library/user-event @types/react @types/react-dom happy-dom @vitejs/plugin-react vite-tsconfig-paths</pre><p>Then, create a vitest.config.ts file.</p><pre>import react from &#39;@vitejs/plugin-react&#39;;<br>import tsconfigPaths from &#39;vite-tsconfig-paths&#39;;<br>import { defineConfig } from &#39;vitest/config&#39;;<br><br>export default defineConfig({<br>  plugins: [react(), tsconfigPaths()],<br>  server: {<br>    port: 3000,<br>  },<br>  test: {<br>    environment: &#39;happy-dom&#39;,<br>    globals: true,<br>    setupFiles: [&#39;./src/tests/setup-test-environment.ts&#39;],<br>    include: [&#39;./src/**/*.{spec,test}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}&#39;],<br>    watch: {<br>      ignored: [<br>        String.raw`.*\\/node_modules\\/.*`,<br>        String.raw`.*\\/build\\/.*`,<br>        String.raw`.*\\/postgres-data\\/.*`,<br>      ],<br>    },<br>    coverage: {<br>      reporter: [&#39;text&#39;, &#39;json&#39;, &#39;html&#39;],<br>    },<br>  },<br>});</pre><p>It sets up the development server, specifies testing parameters like that the environment is happy-dom and file paths, and defines coverage report formats.</p><p>And create a file called src/tests/setup-test-environment.ts to set up your test environment.</p><pre>import &#39;@testing-library/jest-dom/vitest&#39;;<br><br>// See &lt;https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#configuring-your-testing-environment&gt;.<br><br>// @ts-ignore<br>globalThis.IS_REACT_ACT_ENVIRONMENT = true;</pre><p>The first line imports additional assertions, which extend Vitest’s built-in assertions, allowing you to more easily test DOM nodes. For example, you can check if an element is visible, has a certain text content, or includes specific attributes.</p><p>The global environment flag ensures that tests involving state updates work as expected without timing issues.</p><p>Next, you want to set up a custom render method src/tests/react-test-utils.tsx.</p><pre>/* eslint-disable import/export */<br>import type { RenderOptions } from &#39;@testing-library/react&#39;;<br>import { render } from &#39;@testing-library/react&#39;;<br>import type { ReactElement } from &#39;react&#39;;<br><br>const customRender = (<br>  ui: ReactElement,<br>  options?: Omit&lt;RenderOptions, &#39;queries&#39;&gt;,<br>) =&gt;<br>  render(ui, {<br>    wrapper: ({ children }) =&gt; &lt;&gt;{children}&lt;/&gt;,<br>    ...options,<br>  });<br><br>// re-export everything<br>export * from &#39;@testing-library/react&#39;;<br><br>// override render method<br>export { customRender as render };<br>export { default as userEvent } from &#39;@testing-library/user-event&#39;;</pre><p>You can use this to wrap your code in providers, for example, when using Redux, or adding layouts and styling.</p><p>Make sure your configuration worked by writing a test for a React component.</p><pre>import { describe, expect, test } from &#39;vitest&#39;;<br><br>import { render, screen } from &#39;@/tests/react-test-utils&#39;;<br><br>function MyReactComponent() {<br>  return &lt;div&gt;My React Component&lt;/div&gt;;<br>}<br><br>describe(&#39;MyReactComponent&#39;, () =&gt; {<br>  test(&#39;given no props: renders a text&#39;, () =&gt; {<br>    render(&lt;MyReactComponent /&gt;);<br><br>    expect(screen.getByText(&#39;My React Component&#39;)).toBeInTheDocument();<br>  });<br>});</pre><p>You import render and screen from your test utils file instead of from RTL directly.</p><p>toBeInTheDocument() is one of those special assertions that you configured earlier in your test environment setup file.</p><p>It should pass, too.</p><pre> ✓ src/features/example.test.tsx (1)<br>   ✓ MyReactComponent (1)<br>     ✓ given no props: renders a text<br><br> Test Files  1 passed (1)<br>      Tests  1 passed (1)<br>   Start at  09:33:32<br>   Duration  402ms (transform 24ms, setup 55ms, collect 87ms, tests 9ms, environment 80ms, prepare 45ms)<br><br><br> PASS  Waiting for file changes...<br>       press h to show help, press q to quit</pre><h3>Styling</h3><p>Now let’s talk about styling. The two most important aspects to get right are <strong>accessibility</strong> and <strong>maintainability</strong>. One of the most popular libraries out there that nails both of these aspects is <a href="https://ui.shadcn.com/">Shadcn</a>. It uses <a href="https://tailwindcss.com/">Tailwind</a> for streamlined style management and <a href="https://www.radix-ui.com/">Radix</a> for enhanced accessibility.</p><p>Initialize Shadcn in your project.</p><pre>$ npx shadcn@latest init<br><br>✔ **Which** **style** **would you like to use?** › New York<br>✔ **Which color would you like to use as** **base color****?** › Slate<br>✔ **Would you like to use** **CSS variables** **for colors?** … no / yes<br><br>✔ Writing components.json...<br>✔ Initializing project...<br>✔ Installing dependencies...<br><br>Success! Project initialization completed. You may now add components.</pre><p>$ npx shadcn@latest init</p><p>✔ **Which** **style** **would you like to use?** › New York<br>✔ **Which color would you like to use as** **base color****?** › Slate<br>✔ **Would you like to use** **CSS variables** **for colors?** … no / yes</p><p>✔ Writing components.json…<br>✔ Initializing project…<br>✔ Installing dependencies…</p><p>Success! Project initialization completed. You may now add components.</p><pre>npx shadcn@latest add card</pre><h3>Internationalization</h3><p>As your app scales, you want to translate it to multiple languages, so it can reach more users.</p><p>And adding internationalization — or i18n — in a later cycle of an app, can be a pain because you’ll have to find all hardcoded strings and replace them with translation function calls.</p><p>Install negotiator and a locale matcher.</p><pre>npm install negotiator @formatjs/intl-localematcher</pre><p>The @formatjs/intl-localematcher package selects the best language for your app&#39;s content based on a user&#39;s language preferences. The negotiator package helps your app to figure out what type of content (like language or format) your user&#39;s browser can handle best, based on the information the browser sends.</p><p>You’ll also need to install the TypeScript types for the negotiator package.</p><pre>npm install --save-dev @types/negotiator</pre><p>Then, create your i18n config in a new file at @src/features.internationalization/il8n-config.ts.</p><pre>import { match } from &#39;@formatjs/intl-localematcher&#39;;<br>import Negotiator from &#39;negotiator&#39;;<br>import { type NextRequest, NextResponse } from &#39;next/server&#39;;<br><br>import { i18n } from &#39;./i18n-config&#39;;<br><br>function getLocale(request: NextRequest) {<br>  const headers = {<br>    &#39;accept-language&#39;: request.headers.get(&#39;accept-language&#39;) ?? &#39;&#39;,<br>  };<br>  const languages = new Negotiator({ headers }).languages();<br>  return match(languages, i18n.locales, i18n.defaultLocale);<br>}<br><br>export function localizationMiddleware(request: NextRequest) {<br>  const { pathname } = request.nextUrl;<br>  const pathnameHasLocale = i18n.locales.some(<br>    locale =&gt; pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`,<br>  );<br><br>  if (pathnameHasLocale) {<br>    return;<br>  }<br><br>  const locale = getLocale(request);<br>  request.nextUrl.pathname = `/${locale}${pathname}`;<br>  return NextResponse.redirect(request.nextUrl);<br>}</pre><p>Use that il8n config, in your localization-middleware at src/features/internationalization/localization-middleware.ts .</p><pre>import { match } from &#39;@formatjs/intl-localematcher&#39;;<br>import Negotiator from &#39;negotiator&#39;;<br>import { type NextRequest, NextResponse } from &#39;next/server&#39;;<br><br>import { i18n } from &#39;./i18n-config&#39;;<br><br>function getLocale(request: NextRequest) {<br>  const headers = {<br>    &#39;accept-language&#39;: request.headers.get(&#39;accept-language&#39;) ?? &#39;&#39;,<br>  };<br>  const languages = new Negotiator({ headers }).languages();<br>  return match(languages, i18n.locales, i18n.defaultLocale);<br>}<br><br>export function localizationMiddleware(request: NextRequest) {<br>  const { pathname } = request.nextUrl;<br>  const pathnameHasLocale = i18n.locales.some(<br>    locale =&gt; pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`,<br>  );<br><br>  if (pathnameHasLocale) {<br>    return;<br>  }<br><br>  const locale = getLocale(request);<br>  request.nextUrl.pathname = `/${locale}${pathname}`;<br>  return NextResponse.redirect(request.nextUrl);<br>}</pre><p>The purpose of this middleware is to automatically detect and redirect your users to the appropriate language version of the website based on their browser language preferences.</p><p>Then create a middleware file src/middleware.ts in the root of your project, and use your localization middleware there.</p><pre>import { NextRequest } from &#39;next/server&#39;;<br><br>import { localizationMiddleware } from &#39;./features/internationalization/localization-middleware&#39;;<br><br>// Matcher ignoring `/_next/` and `/api/` and svg files.<br>export const config = { matcher: [&#39;/((?!api|_next|.*.svg$).*)&#39;] };<br><br>export function middleware(request: NextRequest) {<br>  return localizationMiddleware(request);<br>}</pre><p>It’s time to add your translations. Create a json dictionary for your English translations at src/features/internalization/dictionaries/en-us.json.</p><pre>{<br>  &quot;counter&quot;: {<br>    &quot;decrement&quot;: &quot;Decrement&quot;,<br>    &quot;increment&quot;: &quot;Increment&quot;<br>  },<br>  &quot;landing&quot;: {<br>    &quot;welcome&quot;: &quot;Welcome&quot;<br>  }<br>}</pre><p>And then, create a file src/features/internalization/get-dictionaries.ts for your getDictionary function.</p><pre>import &quot;server-only&quot;;<br>import type { Locale } from &quot;./i18n-config&quot;;<br><br>// We enumerate all dictionaries here for better linting and typescript support.<br>// We also get the default import for cleaner types.<br>const dictionaries = {<br>  &quot;en-US&quot;: () =&gt; import(&quot;./dictionaries/en-US.json&quot;).then((module) =&gt; module.default),<br>};<br><br>export const getDictionary = async (locale: Locale) =&gt;<br>  dictionaries[locale]?.() ?? dictionaries[&quot;en-US&quot;]();</pre><p>It takes in a locale and then returns the respective dictionary.</p><p>If you want to add a hook that lets your users pick their language, it can look like this.</p><pre>import { usePathname } from &#39;next/navigation&#39;;<br><br>import { Locale } from &#39;./i18n-config&#39;;<br><br>export function useSwitchLocaleHref() {<br>  const pathName = usePathname();<br><br>  const getSwitchLocaleHref = (locale: Locale) =&gt; {<br>    if (!pathName) return &#39;/&#39;;<br>    const segments = pathName.split(&#39;/&#39;);<br>    segments[1] = locale;<br>    return segments.join(&#39;/&#39;);<br>  };<br><br>  return getSwitchLocaleHref;<br>}</pre><p>And then you can use that hook in a component, and use it’s return value in a &lt;Link/&gt; like this.</p><pre>&lt;Link href={getSwitchLocaleHref(locale)}&gt;{locale}&lt;/Link&gt;</pre><p>Change your URL structure to support i18n. Create a new folder [lang] in your app/ folder, which creates a dynamic segment for the language.</p><p>Move your page.tsx and layout.tsx files into that folder, and modify the layout to set the correct language on the &lt;html /&gt; tag.</p><pre>import &#39;../globals.css&#39;;<br><br>import type { Metadata } from &#39;next&#39;;<br>import { Inter } from &#39;next/font/google&#39;;<br><br>import { Locale } from &#39;@/features/internationalization/i18n-config&#39;;<br><br>const inter = Inter({ subsets: [&#39;latin&#39;] });<br><br>export const metadata: Metadata = {<br>  title: &quot;Jan Hesters&#39; Next.js for production tutorial&quot;,<br>  description: &#39;Brought to you by ReactSquad.io&#39;,<br>};<br><br>export default function RootLayout({<br>  children,<br>  params,<br>}: Readonly&lt;{<br>  children: React.ReactNode;<br>  params: { lang: Locale };<br>}&gt;) {<br>  return (<br>    &lt;html lang={params.lang}&gt;<br>      &lt;body className={inter.className}&gt;{children}&lt;/body&gt;<br>    &lt;/html&gt;<br>  );<br>}</pre><p>Going forward, you can use the getDictionary function in any server component.</p><pre>import { getDictionary } from &#39;@/features/internationalization/get-dictionaries&#39;;<br>import { Locale } from &#39;@/features/internationalization/i18n-config&#39;;<br><br>import { CounterComponent } from &#39;./counter-component&#39;;<br><br>export default async function IndexPage({<br>  params: { lang },<br>}: {<br>  params: { lang: Locale };<br>}) {<br>  const dictionary = await getDictionary(lang);<br><br>  return (<br>    &lt;div&gt;<br>      &lt;p&gt;Current locale: {lang}&lt;/p&gt;<br>      &lt;p&gt;This text is rendered on the server: {dictionary.landing.welcome}&lt;/p&gt;<br>      &lt;CounterComponent dictionary={dictionary.counter} /&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>For client components, you’ll want to pass in the respective dictionary instead.</p><pre>&#39;use client&#39;;<br><br>import { useState } from &#39;react&#39;;<br><br>import type { getDictionary } from &#39;@/features/internationalization/get-dictionaries&#39;;<br><br>export function CounterComponent({<br>  dictionary,<br>}: {<br>  dictionary: Awaited&lt;ReturnType&lt;typeof getDictionary&gt;&gt;[&#39;counter&#39;];<br>}) {<br>  const [count, setCount] = useState(0);<br><br>  return (<br>    &lt;p&gt;<br>      This component is rendered on the client:<br>      &lt;button onClick={() =&gt; setCount(n =&gt; n - 1)}&gt;<br>        {dictionary.decrement}<br>      &lt;/button&gt;<br>      {count}<br>      &lt;button onClick={() =&gt; setCount(n =&gt; n + 1)}&gt;<br>        {dictionary.increment}<br>      &lt;/button&gt;<br>    &lt;/p&gt;<br>  );<br>}</pre><pre>&#39;use client&#39;;<br><br>import { useState } from &#39;react&#39;;<br><br>import type { getDictionary } from &#39;@/features/internationalization/get-dictionaries&#39;;<br><br>export function CounterComponent({<br>  dictionary,<br>}: {<br>  dictionary: Awaited&lt;ReturnType&lt;typeof getDictionary&gt;&gt;[&#39;counter&#39;];<br>}) {<br>  const [count, setCount] = useState(0);<br><br>  return (<br>    &lt;p&gt;<br>      This component is rendered on the client:<br>      &lt;button onClick={() =&gt; setCount(n =&gt; n - 1)}&gt;<br>        {dictionary.decrement}<br>      &lt;/button&gt;<br>      {count}<br>      &lt;button onClick={() =&gt; setCount(n =&gt; n + 1)}&gt;<br>        {dictionary.increment}<br>      &lt;/button&gt;<br>    &lt;/p&gt;<br>  );<br>}</pre><h3>Database</h3><p>This tutorial is going to use <a href="https://www.postgresql.org/">Postgres</a> for its database because it is well battle-tested, but you’re going to use the <a href="https://www.prisma.io/">Prisma ORM</a> to abstract away the database layer. This gives you the flexibility to use a variety of databases, and simplifies the API that you use to interact with it.</p><pre>npm install prisma --save-dev</pre><p>You’ll also need to install the Prisma client.</p><pre>npm install @prisma/client</pre><p>Initialize Prisma.</p><pre>npx prisma init</pre><p>This auto-generates yourprisma/schema.prisma file.</p><pre>generator client {<br>  provider = &quot;prisma-client-js&quot;<br>}<br><br>datasource db {<br>  provider = &quot;postgresql&quot;<br>  // Uses connection pooling<br>  url = env(&quot;DATABASE_URL&quot;)<br>}<br><br>model UserProfile {<br>  id                         String   @id @default(cuid())<br>  createdAt                  DateTime @default(now())<br>  updatedAt                  DateTime @updatedAt<br>  email                      String   @unique<br>  name                       String   @default(&quot;&quot;)<br>  acceptedTermsAndConditions Boolean  @default(false)<br>}</pre><p>Add a aUserProfile model to it, which has an email and a name and a boolean whether they accepted your terms and conditions.</p><p>If you ran npx prisma init, rename your .env file to .env.local, otherwise create it and make sure it contains the credentials for your Prisma database.</p><pre>DATABASE_URL=&quot;postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public&quot;</pre><p>Now create a src/lib/prisma.ts file, which will contain your Prisma client connection.</p><pre>import { PrismaClient } from &#39;@prisma/client&#39;;<br><br>declare global {<br>  var __database__: PrismaClient;<br>}<br><br>let prisma: PrismaClient;<br><br>if (process.env.NODE_ENV === &#39;production&#39;) {<br>  prisma = new PrismaClient();<br>} else {<br>  if (!global.__database__) {<br>    global.__database__ = new PrismaClient();<br>  }<br>  prisma = global.__database__;<br>}<br><br>export default prisma;</pre><p>The re-assignment ensures that in a non-production environment (like during development or testing), only one instance of the Prisma client is created and reused across the entire application. This approach prevents the overhead of repeatedly initializing new connections to the database every time a module that needs database access is imported.</p><p>Modify your package.json to include the following helper commands for Prisma.</p><pre>&quot;prisma:deploy&quot;: &quot;npx prisma migrate deploy &amp;&amp; npx prisma generate&quot;,<br>&quot;prisma:migrate&quot;: &quot;npx prisma migrate dev --name&quot;,<br>&quot;prisma:push&quot;: &quot;npx prisma db push &amp;&amp; npx prisma generate&quot;,<br>&quot;prisma:reset-dev&quot;: &quot;run-s prisma:wipe prisma:seed dev&quot;,<br>&quot;prisma:seed&quot;: &quot;tsx ./prisma/seed.ts&quot;,<br>&quot;prisma:setup&quot;: &quot;prisma generate &amp;&amp; prisma migrate deploy &amp;&amp; prisma db push&quot;,<br>&quot;prisma:studio&quot;: &quot;npx prisma studio&quot;,<br>&quot;prisma:wipe&quot;: &quot;npx prisma migrate reset --force &amp;&amp; npx prisma db push&quot;,</pre><p>Some of these commands use the run-s, tsxand dotenvpackages, which you’ll need to install.</p><pre>npm install --save-dev npm-run-all tsx dotenv</pre><p>Here’s an explanation of each Prisma command:</p><ul><li><strong>“prisma:deploy”</strong>: Deploys database migrations and generates Prisma Client. It runs migrations on production databases and updates the client API.</li><li><strong>“prisma:migrate”</strong>: Creates a new migration from changes in the Prisma schema, applying it in the development environment. You must specify a name for the migration after the command.</li><li><strong>“prisma:push”</strong>: Pushes the schema changes directly to the database and updates the Prisma Client. Useful for prototyping without creating migration files.</li><li><strong>“prisma:reset-dev”</strong>: Resets the development database by wiping it, re-seeding it, and applying migrations in development mode.</li><li><strong>“prisma:seed”</strong>: Runs a TypeScript seed script to populate the database with initial data.</li><li><strong>“prisma:setup”</strong>: Generates the Prisma Client, deploys migrations to the database, and pushes the schema to the database.</li><li><strong>“prisma:studio”</strong>: Opens Prisma Studio, a GUI for viewing and editing the database records.</li><li><strong>“prisma:wipe”</strong>: Resets the database by forcefully dropping all data and migrations and then pushes the schema changes.</li></ul><pre>npm run prisma:setup</pre><p>If your Prisma doesn’t recognize your .env.local, set your environment variable manually in your terminal. On Mac, this can be done using theexport command.</p><pre>export DATABASE_URL=&quot;postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public&quot;</pre><p>Create a primsa/seed.ts. You can use it to seed your database with data for development.</p><pre>import { exit } from &#39;node:process&#39;;<br><br>import { PrismaClient } from &#39;@prisma/client&#39;;<br>import dotenv from &#39;dotenv&#39;;<br><br>dotenv.config({ path: &#39;.env.local&#39; });<br><br>const prisma = new PrismaClient();<br><br>const prettyPrint = (object: any) =&gt;<br>  console.log(JSON.stringify(object, undefined, 2));<br><br>async function seed() {<br>  const user = await prisma.userProfile.create({<br>    data: {<br>      email: &#39;jan@reactsquad.io&#39;,<br>      name: &#39;Jan Hesters&#39;,<br>      acceptedTermsAndConditions: true,<br>    },<br>  });<br><br>  console.log(&#39;========= 🌱 result of seed: =========&#39;);<br>  prettyPrint({ user });<br>}<br><br>seed()<br>  .then(async () =&gt; {<br>    await prisma.$disconnect();<br>  })<br>  // eslint-disable-next-line unicorn/prefer-top-level-await<br>  .catch(async error =&gt; {<br>    console.error(error);<br>    await prisma.$disconnect();<br>    exit(1);<br>  });</pre><p>If you run it, it’ll create your user.</p><pre>$ npm run prisma:seed<br><br>&gt; reactsquad-production@0.1.0 prisma:seed<br>&gt; tsx ./prisma/seed.ts<br><br>========= 🌱 result of seed: =========<br>{<br>  &quot;user&quot;: {<br>    &quot;id&quot;: &quot;clzekb5sp0000ock9gsp72p33&quot;,<br>    &quot;createdAt&quot;: &quot;2024-08-03T20:04:27.289Z&quot;,<br>    &quot;updatedAt&quot;: &quot;2024-08-03T20:04:27.289Z&quot;,<br>    &quot;email&quot;: &quot;jan@reactsquad.io&quot;,<br>    &quot;name&quot;: &quot;Jan Hesters&quot;,<br>    &quot;acceptedTermsAndConditions&quot;: true<br>  }<br>}</pre><p>In your server components, you can use prisma to fetch any data.</p><pre>import prisma from &#39;@/lib/prisma&#39;;<br>import {<br>  Card,<br>  CardContent,<br>  CardHeader,<br>  CardTitle,<br>} from &#39;@/components/ui/card&#39;;<br><br>export default async function Dashboard() {<br>  const user = await prisma.userProfile.findUnique({<br>    where: { email: &#39;jan@reactsquad.io&#39; },<br>  });<br><br>  return (<br>    &lt;Card className=&quot;max-w-md mx-auto&quot;&gt;<br>      &lt;CardHeader&gt;<br>        &lt;CardTitle&gt;User Profile&lt;/Card.Title&gt;<br>      &lt;/CardHeader&gt;<br>      &lt;CardContent&gt;<br>        {user ? (<br>          &lt;ul&gt;<br>            &lt;li&gt;Name: {user.name}&lt;/li&gt;<br>            &lt;li&gt;Email: {user.email}&lt;/li&gt;<br>          &lt;/ul&gt;<br>        ) : (<br>          &lt;p&gt;User not found.&lt;/p&gt;<br>        )}<br>      &lt;/CardContent&gt;<br>    &lt;/Card&gt;<br>  );<br>}</pre><h3>Use Facades</h3><p>It’s a good idea to abstract away your database calls using facades. A facade is a design pattern where you <strong>provide a simplified interface to a complex subsystem</strong>.</p><p>Create a file that contains all facades related to your user profile model features/user-profile/user-profile-model.ts.</p><pre>import { UserProfile } from &#39;@prisma/client&#39;;<br><br>import prisma from &#39;@/lib/prisma&#39;;<br><br>export async function retrieveUserProfileFromDatabaseByEmail(<br>  email: UserProfile[&#39;email&#39;],<br>) {<br>  return await prisma.userProfile.findUnique({ where: { email } });<br>}</pre><p>There are two main reasons for using facades.</p><ol><li><strong>Increase vendor resistance </strong>— You can switch third-party providers easily. For example, you might switch from Firebase to Supabase or vice versa. Instead of updating your whole codebase to reflect the change, you only update the facade.</li><li><strong>Simplify your code</strong> — A facade can reduce the code you need to write in your application because it reduces the API to your specific application needs. Simultaneously, it makes your code easier to understand because you can give the facade descriptive names.</li></ol><p>Then use your facade in your server component.</p><pre>import { retrieveUserProfileFromDatabaseByEmail } from &#39;@/features/user-profiles/user-profiles-model&#39;;<br>import {<br>  Card,<br>  CardContent,<br>  CardHeader,<br>  CardTitle,<br>} from &#39;@/components/ui/card&#39;;<br><br>export default async function Dashboard() {<br>  const user =<br>    await retrieveUserProfileFromDatabaseByEmail(&#39;jan@reactsquad.io&#39;);<br><br>  return (<br>    &lt;Card className=&quot;max-w-md mx-auto&quot;&gt;<br>      &lt;CardHeader&gt;<br>        &lt;CardTitle&gt;User Profile&lt;/Card.Title&gt;<br>      &lt;/CardHeader&gt;<br><br>      &lt;CardContent&gt;<br>        {user ? (<br>          &lt;ul&gt;<br>            &lt;li&gt;Name: {user.name}&lt;/li&gt;<br>            &lt;li&gt;Email: {user.email}&lt;/li&gt;<br>          &lt;/ul&gt;<br>        ) : (<br>          &lt;p&gt;User not found.&lt;/p&gt;<br>        )}<br>      &lt;/CardContent&gt;<br>    &lt;/Card&gt;<br>  );<br>}</pre><h3>Vercel Postgres</h3><p>You can use Vercel Postgres for your production deployment. They have an easy to follow guide, which you can <a href="https://vercel.com/docs/storage/vercel-postgres">check out in the Vercel documentation</a>. But here are the steps real quick for your convenience.</p><p>To set up a database in your Vercel project, follow these steps:</p><ol><li>Go to the <strong>Storage</strong> tab and click the <strong>Create Database</strong> button.</li><li>When the <strong>Browse Storage</strong> modal opens, choose <strong>Postgres</strong> and click <strong>Continue</strong>.</li></ol><p>For creating a new database:</p><ol><li>In the dialog box, type sample_postgres_db (or a name you prefer) in the <strong>Store Name</strong> field. Ensure the name has only alphanumeric characters, &quot;_&quot; or &quot;-&quot;, and does not exceed 32 characters.</li><li>Choose a region. For lower latency, select a region close to your function region, which is US East by default.</li><li>Click <strong>Create</strong>.</li></ol><p>Then you’ll need to add POSTGRES_URL_NON_POOLING to the datasource in your Prisma schema.</p><pre>datasource db {<br>  provider  = &quot;postgresql&quot;<br>  // Uses connection pooling<br>  url       = env(&quot;DATABASE_URL&quot;)<br>  // Uses direct connection, ⚠️ make sure to keep this to `POSTGRES_URL_NON_POOLING`<br>  // or you&#39;ll have dangling databases from migrations<br>  directUrl = env(&quot;POSTGRES_URL_NON_POOLING&quot;)<br>}</pre><p>Vercel uses connection poolers, which manage a pool of database connections that can be reused by different parts of an application, rather than establishing a new connection for each database request. The directUrl property is used to ensure operations requiring direct database access, such as migrations, can bypass the connection pooler for reliable execution.</p><p>You can get the environment variables for your Vercel database by pulling them from Vercel.</p><pre>vercel env pull .env</pre><h3>Playwright</h3><p>You also want to use E2E tests because E2E tests give you the most confidence that your app works as intended. E2E tests are skipped far too often, but you really should make it a habit to write them. The benefits are compounding.</p><p>Initialize <a href="https://playwright.dev/">Playwright</a>.</p><pre>$ npm init playwright@latest<br>Getting started with writing end-to-end tests with Playwright:<br>Initializing project in &#39;.&#39;<br>✔ Where to put your end-to-end tests? · playwright<br>✔ Add a GitHub Actions workflow? (y/N) · false<br>✔ Install Playwright browsers (can be done manually via &#39;npx playwright install&#39;)? (Y/n) · true</pre><p>If this is your first time using Playwright, you can have a look through the test-examples/ folder that the initialization script creates, and then deleted it because you won&#39;t need it.</p><p>Modify the webServer key in your playwright.config.ts file.</p><pre>webServer: {<br>  command: process.env.CI ? &#39;npm run build &amp;&amp; npm run start&#39; : &#39;npm run dev&#39;,<br>  port: 3000,<br>},</pre><p>Add two scripts for your E2E tests to your package.json.</p><pre>&quot;test:e2e&quot;: &quot;npx playwright test&quot;,<br>&quot;test:e2e:ui&quot;: &quot;npx playwright test --ui&quot;,</pre><p>The first one runs your Playwright test in headless mode, while the second one runs your test in UI mode, which gives you time travel debugging, watch mode and more.</p><pre>import { expect, test } from &#39;@playwright/test&#39;;<br><br>test.describe(&#39;landing page&#39;, () =&gt; {<br>  test(&#39;given any user: shows the test user&#39;, async ({ page }) =&gt; {<br>    await page.goto(&#39;/&#39;);<br><br>    await expect(page.getByText(&#39;Jan Hesters&#39;)).toBeVisible();<br>    await expect(page.getByText(&#39;jan@reactsquad.io&#39;)).toBeVisible();<br>  });<br>});</pre><p>Run your tests to check that your Playwright setup worked.</p><pre>$ npm run test:e2e<br><br>&gt; reactsquad-production@0.1.0 test:e2e<br>&gt; npx playwright test<br><br><br>Running 3 tests using 3 workers<br>  3 passed (3.9s)</pre><p>It ran three tests because by default Playwright is configured to run in Chrome, Safari, and Firefox.</p><h3>GitHub Actions</h3><p>It’s good practice to run your app with CI/CD. CI/CD stands for continuous delivery and continuous deployment.</p><p>Add a secret for your database URL to your <a href="https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository">repository’s settings in GitHub</a>.</p><pre>DATABASE_URL=&quot;postgresql://postgres:postgres@localhost:5432/testdb&quot;</pre><p>Then create your GitHub Actions YAML configuration in a .github/workflows/pull-request.yml file for a comprehensive CI/CD pipeline including linting, type checking, testing, and more.</p><pre>name: Pull Request<br><br>on: [pull_request]<br><br>jobs:<br>  lint:<br>    name: ⬣ ESLint<br>    runs-on: ubuntu-latest<br>    steps:<br>      - name: ⬇️ Checkout repo<br>        uses: actions/checkout@v3<br><br>      - name: ⎔ Setup node<br>        uses: actions/setup-node@v3<br>        with:<br>          node-version: 20<br><br>      - name: 📥 Download deps<br>        uses: bahmutov/npm-install@v1<br><br>      - name: 🔬 Lint<br>        run: npm run lint<br><br>  type-check:<br>    name: ʦ TypeScript<br>    runs-on: ubuntu-latest<br>    steps:<br>      - name: ⬇️ Checkout repo<br>        uses: actions/checkout@v3<br><br>      - name: ⎔ Setup node<br>        uses: actions/setup-node@v3<br>        with:<br>          node-version: 20<br><br>      - name: 📥 Download deps<br>        uses: bahmutov/npm-install@v1<br><br>      - name: 🔎 Type check<br>        run: npm run type-check --if-present<br><br>  commitlint:<br>    name: ⚙️ commitlint<br>    runs-on: ubuntu-latest<br>    if: github.actor != &#39;dependabot[bot]&#39;<br>    env:<br>      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}<br>    steps:<br>      - name: ⬇️ Checkout repo<br>        uses: actions/checkout@v3<br>        with:<br>          fetch-depth: 0<br>      - name: ⚙️ commitlint<br>        uses: wagoid/commitlint-github-action@v4<br><br>  vitest:<br>    name: ⚡ Vitest<br>    runs-on: ubuntu-latest<br>    services:<br>      postgres:<br>        image: postgres:12<br>        env:<br>          POSTGRES_USER: postgres<br>          POSTGRES_PASSWORD: postgres<br>          POSTGRES_DB: testdb<br>        ports:<br>          - 5432:5432<br>    steps:<br>      - name: ⬇️ Checkout repo<br>        uses: actions/checkout@v3<br><br>      - name: ⎔ Setup node<br>        uses: actions/setup-node@v3<br>        with:<br>          node-version: 20<br><br>      - name: 📥 Download deps<br>        uses: bahmutov/npm-install@v1<br><br>      - name: 🛠 Setup Database<br>        run: npm run prisma:wipe<br>        env:<br>          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb<br><br>      - name: ⚡ Run vitest<br>        run: npm run test -- --coverage<br>        env:<br>          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb<br><br>  playwright:<br>    name: 🎭 Playwright<br>    runs-on: ubuntu-latest<br>    services:<br>      postgres:<br>        image: postgres:12<br>        env:<br>          POSTGRES_USER: postgres<br>          POSTGRES_PASSWORD: postgres<br>          POSTGRES_DB: testdb<br>        ports:<br>          - 5432:5432<br>    steps:<br>      - name: ⬇️ Checkout repo<br>        uses: actions/checkout@v3<br><br>      - name: ⎔ Setup node<br>        uses: actions/setup-node@v3<br>        with:<br>          node-version: 20<br><br>      - name: 📥 Download deps<br>        uses: bahmutov/npm-install@v1<br><br>      - name: 🌐 Install Playwright Browsers<br>        run: npx playwright install --with-deps<br><br>      - name: 🛠 Setup Database<br>        run: npm run prisma:wipe<br>        env:<br>          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb<br><br>      - name: 🎭 Playwright Run<br>        run: npx playwright test<br>        env:<br>          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb<br><br>      - name: 📸 Playwright Screenshots<br>        uses: actions/upload-artifact@v4<br>        if: failure()<br>        with:<br>          name: playwright-report<br>          path: playwright-report/<br>          retention-days: 30</pre><p>Now every time you make a pull request to your app, it automatically runs your tests to ensure it works, runs TypeScript type checks and lints it, so that everyone contributes code with the same formatting.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=347f542922b4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What is Memoization? (In JavaScript & TypeScript)]]></title>
            <link>https://medium.com/@jan.hesters/what-is-memoization-in-javascript-typescript-5ba2f53cb475?source=rss-bf465a0206e8------2</link>
            <guid isPermaLink="false">https://medium.com/p/5ba2f53cb475</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[coding]]></category>
            <dc:creator><![CDATA[Jan Hesters]]></dc:creator>
            <pubDate>Tue, 05 Nov 2024 09:38:11 GMT</pubDate>
            <atom:updated>2024-11-05T09:38:11.848Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3AoHpEyKVnIk5rbq63rQAg.jpeg" /></figure><p>Memoization and caching are foundational concepts in programming.</p><p>You want to understand memoization, so you can understand concepts that build upon memoization. For example, the memo higher-order component in React, useCallback and useMemo hooks in React, selectors in Redux and much more.</p><p>In this article, you’re going to get a detailed look into memoization with examples in JavaScript and TypeScript.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FKwXWI8pm0Vk%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DKwXWI8pm0Vk&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FKwXWI8pm0Vk%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/dba5e88df6ae12d50d227fc0a2f02a43/href">https://medium.com/media/dba5e88df6ae12d50d227fc0a2f02a43/href</a></iframe><h3>What Is Memoization?</h3><p><strong>Memoization</strong> is a way to <strong>speed up performance</strong> by <strong>reducing computations</strong>.</p><p>When you first calculate a result, you save it. If you need the result for the same arguments again, you use the saved result instead of recalculating.</p><p>And there are two ways that you can implement memoization:</p><ol><li>Implicit Caching</li><li>Decorator Functions</li></ol><h3>Implicit Caching</h3><p>Implicit caching is when you manually implement caching logic within functions.</p><p>Let’s say you have a simple add function and you want to memoize it.</p><pre>function add(a, b) {<br>  return a + b;<br>}</pre><p>You can rewrite this add function, so that it memoizes its results.</p><p>If you want to code along with this article, initialize a new npm project.</p><pre>npm init --y</pre><p>Then write a function called memoizeAdd().</p><pre>function memoizeAdd() {<br>  const cache = {};<br><br>  return function memoizedAdd(a, b) {<br>    const key = `${a},${b}`;<br><br>    if (key in cache) {<br>      return cache[key];<br>    } else {<br>      const result = a + b;<br><br>      cache[key] = result;<br><br>      return result;<br>    }<br>  };<br>}<br><br>const memoizedAdd = memoizeAdd();<br><br>console.log(memoizedAdd(3, 4));  // Computes and caches the result.<br>console.log(memoizedAdd(3, 4));  // Retrieves the result from cache.<br>console.log(memoizedAdd(5, 6));  // Computes and caches a new result.<br>console.log(memoizedAdd(3, 4));  // Still retrieves the result from cache.</pre><p>Let’s break down what’s happening here.</p><p>In memoizeAdd() you create a cache, which is typically a hash table or an object, where you store the results of function calls. Each entry uses the function&#39;s input as the key and the output as the value.</p><pre>const cache = {<br>  &#39;3,1&#39;: 4,<br>  &#39;9001,2012&#39;: 11_013,<br>};</pre><p>Next, you return the memoizedAdd function, which takes in two numbers a and b.</p><p>Each unique combination of arguments corresponds to a key.</p><p>Before executing the main logic, the function checks the cache using the current input as the key. If the input is in the cache, it returns the cached result.</p><p>If the input is missing in the cache, the function computes the result, stores it in the cache, and then returns the result.</p><p>When you call the memoizedAdd function, the result is stored together with its parameters. If you call it again with the same parameters, the result comes from the cache. If the parameters are new, the function computes the result again.</p><p>When you run this code, you get the following output.</p><pre>$ node implicit-caching.js<br>7<br>7<br>11<br>7</pre><h3>Decorator Functions For Memoization</h3><p>In languages that support higher-order functions, you can use decorators to add memoization without changing the function’s core logic.</p><p>Higher-order functions are functions that take in functions and return functions.</p><p>If this concept is new to you, you might want to read <a href="https://www.reactsquad.io/blog/functional-programming-in-javascript">“Unleash JavaScript’s Potential with Functional Programming”</a>. This article explains higher-order functions and much more from first principles.</p><p>Now, check out this simple example of a decorator function for memoization.</p><pre>function memoize(fn) {<br>  const cache = new Map();<br><br>  return function(...args) {<br>    const key = JSON.stringify(args);<br><br>    if (cache.has(key)) {<br>      return cache.get(key);<br>    }<br><br>    const result = fn.apply(this, args);<br>    cache.set(key, result);<br><br>    return result;<br>  };<br>}<br><br>function add(a, b) {<br>  return a + b;<br>}<br><br>const memoizedAdd = memoize(add);<br><br>console.log(memoizedAdd(3, 4));  // Calculates and caches the result.<br>console.log(memoizedAdd(3, 4));  // Returns the cached result.<br>console.log(memoizedAdd(5, 6));  // Computes and caches a new result.<br>console.log(memoizedAdd(3, 4));  // Still retrieves the result from cache.</pre><p>Let’s go through this code step by step, too.</p><p>First, you create a memoize function that takes another function, fn, as its argument.</p><p>It starts by initializing a cache using a Map. This cache will store the results of the function calls. A Map is used instead of an object because it can handle a wider variety of key types.</p><pre>const cache = new Map();<br><br>// Use numbers as key for cache.<br>map.set(&#39;[3,1]&#39;, 4);<br>// Use functions as key for cache.<br>map.set(&#39;[function increment(), function double()]&#39;, 42);</pre><p>The returned function accepts any number of arguments. It serializes these arguments into a string to use as a key for the cache.</p><p>Before executing the original function, the wrapper checks if the key already exists in the cache. If it does, the function returns the cached result.</p><p>If the key is missing in the cache, the wrapper calls the original function using fn.apply(this, args). It stores the result in the cache and returns it.</p><p>You can now memoize your add function by passing it to the memoize function.</p><p>The memoizedAdd function will work just like in the implicit caching example.</p><p>And running your code yields the same result.</p><pre>$ node decorator-function.js<br>7<br>7<br>11<br>7</pre><h3>Recursion And The Fibonacci Sequence</h3><p>The add function is computationally cheap.</p><p>To experience the benefits of memoization, you need to memoize an expensive function.</p><p>A classic example is the fibonacci function. Outside of memoization, learning about this function is useful because it&#39;s a common topic in coding interviews.</p><p>The Fibonacci sequence is a series of numbers where each number is the sum of the two preceding ones. It usually starts with 0 and 1. Here’s how it begins:</p><p>0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …</p><p>In mathematical terms, you can express this as:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/739/1*VtkmZOexdjd2w3a3F9-bgg.png" /></figure><pre>function fibonacci(n) {<br>  if (n &lt;= 1) {<br>    return n;<br>  }<br><br>  return fibonacci(n - 1) + fibonacci(n - 2);<br>}</pre><p>f n is 0 or 1, the function returns n directly. These are the first two numbers in the Fibonacci sequence.</p><p>For values of n greater than 1, the function calls itself twice: once for fibonacci(n - 1) and once for fibonacci(n - 2), and adds the results of these two calls.</p><p>Here’s a table showing how the function computes fibonacci(4):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ik4PmPAuxRfPFxjBUw5AXQ.png" /></figure><p>You can see that fibonacci(2) is calculated twice when calling fibonacci(4): once directly from fibonacci(4) and again from fibonacci(3).</p><p>The negative performance impacts of these redundant calculations compound as the input to the fibonacci function increases. You want to read this article until the end to learn how memoization can make this computation lightning fast.</p><h3>Stack Tracing</h3><p>Before memoizing your fibonacci function, add a name property to your memoized function. This makes stack tracing easier if your function throws an error.</p><pre>function memoize(fn) {<br>  const cache = new Map();<br><br>  function memoizedFunction(...args) {<br>    const key = JSON.stringify(args);<br><br>    if (cache.has(key)) {<br>      return cache.get(key);<br>    }<br><br>    const result = fn.apply(this, args);<br>    cache.set(key, result);<br><br>    return result;<br>  }<br><br>  Object.defineProperty(memoizedFunction, &#39;name&#39;, {<br>    value: `memoized_${fn.name}`,<br>    configurable: true<br>  });<br><br>  return memoizedFunction;<br>}</pre><p>Use Object.defineProperty to set the name property of the memoizedFunction to a more descriptive value, like memoized_${fn.name}, where fn.name is the name of the original function. By setting this property to be configurable, you allow further modifications or deletion of the property if necessary.</p><h3>Measuring Memoization Performance</h3><p>Now you’re ready to measure the impact of memoization. Memoize your fibonacci function and time how long it takes to calculate large numbers.</p><pre>function memoize(fn) {<br>  const cache = new Map();<br><br>  function memoizedFunction(...args) {<br>    const key = JSON.stringify(args);<br><br>    if (cache.has(key)) {<br>      return cache.get(key);<br>    }<br><br>    const result = fn.apply(this, args);<br>    cache.set(key, result);<br><br>    return result;<br>  }<br><br>  Object.defineProperty(memoizedFunction, &#39;name&#39;, {<br>    value: `memoized_${fn.name}`,<br>    configurable: true<br>  });<br><br>  return memoizedFunction;<br>}<br><br>function fibonacci(n) {<br>  if (n &lt;= 1) {<br>    return n;<br>  }<br><br>  return fibonacci(n - 1) + fibonacci(n - 2);<br>}<br><br>const memoizedFibonacci = memoize(fibonacci);<br><br>function measurePerformance(func, arg) {<br>  const startTime = process.hrtime.bigint();<br>  const result = func(arg);<br>  const endTime = process.hrtime.bigint();<br>  // Convert nanoseconds to milliseconds.<br>  const duration = (endTime - startTime) / BigInt(1000000);<br>  console.log(`${func.name}(${arg}) = ${result}, Time: ${duration}ms`);<br>}<br><br>const n = 42;<br><br>console.log(&quot;Starting performance measurement:&quot;);<br>measurePerformance(fibonacci, n);<br>measurePerformance(memoizedFibonacci, n);<br>// Second call to show caching effect.<br>measurePerformance(memoizedFibonacci, n);</pre><p>The measurePerformance function evaluates and reports the execution time of a given function by capturing start and end times with process.hrtime.bigint(), which provides precise timestamps in nanoseconds. After executing the function with a given argument, it calculates the duration in milliseconds by subtracting the start time from the end time and converting the result from nanoseconds. It then logs the function name, argument, output, and execution duration to the console.</p><p>Using measurePerformance, you can compare the performance of the standard Fibonacci function with the memoized version.</p><p>Here are the results for the performance measurements.</p><pre>$ node fibonacci.js<br>Starting performance measurement:<br>fibonacci(42) = 267914296, Time: 2405ms<br>memoized_fibonacci(42) = 267914296, Time: 2394ms<br>memoized_fibonacci(42) = 267914296, Time: 0ms</pre><p>As you can see, running the memoized version the second time is instant because the result is retrieved from the cache.</p><h3>Clearing Cache</h3><p>When you use memoization, you want to implement .clear() method to manage your application&#39;s resources and performance. Here&#39;s why:</p><ul><li><strong>Memory management</strong>: It frees up resources by allowing you to manually purge the cache.</li><li><strong>Data freshness</strong>: It ensures cached results stay accurate by removing outdated or incorrect data.</li><li><strong>Control over cache behavior</strong>: It gives you the ability to reset the cache in response to events or conditions that affect data processing.</li><li><strong>Testing and debugging</strong>: It allows you to operate your functions in a known state, without interference from cached data, which is important for reliable testing.</li><li><strong>Performance optimization</strong>: It maintains cache efficiency by allowing periodic resets to manage size and lookup costs.</li></ul><p>Now, add the ability to clear the cache.</p><pre>function memoize(fn) {<br>  const cache = new Map();<br><br>  function memoizedFunction(...args) {<br>    const key = JSON.stringify(args);<br><br>    if (cache.has(key)) {<br>      return cache.get(key);<br>    }<br><br>    const result = fn.apply(this, args);<br>    cache.set(key, result);<br><br>    return result;<br>  }<br><br>  memoizedFunction.clear = function clear() {<br>    cache.clear();<br>  };<br><br>  Object.defineProperty(memoizedFunction, &#39;name&#39;, {<br>    value: `memoized_${fn.name}`,<br>    configurable: true<br>  });<br><br>  return memoizedFunction;<br>}<br><br>function fibonacci(n) {<br>  if (n &lt;= 1) {<br>    return n;<br>  }<br><br>  return fibonacci(n - 1) + fibonacci(n - 2);<br>}<br><br>const memoizedFibonacci = memoize(fibonacci);<br><br>function measurePerformance(func, arg) {<br>  const startTime = process.hrtime.bigint();<br>  const result = func(arg);<br>  const endTime = process.hrtime.bigint();<br>  // Convert nanoseconds to milliseconds.<br>  const duration = (endTime - startTime) / BigInt(1000000);<br>  console.log(`${func.name}(${arg}) = ${result}, Time: ${duration}ms`);<br>}<br><br>const n = 42;<br><br>// Measure memoized Fibonacci.<br>measurePerformance(memoizedFibonacci, n);<br><br>// Measure memoized Fibonacci second call.<br>measurePerformance(memoizedFibonacci, n);<br><br>// Clear cache and measure again.<br>console.log(&#39;Clearing cache and measuring again:&#39;);<br>memoizedFibonacci.clear();<br>measurePerformance(memoizedFibonacci, n);</pre><p>Then modify your usage example to include a call of the .clear() method after the result has already been memoized successfully.</p><pre>$ node fibonacci.js<br>memoized_fibonacci(42) = 267914296, Time: 2456ms<br>memoized_fibonacci(42) = 267914296, Time: 0ms<br>Clearing cache and measuring again:<br>memoized_fibonacci(42) = 267914296, Time: 2484ms</pre><p>As you can see, clearing the cache causes the function to compute its result again.</p><h3>Memoization In TypeScript</h3><p>So far, all code has been written in JavaScript. Now you will translate the code examples into TypeScript.</p><p>Install TypeScript and the node types.</p><pre>$ npm i --save-dev typescript @types/node<br><br>added 3 packages, and audited 4 packages in 1s<br><br>found 0 vulnerabilities</pre><p>Then initialize a new TypeScript project.</p><pre>$  npx tsc --init<br><br><br>Created a new tsconfig.json with:<br>                                                                      TS<br>  target: es2016<br>  module: commonjs<br>  strict: true<br>  esModuleInterop: true<br>  skipLibCheck: true<br>  forceConsistentCasingInFileNames: true<br><br><br>You can learn more at &lt;https://aka.ms/tsconfig&gt;</pre><p>Now you can write your memoize function in TypeScript.</p><pre>type AnyFunction = (...args: any[]) =&gt; any;<br><br>interface MemoizedFunction&lt;T extends AnyFunction&gt; extends CallableFunction {<br>  (...args: Parameters&lt;T&gt;): ReturnType&lt;T&gt;;<br>  clear: () =&gt; void;<br>}<br><br>function memoize&lt;T extends AnyFunction&gt;(fn: T): MemoizedFunction&lt;T&gt; {<br>  const cache = new Map&lt;string, ReturnType&lt;T&gt;&gt;();<br><br>  const memoizedFunction = function(...args: Parameters&lt;T&gt;): ReturnType&lt;T&gt; {<br>    const key = JSON.stringify(args);<br><br>    if (cache.has(key)) {<br>      return cache.get(key)!;<br>    }<br><br>    const result = fn(...args);<br>    cache.set(key, result);<br><br>    return result;<br>  } as MemoizedFunction&lt;T&gt;;<br><br>  memoizedFunction.clear = function clear() {<br>    cache.clear();<br>  };<br><br>  Object.defineProperty(memoizedFunction, &#39;name&#39;, {<br>    value: `memoized_${fn.name}`,<br>    configurable: true<br>  });<br><br>  return memoizedFunction;<br>}<br><br>function fibonacci(n: number): number {<br>  if (n &lt;= 1) {<br>    return n;<br>  }<br><br>  return fibonacci(n - 1) + fibonacci(n - 2);<br>}<br><br>const memoizedFibonacci = memoize(fibonacci);<br><br>function measurePerformance(func: MemoizedFunction&lt;typeof fibonacci&gt;, arg: number) {<br>  const startTime = process.hrtime.bigint();<br>  const result = func(arg);<br>  const endTime = process.hrtime.bigint();<br>  // Convert nanoseconds to milliseconds.<br>  const duration = (endTime - startTime) / BigInt(1000000);<br>  console.log(`${func.name}(${arg}) = ${result}, Time: ${duration}ms`);<br>}<br><br>const n = 42;<br><br>// Measure memoized Fibonacci.<br>measurePerformance(memoizedFibonacci, n);<br><br>// Measure memoized Fibonacci second call.<br>measurePerformance(memoizedFibonacci, n);<br><br>// Clear cache and measure again.<br>memoizedFibonacci.clear();<br>measurePerformance(memoizedFibonacci, n);</pre><p>Here, you create an AnyFunction type, which represents a generic function type in TypeScript. This type is flexible and allows for any function that takes an arbitrary number of arguments of any type and returns any type.</p><p>Next, you define a generic MemoizedFunction interface that extends the built-in CallableFunction interface and introduces a clear method. Here, T is a generic type parameter constrained to any function that matches the AnyFunction type. The interface ensures that your memoized function behaves like the original function in terms of accepting the same parameters and returning the same type and additionally exposes the clear method.</p><p>You can now implement your memoize function so that it returns the MemoizedFunction interface. Using the as keyword, you can let TypeScript know that the returned function is a MemoizedFunction.</p><p>Now when you hover over memoizedFibonacci, TypeScript knows the correct type of the function.</p><pre>const memoizedFibonacci: MemoizedFunction&lt;(n: number) =&gt; number&gt;</pre><p>And TypeScript also knows about the .clear() method on the memoized function.</p><pre>(property) MemoizedFunction&lt;(n: number) =&gt; number&gt;.clear: () =&gt; void</pre><h3>Recursion With Memoization</h3><p>Your function still has a shortcoming. Currently, the memoization only caches the results of the full function call for each argument, but does <strong><em>NOT</em></strong> cache the results of the intermediate recursive calls.</p><p>What is a potential drawback of using recursion without memoization for solving certain problems?</p><p>It is the significant increase in computational time and resource usage due to redundant calculations.</p><p>Call the function with a big number n and then with n + 1.</p><pre>// ... previous code stays the same.<br><br>const n = 42;<br><br>measurePerformance(memoizedFibonacci, n);<br>measurePerformance(memoizedFibonacci, n + 1);</pre><p>Then run your code to see the difference in performance between 42 and 43.</p><pre>$ npx tsx example-5.ts<br>memoized_fibonacci(42) = 267914296, Time: 2442ms<br>memoized_fibonacci(43) = 433494437, Time: 3975ms</pre><p>As you can see, fibonacci(43) takes much longer because it recalculates fibonacci(42) from scratch, even though the result is already cached. The same problem occurs with every other intermediate recursive call.</p><p>You can fix this by using memoization in the implementation of the fibonacci function.</p><pre>// ... `memoize` is the same as above.<br><br>function fibonacci(n: number): number {<br>  if (n &lt;= 1) {<br>    return n;<br>  }<br><br>  return memoizedFibonacci(n - 1) + memoizedFibonacci(n - 2);<br>}<br><br>const memoizedFibonacci = memoize(fibonacci);<br><br>function measurePerformance(func: MemoizedFunction&lt;typeof fibonacci&gt;, arg: number) {<br>  const startTime = process.hrtime.bigint();<br>  const result = func(arg);<br>  const endTime = process.hrtime.bigint();<br>  // Convert nanoseconds to milliseconds.<br>  const duration = (endTime - startTime) / BigInt(1000000);<br>  console.log(`${func.name}(${arg}) = ${result}, Time: ${duration}ms`);<br>}<br><br>const numbers = [10, 20, 30, 40, 42, 43, 500];<br><br>numbers.forEach(n =&gt; {<br>  measurePerformance(memoizedFibonacci, n);<br>});</pre><p>When you define the fibonacci function, use its memoized version to calculate the next number.</p><p>Now, run the function on various large numbers.</p><pre>$ npx tsx fibonacci.ts<br>memoized_fibonacci(10) = 55, Time: 0ms<br>memoized_fibonacci(20) = 6765, Time: 0ms<br>memoized_fibonacci(30) = 832040, Time: 0ms<br>memoized_fibonacci(40) = 102334155, Time: 0ms<br>memoized_fibonacci(42) = 267914296, Time: 0ms<br>memoized_fibonacci(43) = 433494437, Time: 0ms<br>memoized_fibonacci(500) = 1.394232245616977e+104, Time: 0ms</pre><p>The calculation is now virtually instant for every function call no matter how large.</p><p>So when you memoize, ask yourself: “What do you want to memoize?” Do you want to memoize only the outer function, or do you want to also memoize intermediary results in the implementation?</p><h3>Why Memoize? (Use Cases)</h3><p>When do you want to use memoization?</p><p>Memoization is useful in:</p><ul><li><strong>Recursive algorithms</strong>: As shown with the Fibonacci sequence, recursive functions that repeat the same calculations can benefit greatly from memoization by avoiding redundant operations.</li><li><strong>Database queries</strong>: You can cache results from expensive database queries.</li><li><strong>Data fetching</strong>: In web development, memoization can cache responses from API calls. This reduces network traffic and speeds up response times by serving cached data for repeated requests with the same parameters.</li><li><strong>Data transformation</strong>: You can cache results of transformations that are costly in processing time.</li><li><strong>Preventing component rendering</strong>: In frontend frameworks like React, memoization can prevent unnecessary re-renders of components as long as their their props stay the same.</li></ul><h3>Memoization Trade-Offs</h3><p>As you’ve learned, memoization improves performance by reducing time complexity of operations and lowering the load on compute resources by avoiding repeated calculations.</p><p>But memoization also comes with drawbacks.</p><ul><li><strong>Memory usage</strong>: Memoization increases memory consumption by storing function call results, which can be a problem in environments with limited memory.</li><li><strong>Complexity</strong>: Adding memoization introduces complexity to your codebase, and requires careful cache management to avoid bugs and maintainability issues.</li><li><strong>Cache management</strong>: You must implement strategies for cache eviction and invalidation to prevent outdated or incorrect data, which can be challenging.</li><li><strong>side-effects</strong>: Functions with side-effects are can be problematic for memoization because the side-effects might need to occur with each function call. Memoization should primarily be used with pure functions.</li><li><strong>Concurrency issues</strong>: In multi-threaded applications, memoization must be handled carefully to avoid race conditions and ensure thread safety when accessing the cache.</li></ul><p><a href="https://youtube.com/@janhesters?sub_confirmation=1">Subscribe to my YouTube channel</a> for more great JavaScript and TypeScript tutorials!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5ba2f53cb475" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Unleash JavaScript’s Potential with Functional Programming]]></title>
            <link>https://medium.com/@jan.hesters/unleash-javascripts-potential-with-functional-programming-31fb4cffbed8?source=rss-bf465a0206e8------2</link>
            <guid isPermaLink="false">https://medium.com/p/31fb4cffbed8</guid>
            <category><![CDATA[coding-tutorial]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[developer]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Jan Hesters]]></dc:creator>
            <pubDate>Tue, 15 Oct 2024 13:50:59 GMT</pubDate>
            <atom:updated>2024-10-15T13:50:59.514Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1Aar312VBgTr5RA6QzE4Hw.jpeg" /></figure><p>Too many JavaScript developers have no idea what JavaScript is capable of.</p><p>This article will transform the way you write code and unleash your potential as a programmer.</p><p>By the end of this article and video, you will be able to read, understand and write code like this:</p><pre>const curry =<br> (f, array = []) =&gt;<br> (...args) =&gt;<br> (a =&gt; (a.length &gt;= f.length ? f(...a) : curry(f, a)))([<br> ...array,<br> ...args,<br> ]);<br>const add = curry((a, b) =&gt; a + b);<br>const inc = add(1);<br>const pipe = (...fns) =&gt; x =&gt; fns.reduce((y, f) =&gt; f(y), x);<br>const doubleInc = pipe(x =&gt; x * 2, inc);</pre><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fxw5SJZnTWp4%3Fstart%3D2006%26feature%3Doembed%26start%3D2006&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dxw5SJZnTWp4&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2Fxw5SJZnTWp4%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/4b611b7a5683f5d4d8963ac62b792dd7/href">https://medium.com/media/4b611b7a5683f5d4d8963ac62b792dd7/href</a></iframe><p>JavaScript is a two paradigm programming language because it supports OOP and FP. This article is your “step-by-step with no steps skipped” guide to functional programming, so you can use the language to its full capabilities and write more modular, deterministic and testable code. It literally goes from primitives all the way to advanced function composition.</p><h3>Definition</h3><p>If you look up functional programming you might get lost in academic lingo. If you’re lucky you will find some kind of simple definition like this:</p><blockquote><em>“</em><strong><em>Functional programming</em></strong><em> is the process of building software by composing functions. Functional programming is declarative rather than </em><strong><em>imperative</em></strong><em>. </em><strong><em>Side-effects</em></strong><em> are isolated. And application state usually flows through </em><strong><em>pure functions.</em></strong><em>”</em></blockquote><p>You can relax because this article will explain all of these terms and more. Starting at Primitives.</p><h3>Primitives</h3><p>Primitives are all datatypes that can hold only one value at a time. JavaScript has 7 primitive data types.</p><ul><li>string: Represents text.</li><li>number: Represents numerical values.</li><li>boolean: Represents true or false.</li><li>undefined: Indicates a variable has not been assigned a value.</li><li>null: Represents an intentional absence of any object value.</li><li>Symbol: Creates unique identifiers.</li><li>BigInt: Handles numbers larger than the standard number type can.</li></ul><pre>// primitives.js<br>const string = &quot;ReactSquad.io&quot;;<br>const number = 9001;<br>const boolean = true;<br>const notThere = undefined;<br>const weirdUndefined = null;<br>const noOneUsesThis = Symbol(&#39;🤯 &#39;);<br>const bigInt = 1n;</pre><h3>Composite data types</h3><p><strong>Composite data types</strong> can store collections or combinations of values.</p><ul><li><strong>Object:</strong> A structure that can hold multiple values as named properties.</li><li><strong>Array:</strong> A list-like structure that holds values in a specific order.</li><li><strong>Map:</strong> A collection of keyed data items, similar to an object but with keys of any type and maintains the order of insertions.</li><li><strong>Set:</strong> A collection of unique values. Like an array, but each value can only occur once.</li></ul><pre>// composite.js<br>const obj = { key: &quot;value&quot; };<br>const array = [1, 2, 3, 4, 5];</pre><p>A Map in JavaScript is a collection of keyed data items that maintains the order of insertions and allows keys of any type.</p><pre>// map.js<br>// Creating a new Map<br>const map = new Map();<br><br>// Setting key-value pairs in the Map<br>map.set(&#39;name&#39;, &#39;John&#39;);<br>map.set(&#39;age&#39;, 30);<br>map.set(1, &#39;one&#39;);<br><br>console.log(map);<br>// Output: Map(3) {&#39;name&#39; =&gt; &#39;John&#39;, &#39;age&#39; =&gt; 30, 1 =&gt; &#39;one&#39;}<br><br>// Retrieving a value by key.<br>console.log(map.get(&#39;name&#39;)); // Output: John<br><br>// Checking if a key exists.<br>console.log(map.has(&#39;age&#39;)); // Output: true<br><br>// Size of the Map.<br>console.log(map.size); // Output: 3<br>Unleash JavaScript&#39;s Potential With Functional Programming Article.md 2024-09-30<br>3 / 29<br><br>// Removing a key-value pair.<br>map.delete(1);<br><br>// Clearing all entries.<br>map.clear();</pre><p>A Set in JavaScript is a collection of unique values, ensuring that each value appears only once.</p><pre>// set.js<br>// Creating a new Set.<br>const set = new Set();<br><br>// Adding values to the Set.<br>set.add(&#39;apple&#39;);<br>set.add(&#39;banana&#39;);<br>set.add(&#39;apple&#39;); // This will not be added again.<br><br>console.log(set);<br>// Output: Set(2) {&#39;apple&#39;, &#39;banana&#39;}<br>// Reminder: Sets delete duplicates.<br><br>// Checking if a value exists.<br>console.log(set.has(&#39;banana&#39;)); // Output: true<br><br>// Size of the Set.<br>console.log(set.size); // Output: 2<br><br>// Deleting a value.<br>set.delete(&#39;apple&#39;);<br><br>// Iterating over Set values.<br>set.forEach(value =&gt; {<br> console.log(value);<br>});<br><br>// Clearing all values.<br>set.clear();</pre><h3>Functions</h3><pre>function sumDeclaration(a, b) {<br> return a + b;<br>}<br>const sumArrow = (a, b) =&gt; a + b;</pre><p>A function is a process that can take inputs, called <strong>parameters</strong>, and can produce some output called <strong>return value</strong>.</p><h3>Parameters vs. Arguments</h3><ul><li>Parameters are variables defined in the function declaration. They act as placeholders for the values that a function will operate on.</li><li>Arguments are the actual values or data passed to the function when it is called. These values replace the parameters during the function’s execution.</li><li>An application happens when the arguments are used to replace the function’s parameters. This allows the function to perform its task using the provided arguments.</li></ul><pre>function myFunction(parameter) {<br> return parameter;<br>}<br>const myArgument = &quot;Some value&quot;;<br>myFunction(myArgument); // Output: &quot;Some value&quot;;</pre><p>A function can be a mapping, a procedure or handle I/O operations.</p><ul><li><strong>Mapping:</strong> Produce some output based on given inputs. A function <strong>maps</strong> input values to output values. (So the examples you saw earlier are mappings.)</li></ul><pre>const square = x =&gt; x * x;<br>square(7); // 49</pre><ul><li><strong>Procedure:</strong> A function executes some steps in a sequence. The sequence is known as a procedure, and programming in this style is known as <strong>procedural programming.</strong></li></ul><pre>function prepareTea(teaType) {<br> let steps = [];<br> steps.push(&quot;Boil water&quot;);<br> steps.push(&quot;Add &quot; + teaType);<br> steps.push(&quot;Steep for 5 minutes&quot;);<br> steps.push(&quot;Serve hot&quot;);<br> return steps.join(&quot;\\n&quot;);<br>}<br><br>console.log(prepareTea(&quot;green tea&quot;));<br>// Output: Boil water<br>// Add green tea<br>// Steep for 5 minutes<br>// Serve hot<br><br>function calculateCircleArea(radius) {<br> if (radius &lt;= 0) {<br> return &quot;Error: Radius must be greater than zero.&quot;;<br> }<br><br> const pi = Math.PI; // Use Math.PI for more accuracy<br> const squaredRadius = radius * radius;<br> const area = pi * squaredRadius;<br> const roundedArea = Math.round(area * 100) / 100; // Round to 2 decimal<br>places<br> return &quot;Calculated area: &quot; + roundedArea;<br>}<br><br>console.log(calculateCircleArea(5));<br>// Output: Calculated area: 78.54<br><br>console.log(calculateCircleArea(-1));<br>// Output: Error: Radius must be greater than zero.</pre><ul><li><strong>I/O operations:</strong> I/O is short for input and output. Functions may handle interactions with system components like the screen, storage, logs, or network.</li></ul><pre>async function fetchData(url) {<br> const response = await fetch(url);<br> const data = await response.json();<br> return data;<br>}<br><br>fetchData(&#39;https://jsonplaceholder.typicode.com/todos/1&#39;)<br> .then(data =&gt; console.log(data));<br>// {<br>// &quot;userId&quot;: 1,<br>// &quot;id&quot;: 1,<br>// &quot;title&quot;: &quot;delectus aut autem&quot;,<br>// &quot;completed&quot;: false<br>// }</pre><h3>Methods</h3><p><strong>Methods</strong> are functions attached to objects. They allow you to perform operations on the object’s data. In JavaScript, methods are commonly used with objects, arrays, and other built-in types.</p><pre>const person = {<br> name: &#39;John&#39;,<br> greet: function() {<br> console.log(&#39;Hello, &#39; + this.name);<br> }<br>};<br><br>// Calling the greet method.<br>person.greet(); // Output: Hello, John</pre><p>Earlier in this article, you saw how to use methods of the Map and Set objects.</p><p>Primitives also have methods because under the hood everything in JavaScript is an object.</p><pre>const greeting = &#39;Hello, world&#39;;<br><br>// Changing case.<br>console.log(greeting.toUpperCase()); // Output: HELLO, WORLD<br><br>// Replacing part of the string.<br>console.log(greeting.replace(&#39;Hello&#39;, &#39;Goodbye&#39;)); // Output: Goodbye,<br>world<br><br>// Checking if it includes a substring.<br>console.log(greeting.includes(&#39;world&#39;)); // Output: true</pre><h3>Noop</h3><p>A “noop” stands for “no operation.” It describes a function, operation, or command that does nothing. This can be useful for placeholders in code or to intentionally have no effect.</p><p>In JavaScript, a noop function looks like this:</p><pre>function noop() {}<br>const noop = () =&gt; {};</pre><h3>Pure functions</h3><p>A function is a pure function, if:</p><ol><li>given the same inputs, it always returns the same output, and</li><li>it has no side-effects.</li></ol><p>You’ve seen pure functions already. For example, both sum functions and the square function are pure.</p><p>Pure functions are <strong>deterministic</strong>. This is captured by the first property. A pure function will always produce the same output for the same set of inputs, no matter <strong>when</strong> or <strong>how many times</strong> it is called. This predictability is a key property of pure functions and is essential for reliable and testable code.</p><p>Here is an example for a function that violates the first rule.</p><pre>/**<br> * Generates a random integer between the start and end values, both<br>inclusive.<br> *<br> * @param {number} start - The starting value.<br> * @param {number} end - The ending value.<br> * @returns {number} - A random integer between the start and end.<br> */<br>export const generateRandomInt = (start, end) =&gt;<br> Math.round(Math.random() * (end - start) + start);</pre><p>generateRandomInt can be called with the same start and end values, but it produces different results because it uses Math.random().</p><p>And here is a example, which violates the second rule.</p><pre>let externalArray = [];<br>function sideEffectingFunction(x) {<br> externalArray.push(x); // Modifies external array<br> return x;<br>}<br><br>console.log(sideEffectingFunction(5)); // 5, modifies externalArray<br>console.log(sideEffectingFunction(10)); // 10, modifies externalArray, too<br>console.log(externalArray); // Output: [5, 10]</pre><p>Even though sideEffectingFunction returns something, it pushes code as a side-effect to an array and you can meaningfully call it without using it&#39;s return value.</p><blockquote><em>“A dead giveaway that a function is impure is if it makes sense to call it without using its return value. For pure functions, that’s a noop.” — Eric Elliott</em></blockquote><h3>Idempotence</h3><p>Another concept you need to know is <strong>idempotence</strong>.</p><p><strong>Idempotence</strong> is a property of certain operations in which no matter how many times you perform the operation, the result remains the same after the first application. For example, setting a value to 5 is idempotent because no matter how often you do it, the value remains 5.</p><pre>let number = 5;<br>number = 5; // still 5<br>number = 5; // no change, still 5</pre><p>All pure functions are idempotent, but not all idempotent functions are pure functions. An idempotent function can cause idempotent side-effects. A pure function cannot. Deleting a record in a database by ID is idempotent, because the row of the table stays deleted after subsequent calls. Additional calls do nothing.</p><p>Here is a synchronous example.</p><pre>const uniqueItems = new Set();<br>// This custom addItem function is idempotent because ...<br>function addItem(item) {<br> uniqueItems.add(item);<br> console.log(`Added item: ${item}`);<br> return uniqueItems.size;<br>}<br><br>addItem(&quot;apple&quot;); // Outputs &quot;Added item: apple&quot;, returns 1<br>// ... calling addItem with the same item twice leaves the set unchanged.<br>addItem(&quot;apple&quot;); // Outputs &quot;Added item: apple&quot;, returns 1<br>addItem(&quot;banana&quot;); // Outputs &quot;Added item: banana&quot;, returns 2</pre><h3>Referential Transparency</h3><p>Idempotent functions without side-effects have a feature known as <strong>referential transparency.</strong></p><p>That means that if you have a function call:</p><pre>const result = square(7);</pre><p>You could replace that function call with the result of square(7) without changing the meaning of the program. So, for example if the result of square(7) is 49. Therefore, you could change the code above to:</p><pre>const result = 49;</pre><p>and your program would still work the same.</p><h3>Functional Programming Prerequisites</h3><p>A language needs three features to support functional programming, and JavaScript has all three:</p><ul><li>First-class functions (and therefore higher-order functions),</li><li>closures, and</li><li>anonymous functions and concise lambda syntax.</li></ul><h3>First class functions</h3><p>In JavaScript, functions are treated as first-class citizens. This means that functions can be stored in variables. Therefore, you can use functions as:</p><ul><li>arguments to other functions,</li><li>return values from functions,</li><li>values to an objectʼs keys</li></ul><pre>// Storing a function in a variable.<br>const greet = function() {<br> return &quot;Hello, World!&quot;;<br>}<br><br>// Passing a function as an argument (callback).<br>function shout(fn) {<br> const message = fn();<br> console.log(message.toUpperCase());<br>}<br><br>shout(greet); // Output: &quot;HELLO, WORLD!&quot;<br><br>// Returning a function from another function.<br>function multiply(multiplicand) {<br> return function (multiplier) {<br> return multiplicand * multiplier;<br> }<br>}<br><br>const double = multiply(2);<br>console.log(double(5)); // Output: 10</pre><p>If you want to learn what first-class functions enable in React check out this article about higher-order components, link is in the description below.</p><h3>Higher-order functions</h3><p>The last example multiply that you saw is called a &quot;higher-order function&quot; because when you call it for the first time with a number, it returns a function.</p><p>Any function that takes in or returns a function is called a higher order function.</p><pre>function greet() {<br> return &quot;Hello World!&quot;<br>}<br><br>const identity = x =&gt; x; // Same as myFunction from earlier ❗<br>identity(greet)(); // &quot;Hello World!&quot;</pre><h3>Closure</h3><p>The multiply function from earlier hid another key concept: Closure.</p><p>A closure happens when a function is bundled together with it’s lexical scope. In other words, a closure gives you access to an outer functionʼs scope from an inner function. Closures in JavaScript are created whenever a function is created, at function creation time.</p><p>To use a closure, define a function inside another function and expose it by returning the inner function or passing it to another function. The inner function can access variables in the outer function’s scope, even after the outer function has returned.</p><pre>function multiply(multiplicand) {<br> return function(multiplier) {<br> return multiplicand * multiplier;<br> }<br>}<br><br>// Multiplicand of 2 gets captured in the closure because the inner<br>// returned function has access to it, even though the outer `multiply`<br>// function already ran to completion.<br>const double = multiply(2);<br>console.log(double(5)); // Output: 10</pre><p>Closures serve three purposes:</p><ol><li>They provide data privacy for objects.</li><li>In functional programming, they enable partial application and currying.</li><li>They act as parameters for callback or higher-order functions like map, reduce, and filter.</li></ol><p>You’re going to see 2.) and 3.) later in this article, so let’s take a look at the concept of data privacy.</p><pre>const createCounter = () =&gt; {<br> let count = 0;<br> return function increment() {<br> count = count + 1;<br> return count;<br> }<br>}<br><br>const counter1 = createCounter();<br>const counter2 = createCounter();<br>counter1(); // 1<br>counter1(); // 2<br>counter2(); // 1<br>let capturedCount = counter1(); // 3<br>capturedCount = capturedCount + 39; // 42<br>counter1(); // 4</pre><p>No external influence can manipulate the count value of counter1. You need to call counter1 to increment it.</p><p>This matters because some applications require private state. A common pattern is to prefix the private key with __.</p><pre>const user = {<br> __secretKey: &#39;abcj&#39;<br>}</pre><p>However, junior developers might not know that __ signals: &quot;Do NOT change this key.&quot;, so they mutate it. And senior developers sometimes think it&#39;s okay to change it because they believe they know better.</p><p>Closures give you a reliable way to enforce data privacy for everyone.</p><h3>Imperative vs Declarative</h3><p>As you learned at the start of this article, functional programming is declarative. But what does that mean?</p><p><strong>Imperative</strong> code describes “<strong>how</strong>” to do things. The code contains the specific steps needed to achieve your desired result.</p><p><strong>Declarative</strong> code describes “<strong>what</strong>” to do. The “how” gets abstracted away. In other words, imperative programming is about defining the process to reach an outcome. This is known as <strong>flow control</strong>, where you dictate each step of the computation.</p><p>Declarative programming, on the other hand, is about defining the outcome, known as <strong>data flow</strong>. Here, you describe what you want, and the system determines how to achieve it.</p><p>Here is an example for imperative code.</p><pre>let numbers = [1, 2, 3, 4, 5];<br>let doubled = [];<br>for (let i = 0; i &lt; numbers.length; i++) {<br> doubled.push(numbers[i] * 2);<br>}<br>console.log(doubled); // Output: [2, 4, 6, 8, 10]</pre><p>And here is another example of imperative code, but this time with a custom function.</p><pre>function filterEvens(numbers) {<br> let index = 0;<br> <br> while (index &lt; numbers.length) {<br> if (numbers[index] % 2 !== 0) {<br> // Removes the item at the current index if it&#39;s odd.<br> numbers.splice(index, 1);<br> } else {<br> // Only move to the next index if the current item was not removed<br> // because the current index gets taken by the value after the<br> // deleted one.<br> index++;<br> }<br> }<br>}<br><br>let numbers = [1, 2, 3, 4, 5];<br>filterEvens(numbers);<br>console.log(numbers); // Output: [2, 4]</pre><p>Before you look at declarative code, you need to understand immutability and abstraction.</p><h3>Immutability</h3><p><strong>Immutability</strong> in programming means that an object or value <strong><em>cannot</em></strong> be modified after it is created; instead, any changes result in a new object or value.</p><pre>const originalArray = [1, 2, 3];<br>// Creates a new array by spreading the original and adding a new element.<br>const newArray = [...originalArray, 4];<br><br>console.log(originalArray); // Output: [1, 2, 3]<br>console.log(newArray); // Output: [1, 2, 3, 4]</pre><p>Similarly, <strong>mutable</strong> state is state that <strong>can</strong> be modified after you created it.</p><pre>const array = [1, 2, 3];<br>array.push(4); // Modifies the original array by adding a new element.<br><br>console.log(array); // Output: [1, 2, 3, 4]</pre><p>Immutability is a central concept of functional programming because with it, the data flow in your program is preserved. State history is maintained, and it helps prevent strange bugs from creeping into your software.</p><blockquote><em>“non-determinism = parallel processing + mutable state” — Martin Odersky</em></blockquote><p>You want determinism to make your programs easy to reason about, and parallel processing to keep your apps performant, so you have to get rid of mutable state.</p><p>Generally speaking, when you write your code using functional programming it becomes more deterministic, easier to reason about, easier to maintain, more modular and more testable.</p><h3>Abstraction</h3><p><strong>Abstraction</strong> is a fundamental concept in programming that involves hiding the complex reality while exposing only the necessary parts of an object or a system.</p><p>There are two types of abstraction: generalization and specialization.</p><p><strong>Generalization</strong> is when you create a more universal form of something for use in multiple contexts. This process identifies common features among different components and develops a single model to represent all variations.</p><p>This is what most people think of when they hear “abstraction” and what Eric Elliott refers to when he says:</p><blockquote><em>“</em><strong><em>Junior developers</em></strong><em> think they have to write a lot of code to produce a lot of value. </em><strong><em>Senior developers</em></strong><em> understand the value of the code that nobody needed to write.” — Eric Elliott</em></blockquote><p><strong>Specialization</strong> is when you apply the abstraction to a specific use-case and add what makes the current situation different.</p><p>The hard part is knowing <strong><em>when</em></strong> to generalize and when to specialize. Unfortunately, there is no good rule of thumb for this — you develop a feel for both with experience.</p><p>And what you’re going to find is: Abstraction is the key to simplicity.</p><blockquote><em>“Simplicity is about subtracting the obvious and adding the meaningful.” — John Maeda</em></blockquote><p>Using the functional programming paradigm you can create the most beautiful abstractions.</p><p>At this point you’re probably starving for examples and you’re going to see some soon.</p><p>Now, it’s time to look at declarative code, which will also show you more immutability and some abstraction.</p><h3>Array methods</h3><p>Remember, declarative programming is about “what” to do.</p><p>The perfect example of declarative code in JavaScript are the native array methods. You’re going to see the three most common ones: map, filter and reduce.</p><p>map Take a look at the map function first. It does exactly what we did earlier with the for loop, but the code is a lot more concise.</p><pre>const numbers = [1, 2, 3, 4, 5];<br>const doubled = numbers.map(n =&gt; n * 2); // Output: [2, 4, 6, 8, 10]</pre><p>The map method is a perfect example of abstraction.</p><p>map removes the obvious: iterating over the array and changing each value. Since it takes in a function, the map method is a higher-order function.</p><p>You only supply the meaningful: the function that doubles a number, which map applies to every number in the array. This double function is an anonymous function using the concise lamda syntax.</p><p>In general, a <strong>concise lambda</strong> is a simplified way to write a function with minimal syntax. In JavaScript it refers to the arrow function syntax.</p><p>map is also immutable because it returns a new array. numbers and doubled are two distinct arrays and the numbers array still contains the numbers 1 through 5. You can verify this by mapping using the identity function that returns its input.</p><pre>const numbers = [1, 2, 3, 4, 5];<br>const clone = numbers.map(x =&gt; x);<br>console.log(numbers === clone); // false</pre><p>Even though numbers and clone are both an array with the numbers 1 through 5, they are different array instances.</p><p>You might be asking yourself: “Isn’t that generating tons of data that no one uses?” Well kinda, but compared to what modern laptops are capable of the data amount is tiny, and JavaScript has garbage collection which clears up the stale memory.</p><h3>filter</h3><pre>const fruits = [&#39;apple&#39;, &#39;banana&#39;, &#39;citrus&#39;];<br>const containsA = fruits.filter(fruit =&gt; fruit.includes(&#39;a&#39;));<br>// Output: [&#39;apple&#39;, &#39;banana&#39;];</pre><p>The filter method takes in a special function called the &quot;predicate&quot;. A predicate is a function that always returns only a boolean. It tests each element in the array. If it returns true, the element is included in the resulting array.</p><p>filter also returns a new array aka. it&#39;s immutable.</p><h3>reduce</h3><p>The reduce method in JavaScript processes an array to produce a single output value. It takes a reducer function and an optional initial value. The reducer function itself accepts two parameters: an accumulator (which holds the accumulated result) and the currentValue from the array. The reduce method is also immutable.</p><p>Here is an example where you can use reduce on an array of numbers from 1 to 4, summing them together.</p><pre>const numbers = [1, 2, 3, 4];<br><br>const sumReducer = (accumulator, currentValue) =&gt; accumulator +<br>currentValue;<br><br>const total = numbers.reduce(sumReducer, 0); console.log(total); //<br>Output: 10</pre><p>In this example, reduce is called on the numbers array. The sumReducer function is used to add each number to a running total, starting from 0. Here&#39;s what happens at each step:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*clsmkViM7iVd1Jxs9nkVMQ.png" /></figure><p><strong>Details of the Process:</strong></p><ul><li><strong>Step 1:</strong> The accumulator starts at 0 (the initial value). The current value is the first element of the array, which is 1. They are added together to make the new accumulator value 1.</li><li><strong>Step 2:</strong> The accumulator is now 1. The current value is the next element in the array, 2. Adding these gives 3.</li><li><strong>Step 3:</strong> The accumulator is now 3, and the current value is 3. Adding these gives 6.</li><li><strong>Step 4:</strong> The accumulator is now 6, and the current value is the last element, 4. Adding these gives the final result of 10.</li></ul><p>At the end of the process, the reduce method returns 10, which is the sum of all elements in the array. This demonstrates how reduce can be used to transform an array into a single value through repeated application of a function.</p><p>reduce is the most powerful method because you can implement map and filter with reduce, but neither filter nor reduce with map and neither map or reduce with filter.</p><p>You can implement map with reduce like this:</p><pre>const mapUsingReduce = (array, mapFunction) =&gt;<br> array.reduce(<br> (accumulator, current) =&gt; [...accumulator, mapFunction(current)],<br> [],<br> );<br> <br>const numbers = [1, 2, 3, 4];<br><br>const doubled = mapUsingReduce(numbers, x =&gt; x * 2);<br>console.log(doubled); // Output: [2, 4, 6, 8]</pre><p>You can implement filter with reduce like this:</p><pre>const filterUsingReduce = (array, filterFunction) =&gt;<br> array.reduce(<br> (accumulator, current) =&gt;<br> filterFunction(current) ? [...accumulator, current] : accumulator,<br> [],<br> );<br> <br>const numbers = [1, 2, 3, 4];</pre><h3>Expressions over Statements</h3><p>In functional programming, you’ll see many expressions and few statements. Expressions avoid intermediate variables, while statements often bring side-effects and mutable state.</p><h3>Statements</h3><p>Imperative code frequently utilizes statements. A statement is a piece of code that performs an action.</p><ul><li><strong>Loops</strong> — for, while, etc.</li></ul><pre>// Loops:<br>// A for loop that logs numbers 0 to 4.<br>for (let i = 0; i &lt; 5; i++) {<br> console.log(i);<br>}<br>// A while loop that decrements x and logs it until x is no longer greater<br>than 0.<br>while (x &gt; 0) {<br> x--;<br> console.log(x);<br>}</pre><ul><li><strong>Control flow</strong> — if, switch, etc.</li></ul><pre>// An if...else statement that logs if x is positive or not.<br>if (x &gt; 0) {<br> console.log(&quot;x is positive&quot;);<br>} else {<br> console.log(&quot;x is zero or negative&quot;); <br>}<br>// A switch statement that handles different color cases.<br>switch (color) {<br> case &quot;red&quot;:<br> console.log(&quot;Color is red&quot;);<br> break;<br> case &quot;blue&quot;:<br> console.log(&quot;Color is blue&quot;);<br> break;<br> default:<br> console.log(&quot;Color is not red or blue&quot;);<br>}</pre><ul><li><strong>Error handling</strong> — try...catch, throw, etc.</li></ul><pre>// A try...catch block that handles errors from riskyFunction.<br>try {<br> let result = riskyFunction();<br>} catch (error) {<br> console.error(error);<br>}<br>throw new Error(&quot;Something went wrong&quot;); // Throws a new error with a<br>message.</pre><p>Except for functions, if it’s a keyword with curly braces, you’re likely dealing with a statement. (❗)</p><h3>Expressions</h3><p>Declarative code favors <strong>expressions</strong>. An expression evaluates to a value.</p><ul><li><strong>Literal expressions</strong></li></ul><pre>42; // The number 42 is a literal expression.<br>&quot;Hello&quot;; // The string &quot;Hello&quot; is a literal expression.</pre><ul><li><strong>Arithmetic expressions</strong></li></ul><pre>5 + 3; // Evaluates to 8.<br>x * y; // Evaluates to the product of x and y.</pre><ul><li><strong>Logical expressions</strong></li></ul><pre>true &amp;&amp; false; // Evaluates to false.<br>x || y; // Evaluates to x if x is true, otherwise y.</pre><ul><li><strong>Function expressions</strong></li></ul><pre>const funcExpr = function() { return 42; }; // Defines a function<br>expression.<br>const arrowFunc = () =&gt; 42; // Defines an arrow function expression.</pre><ul><li><strong>Object and array initializers</strong></li></ul><pre>{ name: &quot;John&quot;, age: 30 }; // Object initializer expression.<br>[1, 2, 3]; // Array initializer expression.</pre><ul><li><strong>Property access expressions</strong></li></ul><pre>obj.name; // Accesses the &quot;name&quot; property of obj.<br>array[0]; // Accesses the first element of array.</pre><ul><li><strong>Function calls</strong></li></ul><pre>square(7); // Evaluates to 49.<br>Math.max(4, 3, 2); // Evaluates to 4.</pre><h3>Function composition</h3><p>You’re about to unlock a new understanding of code and gain superpowers, so “lock-in”.</p><blockquote><em>“All software development is composition: The act of breaking a complex problem down to smaller parts, and then composing those smaller solutions together to form your application.” — Eric Elliot</em></blockquote><p>Whenever you use functions together, you’re “composing” them.</p><pre>const increment = n =&gt; n + 1;<br>const double = n =&gt; n * 2;<br>function doubleInc(n) {<br> const doubled = double(n);<br> const incremented = increment(doubled);<br> return incremented;<br>}<br><br>doubleInc(5); // 11</pre><p>But with anything in life, you can do it better if you do it consciously. The code above is actually NOT the ideal way to write it because:</p><p>The more code you write, the higher the surface area for bugs to hide in.</p><blockquote><em>less code = less surface area for bugs = less bugs</em></blockquote><p>The obvious exception is clear naming and documentation. It’s fine if you give a function a longer name and supply doc strings to make it easier for your readers to understand your code.</p><p>You can reduce the surface area for bugs by avoiding the capturing of the intermediary results in variables.</p><pre>const increment = n =&gt; n + 1;<br>const double = n =&gt; n * 2;<br><br>const doubleInc = n =&gt; inc(double(n));<br>doubleInc(5); // 11</pre><p>In mathematics, function composition is taking two functions f and g and applying one function to the result of another function: h(x) = (f ∘ g)(x) = f(g(x)). Note: The hollow dot ∘ is called the composition operator.</p><p>In mathematical notation, if you have two functions f and g, and their composition is written as (f ∘ g)(x), this means you first apply g to x and then apply f to the result of g(x). For your example, f(n) = n1 and g(n) = 2n, the composition h(n) = f(g(n)) calculates 2n + 1.</p><p><strong>Note:</strong> Generally mathematicians use x (or y or z etc.) to represent any variable, but in the code example above n is used to subtly hint at the parameter to be a number. The different name has no impact on the result.</p><p>You can abstract away the composition operator ∘ into a function called compose2 which takes two functions and composes them in mathematical order.</p><pre>const compose2 = (f, g) =&gt; x =&gt; f(g(x)); // ∘ operator<br><br>const increment = n =&gt; n + 1; // f(n)<br>const double = n =&gt; n * 2; // g(n)<br><br>const doubleInc = compose2(increment, double); // h(n)<br>doubleInc(5); // 11</pre><p>compose2 only works for two functions at a time.</p><p>But get ready, because this is where it gets powerful.</p><p>If you want to compose an arbitrary amount of functions, you can write a generalized compose.</p><pre>const compose = (...fns) =&gt; x =&gt; fns.reduceRight((y, f) =&gt; f(y), x);<br>// reduceRight works like reduce, but iterates the array from<br>// the last item to the first item.<br><br>const increment = n =&gt; n + 1;<br>const double = n =&gt; n * 2;<br>const square = n =&gt; n * n;<br><br>const incDoubleSquare = compose(square, double, increment);<br>incDoubleSquare(3); // 64</pre><p>The compose function here is written using the mathematical variable names. If you want to take the names that you might be used to from reduce, then you&#39;d write it like this:</p><pre>const compose =<br> (...functions) =&gt;<br> initialValue =&gt;<br> functions.reduceRight(<br> (accumulator, currentFunction) =&gt; currentFunction(accumulator),<br> initialValue,<br> );<br> <br>const increment = n =&gt; n + 1;<br>const double = n =&gt; n * 2;<br>const square = n =&gt; n * n;<br><br>const incDoubleSquare = compose(square, double, increment);<br>incDoubleSquare(3); // 64</pre><p>compose takes multiple functions as its arguments, and collects them into an array fns via the rest syntax.</p><p>It then returns a child function that takes in the initialValue x and returns the array fns with the reduceRight method applied to it. The reduceRight method then takes in a callback function and the initialValue x.</p><p>That callback function is the heart of compose. It takes in the accumulator y (starting from x) and the currentValue f, which is a function from the array fns. It then returns that function f - called with the accumulator y.</p><p>The result of that function call, then becomes the accumulator y for the next iteration. Here the next function f from fns get called with that new accumulator value. This repeats until the initialValue x has been passed and transformed through all functions from the array fns.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cHZuZkFFAgu-Qdy3VebwJA.png" /></figure><p>Many people who are used to reading texts from left to right find it unintuitive to compose functions in mathematical order. Many functional programming packages provide another function called commonly pipe, which composes function from left to right in reverse mathematical order.</p><pre>const pipe = (...fns) =&gt; x =&gt; fns.reduce((y, f) =&gt; f(y), x);<br><br>const increment = n =&gt; n + 1;<br>const double = n =&gt; n * 2;<br>const square = n =&gt; n * n;<br><br>const incDoubleSquare = pipe(increment, double, square);<br>incDoubleSquare(3); // 64</pre><h3>trace</h3><p>You might be asking right now. “But way, what if you want to debug your code? Then you need to capture the intermediate results in variables, right?”</p><p>You actually do not. You only need a helper higher-order function called trace.</p><pre>const trace = msg =&gt; x =&gt; {<br> console.log(msg, x);<br> return x;<br>}</pre><p>And here is how you can use it.</p><pre>const pipe = (...fns) =&gt; x =&gt; fns.reduce((y, f) =&gt; f(y), x);<br>const trace = msg =&gt; x =&gt; {<br> console.log(msg, x);<br> return x;<br>}<br><br>const increment = n =&gt; n + 1;<br>const double = n =&gt; n * 2;<br>const square = n =&gt; n * n;<br><br>const incDoubleSquare = pipe(<br> increment,<br> trace(&#39;before double&#39;),<br> double,<br> trace(&#39;after double&#39;),<br> square<br>);<br>incDoubleSquare(3); // 64<br>// Also logs out:<br>// before double 4<br>// after double 8</pre><h3>Currying</h3><p>You saw another technique being used called currying.</p><p><strong>Currying</strong> is a transformation of functions that translates a function from callable as f(a, b, c) into callable as f(a)(b)(c). In other words, a function is curried if it can take in each of it&#39;s parameters one at a time.</p><pre>function addUncurried(a, b) {<br> return a + b;<br>}<br><br>function addCurried(a) {<br> return function(b) {<br> return a + b;<br> }<br>}<br><br>addUncurried(41, 1); // 42<br>addCurried(41)(1); // 42</pre><p>With arrow functions, these definitions can become one-liners by leveraging their implicit returns.</p><pre>const addUncurried = (a, b) =&gt; a + b;<br><br>const addCurried = a =&gt; b =&gt; a + b;<br><br>addUncurried(41, 1); // 42<br>addCurried(41)(1); // 42</pre><p>Here a addCurried is a function that takes in a number a and returns a function b =&gt; a + b. You can read it like this:</p><pre>const addCurried = (a =&gt; (b =&gt; a + b));</pre><p>You can curry any function. For example, you can create custom map and reduce functions.</p><pre>const map = fn =&gt; arr =&gt; arr.map(fn);<br>const reduce = fn =&gt; x =&gt; arr =&gt; arr.reduce(fn, x);</pre><p>Map takes in two parameters and reduce takes in three. The number of parameters a function expects in its definition is called <strong>arity</strong>.</p><p>There are shorthand terms for functions that take in 1, 2 and 3 parameters.</p><ul><li><strong>Unary function</strong>: Takes one argument, e.g. square.</li><li><strong>Binary function</strong>: Takes two arguments, e.g. map.</li><li><strong>Ternary function</strong>: Takes three arguments, e.g. reduce.</li></ul><p>In the context of currying, understanding arity is important because each step in a curried function reduces the arity by one until all expected arguments have been received and the final operation can be performed.</p><p>Additionally, the first function of a composition can have any arity, but ever following function needs to be unary.</p><pre>const add3 = (a, b, c) =&gt; a + b + ; // Ternary<br>const double = n =&gt; n * 2; // Unary<br><br>const addThenDouble = pipe(add3, double);<br>addThenDouble(6, 7, 8); // 42</pre><p>Exercise: create your own custom filter function that is curried and takes in a predicate pred and then an array arr and then filters the array based on the predicate.</p><p>Wouldn’t it be nice if you had a function that can curry any function?</p><pre>const addUncurried = (a, b) =&gt; a + b;<br><br>const curry = /* ... magic? ... */<br><br>const addCurried = curry(addUncurried);</pre><p>Well there it is.</p><pre>const addUncurried = (a, b) =&gt; a + b;<br><br>const curry = (f, array = []) =&gt;<br> (...args) =&gt;<br> (a =&gt; (a.length &gt;= f.length ? f(...a) : curry(f, a)))([<br> ...array,<br> ...args,<br> ]);<br><br>// NOTE: because of f.length, this implementation of `curry` fails with<br>// functions that use default parameters.<br>const addCurried = curry(addUncurried);<br><br>const increment = addCurried(1);<br>increment(4); // 5<br>addCurried(1, 4); // 5<br>addCurried(1)(4); // 5</pre><p>The previous example used the mathematical names for the variables .If you want to name the variables more descriptively to understand curry now, you can write the function like this:</p><pre>const curry =<br> (targetFunction, collectedArguments = []) =&gt;<br> (...currentArguments) =&gt;<br> (allArguments =&gt;<br> allArguments.length &gt;= targetFunction.length<br> ? targetFunction(...allArguments)<br> : curry(targetFunction, allArguments))([<br> ...collectedArguments,<br> ...currentArguments,<br> ]);</pre><p>curry uses <strong>recursion</strong>, which is when a function calls itself for the purpose of iteration. Let&#39;s break it down:</p><ul><li>f or targetFunction: The original function you want to curry.</li><li>array or collectedArguments: An array to collect the arguments of all currying calls. Each cycle (except for the last) new arguments get added.</li><li>args or currentArguments: An array of the arguments taken in with the current invocation of the curried function (in this case: addCurried).</li><li>a or allArguments: An array of arguments concatenated from the currentArguments and the collectedArguments from the previous calls.</li></ul><p>When we call addCurried with one or more arguments, these current arguments get taken in as args in the child function of curry. This child function returns another function (grand-child function of curry).</p><p>The grand-child function immediately invokes itself with an array concatenated from the collected arguments array and the current arguments args, receiving these values from its grand-parent and parent closure. It takes this new array as the all arguments a and checks if the all arguments a contains the same or higher amount of arguments as the target function f is declared with.</p><p>If yes, this means all necessary currying calls have been performed: It returns a final call of the target function f invoked with the all arguments a. If not, it recursively invokes curry once again with the target function f and the new all arguments a, which then get taken in as the new collected arguments array. This repeats until the a.length condition is met.</p><p><strong>Side notes:</strong></p><ul><li>During the first call, the all arguments a and the current arguments args are the same value, as the collected arguments array has nothing added to it yet.</li><li>The grand-child function’s immediate invocation happens before its parent function completes.</li></ul><h3>Partial Application</h3><p>As you learned earlier, an application happens when the arguments are used to replace the function’s parameters. This allows the function to perform its task using the provided arguments.</p><pre>const add = (a, b) =&gt; a + b;<br>const inc = n =&gt; add(1, n);</pre><p>A <strong>partial application</strong> is the process of applying a function to <strong>some</strong>, but not all, of its arguments. This creates a new function, which you can store in a variable for later use. The new function needs fewer arguments to execute because it only takes the remaining parameters as arguments.</p><p>Partial applications are useful for specialization when you want to reuse a function with common parameters.</p><pre>const add = a =&gt; b =&gt; a + b;<br>const inc = add(1); // point-free<br>const incPointed = n =&gt; add(1)(n); // pointed</pre><p><strong>Point-Free Style</strong> inc is defined in point-free style, which is when you write a function without mentioning the parameters.</p><p>inc uses closure because the argument 1 is captured in the closure of add as a. There are two requirements for your code to be composable: &quot;data last&quot; and the data needs to line up.</p><h3>Data First vs. Data Last</h3><p>“data last” means the data your functions operate on should be their last parameter. For the add and multiply functions you saw earlier the order of arguments is irrelevant because they are commutative.</p><pre>const add = a =&gt; b =&gt; a + b;<br>const otherAdd = b =&gt; a =&gt; a + b;<br><br>const a = 41;<br>const b = 1;<br>console.log(add(41, 1) === otherAdd(41, 1)); // true</pre><p>But division is NOT, so you’re going to learn the importance of the “data last” principle using a divide function.</p><pre>const divideDataLast = (y) =&gt; (x) =&gt; x / y;<br>const divideDataFirst = (x) =&gt; (y) =&gt; x / y;</pre><p>Let’s say you want to specialize and create a halve function that takes in a number and divides it by two.</p><p>Using the “data first” function, it’s impossible for you to define a halve function in point-free style.</p><pre>const divideDataLast = (y) =&gt; (x) =&gt; x / y;<br><br>const divideDataFirst = (x) =&gt; (y) =&gt; x / y;<br><br>const halve = divideDataLast(2);<br>// const halveFail = divideDataFirst(2); 🚫 fails<br>const halvePointed = (n) =&gt; divideDataFirst(n)(2);</pre><p>halfFail captures 2 in the closure of divideDataFirst. It is a function that takes in a number and when called divides 2 by that supplied number.</p><p>In general, you need to write your functions using the “data last” principle to enable partial application.</p><p>BTW, the best library for functional programming in the “data last” paradigm is Ramda. Here is an outlook what you can do with <a href="https://ramdajs.com/">Ramda</a>. You can understand this in-depth some time in the future.</p><pre>import { assoc, curry, keys, length, pipe, reduce, values } from &#39;ramda&#39;;<br><br>const size = pipe(values, length);<br><br>size({ name: &#39;Bob&#39;, age: 42 }); // 2<br>size([&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;]); // 4<br><br>const renameKeys = curry((keyReplacements, object) =&gt;<br> reduce(<br> (accumulator, key) =&gt;<br> assoc(keyReplacements[key] || key, object[key], accumulator),<br> {},<br> keys(object),<br> ),<br>);<br><br>const input = { firstName: &#39;Elisia&#39;, age: 22, type: &#39;human&#39; };<br>const keyReplacements = { firstName: &#39;name&#39;, type: &#39;kind&#39;, foo: &#39;bar&#39; };<br><br>renameKeys(keyReplacements)(input);<br>// Output: { name: &#39;Elisia&#39;, age: 22, kind: &#39;human&#39; }</pre><p>size is a function that can take in objects or arrays and returns how many properties or elements it has.</p><p>Remember, functional programming is meant to improve your code by making it more bug-free, modular, testable, refactorable, understandable and deterministic. So, if you mix imperative and declarative code, like in the definition for renameKeys that is totally fine. Relax if you need some time to understand implementations like this. As you play around with this new paradigm, go with what&#39;s easiest for you to write. There is no need to force functional programming.</p><p>You can call renameKeys with keyReplacements and an input to return a new object with it&#39;s keys renamed according to the keyReplacements.</p><h3>Data Needs to Line Up</h3><p>Similarly, only with “data last” can you compose functions effectively because the types of the arguments and return values of functions have to line up to compose them. For example, you can’t compose a function that accepts an object and returns a string with a function that receives an array and returns a number.</p><pre>// (number, number) =&gt; number[]<br>const echo = (value, times) =&gt; Array(times).fill(value);<br>// number[] =&gt; number[]<br>const doubleMap = array =&gt; array.map(x =&gt; x * 2);</pre><h3>Mozart</h3><p>Now it’s your turn. Try the following exercise to practice what you’ve learned from this article. If you get stuck, you can always scroll up and read it again. If you prefer not to do the exercise, just read the solution and follow along.</p><p>Create a function that takes in an array of numbers and filters all the even numbers (so it rejects the odd numbers), then doubles all the even numbers and lastly, sums up the result. Break down your functions to their most basic abstractions then compose them point-free. (Hint: You’ll need to look up the modulo operator % to check if a number is even.)</p><pre>const curry =<br> (f, array = []) =&gt;<br> (...args) =&gt;<br> (a =&gt; (a.length &gt;= f.length ? f(...a) : curry(f, a)))([<br> ...array,<br> ...args,<br> ]);<br> <br>const add = curry((a, b) =&gt; a + b);<br>const multiply = a =&gt; b =&gt; a * b;</pre><p>Lastly, is this more complicated than simply writing the following?</p><pre>const mozart = numbers =&gt;<br> numbers<br> .filter(n =&gt; n % 2 === 0)<br> .map(n =&gt; n * 2)<br> .reduce((a, b) =&gt; a + b, 0);<br>mozart([1, 2, 3, 4, 5]); // 12</pre><p>Yes, absolutely! You should always use the simplest implementation for your requirements, also known as KISS (Keep It Simple, Stupid), or YAGNI (You Ain’t Going To Need It).</p><p>Functional programming shines as your app grows and you need to generalize and specialize your code so it scales well and stays maintainable. This way your code is more modular, and way easier to test, to reuse and to refactor. Future articles will show you real-world examples how to use these techniques.</p><p>You now know the 20% of functional programming that gives you 80% of the result.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=31fb4cffbed8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JavaScript Generators Explained, But On A Senior-Level]]></title>
            <link>https://medium.com/@jan.hesters/javascript-generators-explained-but-on-a-senior-level-91bccc03f6d2?source=rss-bf465a0206e8------2</link>
            <guid isPermaLink="false">https://medium.com/p/91bccc03f6d2</guid>
            <category><![CDATA[javascript-development]]></category>
            <category><![CDATA[coding-tutorial]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[javascript-tips]]></category>
            <dc:creator><![CDATA[Jan Hesters]]></dc:creator>
            <pubDate>Mon, 07 Oct 2024 19:11:55 GMT</pubDate>
            <atom:updated>2024-10-07T19:11:55.235Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BZfvHXt8SWr5rPDTx6PJXA.jpeg" /></figure><p>Generators are powerful and underused by <a href="https://www.reactsquad.io/hire-fullstack-javascript-developers">JavaScript developers</a>. And many tutorials on generators only scratch the surface. In this article, you’re going to go deep and you’ll develop an advanced understanding of the theory behind generators.</p><p>But first, do you want to see this tutorial in action? Here’s the video version.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FFAK1YR4X-8I%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DFAK1YR4X-8I&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FFAK1YR4X-8I%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/bfc9523d0c24b55d45f8d687e1492d7d/href">https://medium.com/media/bfc9523d0c24b55d45f8d687e1492d7d/href</a></iframe><p>Generators are most commonly seen in sagas, but there are more use cases for them. You’re going to see some of them in this article.</p><p>The short answer to the question “<strong>What is a generator?</strong>” is:</p><p><strong>Generators are pull streams</strong> in JavaScript.</p><p>Let’s dissect this definition and then jump into some examples.</p><p>First, you need to understand two terms: “pull” and “stream”.</p><h3>What is a stream?</h3><p>A <strong>stream</strong> is <strong>data over time</strong>. There are two types of streams: push streams and pull streams.</p><h3>What is a push stream?</h3><p>A <strong>push stream</strong> is a mechanism where you are <strong>NOT</strong> in control <strong>WHEN</strong> the data comes through.</p><p>Examples for pull streams include:</p><ul><li>a websocket,</li><li>reading a file from disk, and</li><li>server-sent events.</li></ul><p>You can see a JavaScript example of a push stream using Node.js to read a large file from disk below.</p><pre>const fs = require(&#39;fs&#39;);<br>const readStream = fs.createReadStream(&#39;./largeFile.txt&#39;);<br><br>readStream.on(&#39;data&#39;, chunk =&gt; {<br>  console.log(&#39;data received&#39;, chunk.length)<br>});<br><br>readStream.on(&#39;end&#39;, () =&gt; {<br>  console.log(&#39;finished reading file&#39;);<br>});<br><br>readStream.on(&#39;error&#39;, error =&gt; {<br>  console.log(&#39;an error occured while reading the file&#39;, error);<br>});</pre><h3>What is a pull stream?</h3><p>A <strong>pull stream</strong> is when you <strong>ARE</strong> in control <strong>WHEN</strong> you want to request the data.</p><p>You will see code examples for pull streams in JavaScript soon when you’re going to see generator code, but first you need to understand another concept.</p><h3>Lazy vs. eager</h3><p>In programming, data can be processed in two fundamental ways: eagerly or lazily.</p><h3>Eager</h3><p><strong>Eager</strong> means data is evaluated immediately, regardless of whether the result is needed in that moment. A push stream is <strong>eager</strong>. (Other examples: array methods, promises)</p><pre>// Eager evaluation with array methods:<br>const numbers = [1, 2, 3, 4, 5];<br><br>// Map immediately processes all elements in the array.<br>const squares = numbers.map(num =&gt; {<br>    console.log(`Squaring ${num}`);<br>    return num * num;<br>});<br><br>console.log(&#39;squares:&#39;, squares); // [1, 4, 9, 16, 25]</pre><p>You might be thinking: “Okay, but why are promises eager? Their result comes in late.”</p><p>Promises in JavaScript exhibit eager evaluation for several reasons.</p><ol><li><strong>Immediate Execution</strong>: The function passed to a new promise (known as the executor function) is executed immediately when the Promise is constructed.</li><li><strong>Irreversible Operations</strong>: Once the executor function begins executing, it cannot be stopped or paused by the consuming code. The results of the operation it performs (either resolution or rejection) will be queued in the JavaScript event loop to be handled as soon as possible.</li><li><strong>No Lazy Option</strong>: A promise lacks any built-in mechanism to defer or cancel the execution of its executor until a value is needed.</li><li><strong>Side Effects</strong>: The eager nature of promises means that any side effects included in the executor (like API calls, timeouts, or I/O operations) will happen immediately as part of the promise creation.</li></ol><p>The following example demonstrates how demonstrates how promises are executed immediately.</p><pre>// Eager evaluation with promises and array methods<br><br>console.log(&quot;Before promise&quot;);<br><br>let promise = new Promise((resolve, reject) =&gt; {<br>    console.log(&quot;Inside promise executor&quot;);<br>    resolve(&quot;Resolved data&quot;);<br>});<br><br>console.log(&quot;After promise&quot;);<br><br>promise.then(result =&gt; {<br>    console.log(result);<br>});</pre><p>This results in the following output.</p><pre>$ node eager-promise-example.js<br>Before promise<br>Inside promise executor<br>After promise<br>Resolved data</pre><h3>Lazy</h3><p><strong>Lazy</strong> means only evaluated when the value is needed (not before). A pull stream is <strong>lazy</strong>.</p><p>A synchronous example would be the operand selector operators.</p><pre>// Lazy evaluation with logical operators<br><br>function processData(data) {<br>    console.log(`Processing ${data}`); // This never logs out 🚫<br>    return data * data;<br>}<br><br>console.log(&#39;Lazy evaluation starts&#39;);<br>const data = 5;<br>const isDataProcessed = false;<br><br>// Lazy evaluation using the logical AND operator.<br>const result = isDataProcessed &amp;&amp; processData(data);<br>console.log(&#39;Result:&#39;, result); // false</pre><p>When you run this code, you’ll observe the following output.</p><pre>$ node lazy-evaluation-example.js<br>Lazy evaluation starts<br>Result: false</pre><p>Since isDataProcessed is false, the processData function never runs and you never see &quot;Processing 5&quot; in the console. This shows that the expression only evaluates what is needed to get the result.</p><h3>What is a generator?</h3><p>A generator is a pull stream in JavaScript. This means its a special kind of function where you can <strong>pause execution</strong> and <strong>resume</strong> it later.</p><p>The <strong>Generator</strong> object is returned by a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*">generator function</a> and it conforms to both the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol">iterable protocol</a> and the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol">iterator protocol</a>.</p><pre>function* myGenerator() {<br>  yield &quot;Hire senior&quot;;<br>  yield &quot;React engineers&quot;;<br>  yield &quot;at ReactSquad.io&quot;;<br>}<br><br>const iterator = myGenerator();<br><br>// Using the generator as an iterator.<br>console.log(iterator.next()); // { done: false, value: &quot;Hire senior&quot; }<br>console.log(iterator.next()); // { done: false, value: &quot;React engineers&quot; }<br>console.log(iterator.next()); // { done: false, value: &quot;at ReactSquad.io&quot; }<br>console.log(iterator.next()); // { done: true, value: undefined }<br><br>// Using the generator as an iterable.<br>for (let string of myGenerator()) {<br>  console.log(number); // &quot;Hire senior&quot; &quot;React engineers&quot; &quot;at ReactSquad.io&quot;<br>}</pre><p>Apart from the .next() method, generators also have .return() and .throw().</p><ul><li><strong>.return()</strong> - The .return() method terminates the generator&#39;s execution and returns the specified value, also triggering any finally blocks.</li><li><strong>.throw()</strong> - The .throw() method allows you to throw an error inside the generator at the point of the last yield, which can be caught and handled or allow the generator to clean up through a finally block. If uncaught, it stops the generator and marks it as done.</li></ul><pre>function* numberGenerator() {<br>  try {<br>    yield 1;<br>    yield 2;<br>    yield 3;<br>  } finally {<br>    console.log(&quot;Cleanup complete&quot;);<br>  }<br>}<br><br>const generator = numberGenerator();<br><br>// Using the generator normally.<br>console.log(generator.next()); // { done: false, value: 1 }<br>console.log(generator.next()); // { done: false, value: 2 }<br><br>// Using return() to finish the generator early.<br>console.log(generator.return(10)); // { done: true, value: 10 }<br>// After return(), no more values are yielded.<br>console.log(generator.next()); // { done: true, value: undefined }<br><br>// Resetting the generator for throw example.<br>const newGenerator = numberGenerator();<br>console.log(newGenerator.next()); // { done: false, value: 1 }<br><br>// Using throw() to signal an error.<br>try {<br>  newGenerator.throw(new Error(&quot;Something went wrong&quot;));<br>} catch (e) {<br>  console.log(e.message); // &quot;Something went wrong&quot;<br>}<br>// After throw(), the generator is closed.<br>console.log(newGenerator.next()); // { done: true, value: undefined }</pre><p>You can also pass in numbers or any other value to generators when you call next() with an argument.</p><p>Try to predict <em>what</em> will log out and <em>when</em> in the following example.</p><pre>function* moreNumbers(x) {<br>    console.log(&#39;x&#39;, x);<br>    const y = yield x + 2;<br>    console.log(&#39;y&#39;, y);<br>    const z = yield x + y;<br>    console.log(&#39;z&#39;, z);<br>}<br><br>const it2 = moreNumbers(40);<br><br>console.log(it2.next());<br>console.log(it2.next(2012));<br>console.log(it2.next());</pre><p>This example demonstrates how the generator function moreNumbers manipulates and yields values based on the input it receives during the sequence of .next() calls.</p><p>Take look at the output and check your prediction.</p><pre>const it2 = moreNumbers(40);<br><br>// x: 40<br>console.log(it2.next()); // { value: 42, done: false }<br><br>// y: 2012<br>console.log(it2.next(2012)); // { value: 2052, done: false }<br><br>// z: undefined<br>console.log(it2.next()); // { value: undefined, done: true }</pre><p>Let’s breakdown of each step of the moreNumbers generator function, so you understand it fully.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DCso4ngAA17-2xvVX7oBLQ.png" /></figure><h3>Use Cases for Generators</h3><p>There are three main uses cases for generators.</p><ol><li><strong>Lazy evaluation</strong> — generate data on demand or process large or infinite data sets.</li><li><strong>Asynchronous programming</strong> — handle asynchronous operations.</li><li><strong>Iterators</strong> — allowing to stop in between steps for complex flows.</li></ol><p>Earlier, you saw an example of reading a file from disk as a push stream. Below is how you would write the data reading using a generator to turn it into a pull stream.</p><pre>const fs = require(&#39;fs&#39;);<br><br>function getChunkFromStream(stream) {<br>    return new Promise((resolve, reject) =&gt; {<br>        stream.once(&#39;data&#39;, (chunk) =&gt; {<br>            stream.pause();<br>            resolve(chunk);<br>        });<br><br>        stream.once(&#39;end&#39;, () =&gt; {<br>            resolve(null);<br>        });<br><br>        stream.once(&#39;error&#39;, (err) =&gt; {<br>            reject(err);<br>        });<br><br>        stream.resume();<br>    });<br>}<br><br>async function* readFileChunkByChunk(filePath) {<br>    const stream = fs.createReadStream(filePath);<br>    let chunk;<br><br>    while (chunk = await getChunkFromStream(stream)) {<br>        yield chunk;<br>    }<br>}<br><br>const generator = readFileChunkByChunk(&#39;./largeFile.txt&#39;);<br><br>(async () =&gt; {<br>    for await (const chunk of generator) {<br>        console.log(&quot;data received&quot;, chunk.length);<br>    }<br>})();</pre><h3>Real-World Examples</h3><p>Sagas are a prime example of handling asynchronous I/O operations. But you’re going to learn how to use sagas in a future article, in a series of articles on Redux.</p><p>And then generally you use generators when you want to be in control WHEN to get a value.</p><p>Take a look at this test example.</p><pre>test(&#39;given an onboarded owner user: shows the invite link creation UI as well as the members of the organization, and lets the user change their role&#39;, async ({ page }) =&gt; {<br>  // Generator for roles in the organization.<br>  function* roleGenerator() {<br>    const allRoles = Object.values(ORGANIZATION_MEMBERSHIP_ROLES);<br>    for (const role of allRoles) {<br>      yield role;<br>    }<br>  }<br>  const roleIterator = roleGenerator();<br>  const data = await setup({<br>    page,<br>    role: ORGANIZATION_MEMBERSHIP_ROLES.OWNER,<br>    numberOfOtherTeamMembers: allRoles.length,<br>  });<br>  const { organization, sortedUsers, user } = data;<br><br>  // Navigate to team members settings page.<br>  await page.goto(`/organizations/${organization.slug}/settings/team-members`);<br><br>  // Loop through each team member to assign roles using the generator.<br>  for (let index = 0; index &lt; sortedUsers.length; index++) {<br>    const memberListItem = page.getByRole(&#39;list&#39;, { name: /team members/i }).getByRole(&#39;listitem&#39;).nth(index);<br>    const otherUser = sortedUsers[index];<br><br>    // Change role for each team member, except the current user.<br>    if (otherUser.id !== user.id) {<br>      await memberListItem.getByRole(&#39;button&#39;, { name: /member/i }).click();<br>      const role = roleIterator.next().value!;<br>      await page.getByRole(&#39;option&#39;, { name: role }).getByRole(&#39;button&#39;).click();<br>      await page.keyboard.press(&#39;Escape&#39;);<br>    }<br>  }<br><br>  await teardown(data);<br>});</pre><p>In this test, you define a roleGenerator to sequentially provide a list of roles for users within an organization. This approach allows the test to dynamically assign each user a unique role from a predefined list as part of a role management feature in a UI.</p><p>The reason a generator — as opposed to an array — was used for this example is that the position of the main user in the test is unknown in the sortedUsers array and since a generator is a pull stream you can get the role values on demand.</p><p>‍</p><p>If you loved this, then you’ll love my YouTube channel. Check it out <a href="https://www.youtube.com/@JanHesters/">here</a>!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=91bccc03f6d2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[12 Keys to Write Senior-Level Tests (Test Desiderata in JavaScript)]]></title>
            <link>https://medium.com/@jan.hesters/12-keys-to-write-senior-level-tests-test-desiderata-in-javascript-41f0755c0cd5?source=rss-bf465a0206e8------2</link>
            <guid isPermaLink="false">https://medium.com/p/41f0755c0cd5</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[testing-framework]]></category>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Jan Hesters]]></dc:creator>
            <pubDate>Fri, 04 Oct 2024 15:24:26 GMT</pubDate>
            <atom:updated>2024-10-04T15:24:26.533Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ddqi3yswq3AYcGc1AqixPA.jpeg" /></figure><p>“Desiderata” is derived from the Latin word for “things desired.”</p><p><a href="https://twitter.com/KentBeck">Kent Beck</a> wrote <a href="https://medium.com/@kentbeck_7670/test-desiderata-94150638a4b3">an article called “Test Desiderata”</a>. And together with <a href="https://twitter.com/KellySutton">Kelly Sutton</a>, they created a <a href="https://www.youtube.com/watch?v=5LOdKDqdWYU&amp;list=PLlmVY7qtgT_lkbrk9iZNizp978mVzpBKl">YouTube playlist</a> about that article that explains the desirable properties of automated tests. Some of these properties are trade-offs and not every test needs to have them.</p><p>The examples Kent and Kelly give are <em>in Ruby</em>. Consider this article and video a TL;DW with some additions and a <strong>translation into JavaScript</strong>.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FhtraT8Qn4Rw%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DhtraT8Qn4Rw&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FhtraT8Qn4Rw%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/8d50b78e441c1049f14cc4498c45220a/href">https://medium.com/media/8d50b78e441c1049f14cc4498c45220a/href</a></iframe><p>The examples in this tutorial use <a href="https://github.com/ericelliott/riteway">RITEway</a> because of its genius API that forces you to write good tests. It also uses <a href="https://jestjs.io/">Jest</a> once, where RITEway prohibits you from writing bad tests. You should really watch Ken’s and Kelly’s playlist in addition to reading this article — even if you, like me, don’t know Ruby. Their explanations are fun and enlightening.</p><p>Obviously you should also learn Jest and Vitest because they are popular testing frameworks that every <a href="https://www.reactsquad.io/hire-fullstack-javascript-developers">fullstack JavaScript developer</a> should know. But that’s a topic for another article.</p><p>Let’s dive into the principles.</p><h3>Behavioral-Sensitive</h3><p>Tests should be sensitive to the behavior of your code.</p><pre>const calculateSF = (hours, rate) =&gt; hours * Math.max(rate, 15);<br><br>const calculate = (hours, rate, zipCode = null) =&gt; {<br>  if (zipCode === &#39;94103&#39;) {<br>    return calculateSF(hours, rate);<br>  } else {<br>    return hours * rate;<br>  }<br>};<br><br>describe(&#39;calculate&#39;, async assert =&gt; {<br>  const hours = 40;<br><br>  {<br>    const rate = 8;<br><br>    assert({<br>      given: &#39;your hours and your rate&#39;,<br>      should: &#39;return the total hourly wage&#39;,<br>      actual: calculate(hours, rate),<br>      expected: 320,<br>    });<br>  }<br><br>  {<br>    const rate = 14;<br>    const zipCode = &#39;94103&#39;;<br><br>    assert({<br>      given:<br>        &#39;your hours, your rate below $15 and your zip code is in San Francisco&#39;,<br>      should:<br>        &#39;return the total hourly wage calculated with the minimum wage of $15&#39;,<br>      actual: calculate(hours, rate, zipCode),<br>      expected: 600,<br>    });<br>  }<br><br>  {<br>    const rate = 16;<br>    const zipCode = &#39;94103&#39;;<br><br>    assert({<br>      given:<br>        &#39;your hours, your rate above $15 and your zip code is in San Francisco&#39;,<br>      should:<br>        &#39;return the total hourly wage calculated with your actual rate&#39;,<br>      actual: calculate(hours, rate, zipCode),<br>      expected: 640,<br>    });<br>  }<br>});</pre><p>If you change the behavior of the code, the results of the tests should change, too. If you change the minimum wage in calculateSF to 150 dollars, the tests for the San Francisco zip code fail.</p><h3>Structure-Insensitive</h3><p>Tests should be insensitive to the internals of your code’s structure.</p><p>RITEway makes it nearly impossible to write structure sensitive tests because there is no mocking API like mock functions, spies or module mocks.</p><p>So the next example uses Jest to write structure-sensitive code by using spyOn. You need to create an hourlyWageCalculator object to be able to use spyOn.</p><pre>const hourlyWageCalculator = {<br>  calculateSF(hours, rate) {<br>    return hours * Math.max(rate, 15);<br>  },<br>  calculate(hours, rate, zipCode = null) {<br>    if (zipCode === &#39;94103&#39;) {<br>      return this.calculateSF(hours, rate);<br>    } else {<br>      return hours * rate;<br>    }<br>  },<br>};<br><br>describe(&#39;calculate&#39;, () =&gt; {<br>  const hours = 40;<br><br>  describe(&#39;given your hours, your rate below $15 and your zip code in San Francisco&#39;, () =&gt; {<br>    it(&#39;should return the total hourly wage calculated with the minimum wage of $15&#39;, () =&gt; {<br>      const rate = 14;<br>      const zipCode = &#39;94103&#39;;<br>      const spy = jest.spyOn(hourlyWageCalculator, &#39;calculateSF&#39;);<br><br>      hourlyWageCalculator.calculate(rate, hours, zipCode);<br><br>      expect(spy).toHaveBeenCalledWith(rate, hours);<br><br>      spy.mockRestore();<br>    });<br>  });<br><br>  describe(&#39;given your hours, your rate above $15 and your zip code in San Francisco&#39;, () =&gt; {<br>    it(&#39;should return the total hourly wage calculated with your actual rate&#39;, () =&gt; {<br>      const rate = 16;<br>      const zipCode = &#39;94103&#39;;<br>      const spy = jest.spyOn(hourlyWageCalculator, &#39;calculateSF&#39;);<br><br>      hourlyWageCalculator.calculate(rate, hours, zipCode);<br><br>      expect(spy).toHaveBeenCalledWith(rate, hours);<br><br>      spy.mockRestore();<br>    });<br>  });<br>});</pre><p>Here we made the assumption that calculateSF exists and that we are using it. If we refactor the code to inline the San Francisco calculation, the tests fail because they are structure sensitive.</p><pre>const hourlyWageCalculator = {<br>  calculate(hours, rate, zipCode = null) {<br>    if (zipCode === &#39;94103&#39;) {<br>      return hours * Math.max(rate, 15);<br>    } else {<br>      return hours * rate;<br>    }<br>  },<br>};</pre><p>If you refactor code without changing its behavior, the test results should stay the same. The RITEway tests from the behavior section above are invariant to the structure of the code. You might now this property as <a href="https://kentcdodds.com/blog/testing-implementation-details/">“avoid testing implementation details”</a>.</p><h3>Readable</h3><blockquote><em>“Tests, like code, are read more than written.” — Kent Beck</em></blockquote><p>Some things that make code more readable actually reduce the readability of tests.</p><pre>const defaultHours = 40;<br><br>describe(&#39;calculate&#39;, async assert =&gt; {<br>  const hours = defaultHours;</pre><p>You might see how most people work 40 hours per week and therefore create a defaultHours constant and re-use it in your test setups. But, <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">drying</a> up your tests often makes them less readable.</p><p>Avoid writing a test that is a few lines, but incomprehensible and impossible to understand without reading a lot of other lines of shared setup code.</p><blockquote><em>“You’re not supposed to repeat yourself, unless you’re supposed to repeat yourself.” — Kent Beck</em></blockquote><p>Tests should be self-contained and therefore tests can be verbose. The reader must be able to extract the motivation for the test and what happens in the test.</p><p>(Obviously, there are trade-offs and there are occasions where you want to generalize your tests setup.)</p><h3>Writable</h3><p>Tests should be easy to write. If the tests for your code are hard to write, it indicates your code has a bad API, is too tightly coupled or you lack good setup functions.</p><pre>const calculate = employee =&gt; employee.weeklyHours * employee.hourlyRate;<br><br>const createEmployee = ({<br>  firstName = &#39;&#39;,<br>  lastName = &#39;&#39;,<br>  address = &#39;&#39;,<br>  weeklyHours = 0,<br>  hourlyRate = 0,<br>}) =&gt; ({<br>  firstName,<br>  lastName,<br>  address,<br>  weeklyHours,<br>  hourlyRate,<br>});<br><br>describe(&#39;calculate&#39;, async assert =&gt; {<br>  {<br>    const employee = createEmployee({<br>      firstName: &#39;Kent&#39;,<br>      lastName: &#39;Beck&#39;,<br>      address: &#39;1 Main St., Berkeley, CA, 94701&#39;,<br>      weeklyHours: 40,<br>      hourlyRate: 8,<br>    });<br><br>    assert({<br>      given: &#39;an employee&#39;,<br>      should: &#39;calculate the total hourly wages&#39;,<br>      actual: calculate(employee),<br>      expected: 320,<br>    });<br>  }<br><br>  {<br>    const employee = createEmployee({<br>      firstName: &#39;Kelly&#39;,<br>      lastName: &#39;Sutton&#39;,<br>      address: &#39;1 Main St., San Francisco, CA, 94103&#39;,<br>      weeklyHours: 40,<br>      hourlyRate: 7,<br>    });<br><br>    assert({<br>      given: &#39;an employee&#39;,<br>      should: &#39;calculate the total hourly wages&#39;,<br>      actual: calculate(employee),<br>      expected: 280,<br>    });<br>  }<br>});</pre><p>In these tests, firstName, lastName and address could be deleted and are irrelevant for calculate. Its the fault of the system that the tests are expensive relative to the code under test.</p><p>If you need dozens or hundreds of lines of test setup to test a few lines of code, that’s a design problem, not a test problem. For example, your tests might use a lot of <a href="https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a">mocks because your code is too tightly coupled</a> and has many side-effects.</p><blockquote><em>“If you’re unable to test it, you designed it wrong.” — Kent Beck</em></blockquote><h3>Fast</h3><p>Tests should run quickly.</p><p>On average it takes your brain <a href="https://news.gallup.com/businessjournal/23146/too-many-interruptions-work.aspx">23 minutes to re-focus</a> on a task after it gets distracted. And those distractions can happen within seconds. Good tests help you stay in flow because they run fast.</p><pre>const delay = seconds =&gt;<br>  new Promise(resolve =&gt; {<br>    setTimeout(() =&gt; {<br>      resolve();<br>    }, seconds * 1000);<br>  });</pre><p>Add this delay function before each assert in the tests.</p><pre>await delay(5);<br><br>assert({<br>  given: // ...</pre><p>Now run your tests and see how you feel. Watch how your mind drifts off while you wait for those tests to finish …</p><p>You can run your <strong>unit tests</strong> in <strong>under a second</strong> by isolating side-effects and composing your code from pure functions. Your <strong>CI / CD pipeline</strong> (including your integration and functional tests) should run in <strong>under 10 minutes</strong>.</p><p>Avoid interrupting flow to keep your engineering team productive.</p><h3>Automated</h3><p>Machines should do the testing, not humans.</p><p>Avoid Q&amp;A teams having to manually test your release candidates for regressions. This is what automated tests are for.</p><p>The benefits of automated tests compound. Initially, it might be faster and cheaper to click through your app and writing the test takes longer.</p><p>However as your app grows, not investing into automated tests causes compounding debt.</p><ul><li>It gets harder to add tests.</li><li>Manually going through your program’s flow takes longer than a machine clicking through it.</li><li>Machines have better memory than humans. Once a test is written, the machine doesn’t forget the test case and prevents regressions.</li><li>You pay a computer less per hour than an engineer.</li><li>While the automation (CI / CD pipeline) runs, you can work on new features.</li></ul><p>Automated tests guarantee program correctness (- when your tests are good).</p><h3>Isolated</h3><p>Isolated tests can run in any order and in parallel. Whether one test passes or not should have no effect on whether another test passes. To achieve this, the state needs to be the same before and after each test.</p><pre>let numberOfCalculations = 0;<br>const calculate = (hours, rate) =&gt; ++numberOfCalculations * hours * rate;<br><br>describe(&#39;calculate&#39;, async assert =&gt; {<br>  const hours = 40;<br><br>  {<br>    const rate = 8;<br><br>    assert({<br>      given: &#39;your hours and your rate&#39;,<br>      should: &#39;return the total hourly wage&#39;,<br>      actual: calculate(hours, rate),<br>      expected: 320,<br>    });<br>  }<br><br>  {<br>    const rate = 10;<br><br>    assert({<br>      given: &#39;your hours and your rate&#39;,<br>      should: &#39;return the total hourly wage&#39;,<br>      actual: calculate(hours, rate),<br>      expected: 560,<br>    });<br>  }<br>});</pre><p>If you change the order of the tests, they both fail, even though the code is the same. If the code was right before, the tests should still pass and if it was wrong before, the tests should still fail.</p><p>In these tests the order matters because they both share mutable state. Tests should have no shared state and they should be isolated from their environment (e.g. DOM, database, scope etc.).</p><p>The tests depending on the order they run in points to a flaw in the code’s design. Prefer to write code where you initialize the state locally (in the scope of the test). If your code is correct and must change the state, you should <strong>restore the initial state <em>after each</em> test</strong>.</p><h3>Composable</h3><blockquote><em>“The confidence provided by one test should combine with the confidence provided by other tests.” — Kent Beck</em></blockquote><pre>const inc = n =&gt; n + 1;<br>const double = n =&gt; n * 2;<br>const incDouble = n =&gt; double(inc(n));<br><br>describe(&#39;inc&#39;, async assert =&gt; {<br>  assert({<br>    given: &#39;a number&#39;,<br>    should: &#39;increment it by 1&#39;,<br>    actual: inc(41),<br>    expected: 42,<br>  });<br>});<br><br>describe(&#39;double&#39;, async assert =&gt; {<br>  assert({<br>    given: &#39;a number&#39;,<br>    should: &#39;return the number doubled&#39;,<br>    actual: double(21),<br>    expected: 42,<br>  });<br>});<br><br>describe(&#39;incDouble&#39;, async assert =&gt; {<br>  assert({<br>    given: &#39;a number&#39;,<br>    should: &#39;increment it by 1 and double the result&#39;,<br>    actual: incDouble(20),<br>    expected: 42,<br>  });<br>});</pre><p>We ensure the correctness of inc and double and then ensure the correctness of the composed function incDouble. The calculation of inc is isolated from double and vice versa and both are isolated from incDouble.</p><p>To make your tests composable, your code needs to be composable, too.</p><h3>Specific</h3><p>A failing test should point you to the cause of failure.</p><p>If you break incDouble by changing the order in the function composition, your test points you to the failure.</p><pre>const incDouble = n =&gt; inc(double(n)); // 🚫</pre><pre>incDouble<br>  ✖  Given a number: should increment it by 1 and double the result<br>  ------------------------------------------------------------------<br>      operator: deepEqual<br>      diff: &quot;42&quot; =&gt; &quot;41&quot;<br>      source: ...</pre><p>The stack trace (given by RITEway in the source key) tells us exactly which test failed and therefore which component broke.</p><p>If you restore incDouble and break inc by having it add 2 to your number, two tests break.</p><pre>const inc = n =&gt; n + 2; // 🚫</pre><pre>inc<br>  ✖  Given a number: should increment it by 1<br>  --------------------------------------------<br>      operator: deepEqual<br>      diff: &quot;42&quot; =&gt; &quot;43&quot;<br>      source: ...<br><br>double<br>  ✔  Given a number: should return the number doubled<br><br>incDouble<br>  ✖  Given a number: should increment it by 1 and double the result<br>  ------------------------------------------------------------------<br>      operator: deepEqual<br>      diff: &quot;42&quot; =&gt; &quot;44&quot;<br>      source: ...</pre><p>Using the stack trace you can find the test that points you to the cause of both errors. When you encounter multiple errors in your stack, the leafs are likely the source of the error.</p><h3>Deterministic</h3><p>Given the same code, tests should produce the same results.</p><p>A few years ago, I helped with a project that had a flaky test.</p><pre>const slice = &#39;userAuthentication&#39;;<br><br>const getExpirationDate = ({ [slice]: { expirationDate } }) =&gt; expirationDate;<br>const THIRTY_MINUTES = 30 * 60 * 1000;<br><br>const getShouldUpdateToken = state =&gt; {<br>  const now = new Date().getTime();<br>  const expirationDate = getExpirationDate(state);<br>  return now &gt; expirationDate - THIRTY_MINUTES;<br>};<br><br>describe(&#39;user authentication reducer&#39;, async assert =&gt; {<br>  // ... more tests<br><br>  {<br>    const now = new Date().getTime();<br>    const state = { auth: { expirationDate: now + THIRTY_MINUTES } };<br><br>    assert({<br>      given:<br>        &#39;an expiration date 30 minutes into the future and a get should update token selector&#39;,<br>      should: &#39;return false&#39;,<br>      actual: getShouldUpdateToken(state),<br>      expected: false,<br>    });<br>  }<br><br>  // ... more tests<br>});</pre><p>What made this test flaky, was the side effect in getShouldUpdateToken. The selector creates a new Date object. Occasionally, this would happen a millisecond after the test setup and the test would fail.</p><p>You can make this test deterministic by making the unit under test a pure function and explicitly passing the arguments to the function in the test.</p><pre>const getShouldUpdateToken = (state, now) =&gt; {<br>  const expirationDate = getExpirationDate(state);<br>  return now &gt; expirationDate - THIRTY_MINUTES;<br>};</pre><p>Tests shouldn’t be flaky (even though some <a href="https://www.cypress.io/blog/2020/08/19/introducing-test-retries-in-cypress-5-0/">test frameworks accommodated for unreliable tests</a>). They should be independent of random number generations, times, dates, locations, language settings, operating systems etc.</p><p>BTW, the example that Kent and Kelly give in their video in Ruby excellently visualizes the problem for E2E tests.</p><h3>Predictive</h3><p>Passing tests should give you confidence to deploy to production.</p><p>Some environments and conditions are only created in production environments. Sometimes you can’t know a condition is going to exist and even if you know about it, it’s hard or impossible to replicate it.</p><p>If you find an error in production, you can write a test for it when you fix it and prevent the bug from resurfacing.</p><p>100% predictability can rarely be achieved in the real world.</p><p>To maximize predictability, aim for 100% case coverage instead of 100% code coverage by using unit tests and integration tests. You can have 100% of your code covered by unit tests, but your application is still broken.</p><h3>Inspiring</h3><p>Tests should give you that feeling of confidence.</p><p>Tests should make a difference to team productivity. Your team should be faster and merge more often. Good tests serve as documentation, help with onboarding and aid in understanding code that someone else wrote. (That someone else might be your past you.) They should allow you to increase your deploy frequency while decreasing your deploy risk. Tests make you relax. Tests allow you to rewrite or delete code.</p><blockquote><em>“And finally, tests are a way to liberate energy, to move from worry to hope, creativity, and empathy, to demonstrate trustworthiness.</em></blockquote><blockquote><strong><em>Passing tests should feel good.</em></strong><em>” — Kent Beck</em></blockquote><p>‍</p><p>My YouTube channel has some more Javascript and React tutorials you’d love. You should check it out <a href="https://www.youtube.com/@janhesters?themeRefresh=1">here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=41f0755c0cd5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Unit Testing Reducers]]></title>
            <link>https://medium.com/@jan.hesters/unit-testing-reducers-2df09bb6ba61?source=rss-bf465a0206e8------2</link>
            <guid isPermaLink="false">https://medium.com/p/2df09bb6ba61</guid>
            <category><![CDATA[reducer]]></category>
            <category><![CDATA[riteway]]></category>
            <category><![CDATA[state]]></category>
            <category><![CDATA[unit-testing]]></category>
            <category><![CDATA[redux]]></category>
            <dc:creator><![CDATA[Jan Hesters]]></dc:creator>
            <pubDate>Tue, 13 Oct 2020 10:02:57 GMT</pubDate>
            <atom:updated>2020-10-19T19:30:20.045Z</atom:updated>
            <content:encoded><![CDATA[<p>Pure functions are simple and so should be their tests (when using the correct techniques). Reducers, action creators (which aren’t thunks) and selectors are pure functions. After you read this article, when you apply these techniques, you’ll <strong>simplify your tests</strong> and <strong>improve your code</strong>.</p><figure><a href="https://janhesters.com/unit-testing-reducers"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Qb2Gy3lurNTQZhagCnarLA.jpeg" /></a><figcaption>Photo by <a href="https://unsplash.com/@ikewinski?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Mike Lewinski</a> on <a href="https://unsplash.com/s/photos/tao-te-ching?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>The tests in the examples are written with <a href="https://medium.com/javascript-scene/rethinking-unit-test-assertions-55f59358253f">RITEway</a> because it has a genius API that forces you to write good tests. RITEway only exposes an equality assertion, which is perfect for testing <a href="https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-pure-function-d1c076bec976">pure functions</a>. (The examples work with Jest, too. 🃏)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/52c09a8ed1bbd66f76a31f7e37613016/href">https://medium.com/media/52c09a8ed1bbd66f76a31f7e37613016/href</a></iframe><p>I’ll let you read the code now first, and afterwards we analyse the structure of the tests.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/52c09a8ed1bbd66f76a31f7e37613016/href">https://medium.com/media/52c09a8ed1bbd66f76a31f7e37613016/href</a></iframe><p>In the example above, we combined the logic and the tests. Usually, the code would live in at least three files (counter-reducer.js, counter-reducer.test.js and root-reducer.js):</p><pre>src/<br>├── ...<br>├── features/<br>│   ├── ...<br>│   └── counter/<br>│       ├── ...<br>│       └── counter-reducer.js<br>│       └── counter-reducer.test.js<br>├── redux/<br>│   ├── root-reducer.js<br>│   ├── root-saga.js<br>│   └── store.js<br>└── index.test.js</pre><p>The first test ensures the reducer has the correct initial state. Use a createState factory function for this test and avoid exporting the initial state and using it for the assertion.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6fe87ead728bb438c23efecf39de3053/href">https://medium.com/media/6fe87ead728bb438c23efecf39de3053/href</a></iframe><p>You need to <strong>ensure the initial state is correct before you use it</strong> in your tests. If you export it and use it in your assertion, an error might slip through. The initial state could have wrong keys, or the wrong initial values for correct keys or be missing keys and your tests would still pass.</p><p>The first test tests the reducer without arguments. A reducer that returns its initial state when called without arguments is not in Redux’ official reducer specification. However, it’s helpful for selector tests (- we will see below exactly how -) or to populate initial state for <a href="https://reactjs.org/docs/hooks-reference.html#usereducer">the </a><a href="https://reactjs.org/docs/hooks-reference.html#usereducer">useReducer Hook</a>.</p><pre>const [state, dispatch] = useReducer(reducer, reducer());</pre><p>Suppose you can’t call your reducers without arguments (for example because you’re using <a href="https://redux-toolkit.js.org/">Redux Toolkit</a>). In that case, you can write this test by passing undefined and an empty object.</p><pre>assert({<br>  given: &#39;no state with an empty action&#39;,<br>  should: &#39;return the valid initial state&#39;,<br>  actual: reducer(undefined, {}),<br>  expected: createState(),<br>});</pre><p><strong>Note:</strong> If you are looking for an alternative to Redux Toolkit that supports empty argument reducers, check out <a href="https://github.com/ericelliott/autodux">Autodux</a>.</p><p><strong>Test the action creators tightly coupled to your reducer</strong> by invoking both in your tests. Avoid action creator tests that assert on their returned action object because all you care about is how they modify the state of your reducer. Using action creators and their handlers together is the most efficient and effective way to test action creators and reducer functions.</p><p><strong>Use a factory function</strong> like createState <strong>to set up the tests</strong> for action creators. (For selectors, directly use the rootReducer in conjunction with your actions to compose the state.) You can easily debug the factory function, which allows you to describe the exact state shape that you want. Favor a factory function over inlining the reducer&#39;s complete state because it&#39;s shorter to overwrite the relevant keys. <strong>Avoid</strong> using <strong>actions creators in the setup</strong> for your action creator tests because you don&#39;t know whether they behave correctly at that point. Sometimes (e.g. for complex reducers with many keys) you should additionally use a createPopulatedState factory function.</p><pre>const createPopulatedState = ({<br>  count: 14<br>  <em>/* , ... more populated values for more keys */<br></em>} = {}) =&gt; createState({ count <em>/* , ... more keys */</em> });</pre><p>Furthermore, notice how the action creators are tightly coupled to their actions and the case statements, so there is no need to worry about importing or using the right constant. If you have an action creator, you have its type (via incrementBy.type, or incrementBy().type - the former is compatible with thunks).</p><p>You should always <strong>write at least two tests for each action creator</strong>. One to test how it affects the reducer with <strong>no prior state</strong> (= initial state), and one to test how it interacts with <strong>existing state</strong> (of the same slice, obviously). (Sometimes you need more than one test for the “with state” scenario.)</p><p>Using createPopulatedState you can simplify your &quot;with existing state&quot; action creator test cases.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/87a8fb50793aac6d2195c230dde42f55/href">https://medium.com/media/87a8fb50793aac6d2195c230dde42f55/href</a></iframe><p>And best of all,<strong> if your action creator tests pass</strong>, you know that they change the state the way you intended. At this point, it’s okay to <strong>use them in your selector tests</strong> to set up state. You also want to <strong>test the selectors with your </strong><strong>rootReducer</strong>. (For local state with useReducer, you replace rootReducer with the reducer you&#39;re testing.) Using selectors in conjunction with action creators and your rootReducer gives you confidence that:</p><ol><li>You hooked up your reducer to your rootReducer, and you did it correctly.</li><li>Your action creators work when interacting with each other (- if you use several to set up the test).</li><li>Your selectors return the correct value.</li></ol><p>You should <strong>test selectors with at least two cases</strong>, too: One with <strong>default state </strong>and one with the <strong>desired populated state</strong>. For the default state, you can call the rootReducer without arguments to get the global initial state. And again, if you can&#39;t call your rootReducer without arguments to produce the default state (for example because you&#39;re using <a href="https://github.com/rt2zz/redux-persist">Redux Persist</a>), you can create and export the rootState from your root-reducer.js file.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/77a6ef48e6d522de9a61e808dfbc8503/href">https://medium.com/media/77a6ef48e6d522de9a61e808dfbc8503/href</a></iframe><p>To test the selector’s behavior with state, you can <strong>create an </strong><strong>actions array and </strong><strong>reduce over it</strong> to build up the state. This is an excellent technique because it works well even if your reducer depends on several pieces of state controlled by multiple actions (and slices e.g. setOffset).</p><p>If you liked this article, you might want to follow me on Twitter and checkout <a href="https://janhesters.com/">janhesters.com</a> because there is more.</p><p><a href="https://janhesters.com/"><em>Jan Hesters</em></a><em> is a developer at </em><a href="https://hopin.to/"><em>Hopin</em></a><em>. After studying physics, he ventured into the startup world and became a programmer. He is specialized in React and Node and has a strong passion for blockchain.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2df09bb6ba61" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Master Higher-Order Components in React Today]]></title>
            <link>https://medium.com/@jan.hesters/master-higher-order-components-in-react-today-db1075df4617?source=rss-bf465a0206e8------2</link>
            <guid isPermaLink="false">https://medium.com/p/db1075df4617</guid>
            <category><![CDATA[higher-order-function]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[higher-order-components]]></category>
            <category><![CDATA[reactjs]]></category>
            <dc:creator><![CDATA[Jan Hesters]]></dc:creator>
            <pubDate>Thu, 06 Feb 2020 19:44:57 GMT</pubDate>
            <atom:updated>2024-10-02T19:26:13.953Z</atom:updated>
            <content:encoded><![CDATA[<h3>Higher Order Components are Misunderstood in React</h3><p>When you interview 100s of “senior” <a href="https://www.reactsquad.io/hire-react-developers">React developers</a>, you’d be surprised how many fail to answer this simple question:</p><p><strong>“What is a higher-order component?”</strong></p><p>And even less can answer the follow-up question: <strong>“Why do higher-order components in React exist?”</strong></p><p>In other words: <strong>“Did any React team member consciously create higher-order components as a concept and put them into React?”</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ipovXuyKwlPoF53_ZBawjQ.jpeg" /></figure><p>In this article, and the video below, you’ll find out the <strong><em>correct</em></strong> answers to these questions, and learn everything you need to know about HOCs.</p><p><strong>Note:</strong> Make sure you understand <a href="https://janhesters.com/arrow-functions">arrow functions</a> and the basics of <a href="https://react.dev/learn">React</a>.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FFtPv5s7O2x8%3Fstart%3D1%26feature%3Doembed%26start%3D1&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DFtPv5s7O2x8&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FFtPv5s7O2x8%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/2125ff0a4b7b2f15d26389595dd7ca2c/href">https://medium.com/media/2125ff0a4b7b2f15d26389595dd7ca2c/href</a></iframe><h3>Abstract</h3><p>You’re first going to see the formal definition of HOCs and through the rest of this article you’ll understand the theory behind it. A Higher-Order component is a function that takes a component and returns a new component.</p><p>The <a href="https://reactjs.org/docs/higher-order-components.html">React docs</a> further state:</p><blockquote><em>“A higher-order component (HOC) is an advanced technique in React for reusing component logic. HOCs are not part of the React API, per se. They are a pattern that emerges from React’s compositional nature.”</em></blockquote><p>The theory behind HOCs comes from …</p><h3>Function Composition</h3><p>In mathematics, <a href="https://en.wikipedia.org/wiki/Function_composition">function composition</a> is the act of combining functions to form a new function or a result, by applying one function to the result of another.</p><p>In JavaScript, this looks this:</p><pre>const inc = n =&gt; n + 1; // f const double = n =&gt; n * 2; // g </pre><pre>// h(x) = (f ∘ g)(x) = f(g(x))  </pre><pre>const doubleThenInc = x =&gt; inc(double(x)); // h</pre><p>Notice how you assign the combined functions to a new variable called doubleThenInc, which you can do because JavaScript has <strong>first-class</strong> functions.</p><p>You can learn more about first-class functions in <a href="https://www.reactsquad.io/blog/usecallback-vs-usememo">this article</a>, which also explains the difference between useCallback and useMemo.</p><p>A programming language has first-class functions if it allows you to assign functions to variables.</p><p>You can abstract the composition to combine any two functions:</p><pre>const compose2 = (f, g) =&gt; x =&gt; f(g(x));<br><br>const doubleThenInc2 = compose2(inc, double);</pre><p>You omit the argument x in the definition of doubleThenInc2. This means doubleThenInc2 is defined point-free, which is when you define a function without mentioning its arguments.</p><pre>const doubleThenInc = x =&gt; inc(double(x)); // mentions X 👉 pointed<br>const doubleThenInc2 = compose2(inc, double); // point-free</pre><p>If you want to compose an arbitrary amount of functions, you need to <a href="https://medium.com/javascript-scene/curry-and-function-composition-2c208d774983">generalize the composition</a> function.</p><pre>const compose = (...fns) =&gt; x =&gt; fns.reduceRight((y, f) =&gt; f(y), x);<br><br>const square = n =&gt; n * n;<br><br>const doubleThenInc3 = compose(inc, double);<br>const doulbeThenIncThenSquare = compose(square, inc, double);</pre><p>More sophisticated versions of the compose function are frequently exposed by libraries that leverage HOCs such as Redux and Apollo.</p><p>The arguments and return values of functions have to line up to compose them. For example, you can’t compose a function that accepts an object and returns a string with a function that receives an array and returns a number.</p><pre>// (number, number) =&gt; number[]<br>const echo = (value, times) =&gt; Array(times).fill(value);<br>// number[] =&gt; number[]<br>const doubleMap = array =&gt; array.map(x =&gt; x * 2);<br><br>// Correct composition. ✅<br>const echoAndDoubleMap = compose(doubleMap, echo);<br><br>console.log(echoAndDoubleMap(3, 4)); // [6, 6, 6, 6]<br><br>// Incorrect composition that will throw an error. ❌<br>const wrongOrder = compose(echo, doubleMap);<br><br>try {<br>  // This will fail because doubleMap expects an array,<br>  // instead of two numbers.<br>  console.log(wrongOrder(3, 4));<br>} catch (error) {<br>  console.error(&quot;Error:&quot;, error.message); // Error: array.map is not a function<br>}</pre><p>Since inc and double both take and return numbers, you can compose them in any order.</p><pre>// Composition that doubles then increments. ✅<br>const doubleThenInc = compose(inc, double);<br>console.log(doubleThenInc(3)); // 7<br><br>// Composition that increments then doubles. ✅<br>const incThenDouble = compose(double, inc);<br>console.log(incThenDouble(3)); 8</pre><p>Additionally, compose2 and compose are <strong>higher-order functions</strong>.</p><p>A higher-order function is a function that either receives or returns a function or does both.</p><pre>const multiply = multiplier =&gt; multiplicant =&gt; multiplier * multiplicant;<br>const double = multiply(2);<br><br>const map = f =&gt; arr =&gt; arr.map(f);<br><br>const doubleMap = map(double);<br><br>const numbers = [1, 2, 3];<br><br>doubleMap(numbers); // [2, 4, 6]</pre><ul><li>multiply IS a higher-order function because it takes in a number and returns a function.</li><li>double IS NOT a higher-order function because it neither receives nor returns a function. It is defined point-free.</li><li>map IS a higher-order function because it both accepts and returns a function.</li><li>doubleMap IS NOT a higher-order function because it neither receives nor returns a function. It is defined point-free.</li></ul><p>React components can either be functions or classes.</p><pre>import { Component } from &#39;react&#39;<br><br>function MyFunctionComponent() {<br>  return &lt;div&gt;Function&lt;/div&gt;<br>}<br><br>class MyClassComponent extends Component {<br>  render() {<br>    return &lt;div&gt;Class&lt;/div&gt;<br>  }<br>}</pre><p>In JavaScript, <a href="https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/es6%20%26%20beyond/ch3.md#classes">the </a><a href="https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/es6%20%26%20beyond/ch3.md#classes">class keyword</a> is essentially a wrapper for the function keyword and handles prototypal inheritance. In other words, classes compile to constructor functions.</p><p>Therefore, since all components are functions in React and JavaScript has higher-order functions, you get HOCs for free. That is what the docs mean when they say HOCs “are a pattern that emerges from React’s compositional nature.”</p><p>Now you should understand the basic definition of HOCs:</p><blockquote><em>A Higher-Order component is a function that takes a component and returns a new component.</em></blockquote><p>Any function whose input and output is a React component is a HOC.</p><h3>HOCs by Example</h3><p>You probably want to see what a higher-order component looks like. Follow the rest of this tutorial to write your own using TDD. You’re going to use <a href="https://vitest.dev/">Vitest</a> with <a href="https://testing-library.com/docs/react-testing-library/intro/">React Testing Library</a> to write the tests.</p><p>You can deduce two requirements from the definition of a higher-order component:</p><ol><li>HOCs are functions.</li><li>HOCs take a component and return a component.</li></ol><p>You can capture these requirements in a unit test.</p><pre>import { render, screen } from &#39;@testing-library/react&#39;;<br>import { describe, expect, test } from &#39;vitest&#39;;<br><br>import myHOC from &#39;./my-hoc&#39;;<br><br>function MyComponent({ title = &#39;Hello&#39; }) {<br>  return &lt;p&gt;{title}&lt;/p&gt;;<br>}<br><br>describe(&#39;myHOC&#39;, () =&gt; {<br>  test(&#39;given a component: returns the component with a default title&#39;, () =&gt; {<br>    const WrappedComponent = myHOC(MyComponent);<br><br>    render(&lt;WrappedComponent /&gt;);<br><br>    expect(screen.getByText(&#39;Hello&#39;)).toHaveTextContent(&#39;Hello&#39;);<br>  });<br>});</pre><p>The test checks both requirements because when this test passes, you can logically deduce that your HOC is a function and that it returns a component without spelling out those requirements explicitly. If the HOC were <strong><em>not</em></strong> a function and you tried to call it, it would throw, and your unit test would fail with a clear stack trace. Likewise, the test renders the return value of the HOC, which ensures it is a React component.</p><p>Notice how you did NOT test for typeof function here. Unit tests which only test types are an anti-pattern. It&#39;s redundant with simply calling the function and checking its output value. In general, type checks are redundant with well-written unit tests. This is why unit tests can catch most type errors, without the need for additional measures like type annotations (though annotations and type inference can still be useful to enable IDE tooling).</p><p>You can get the test to pass by making your HOC the <a href="https://en.wikipedia.org/wiki/Identity_function">identity function</a>.</p><pre>export default Component =&gt; Component;</pre><p>Your test result should now look like this.</p><pre>✓ app/my-hoc.test.jsx (1)<br>   ✓ myHOC (1)<br>     ✓ given a component: returns the component with a default title<br><br> Test Files  1 passed (1)<br>      Tests  1 passed (1)<br>   Start at  16:07:11<br>   Duration  128ms<br><br><br> PASS  Waiting for file changes...<br>       press h to show help, press q to quit</pre><h3>Why HOCs?</h3><p>Your current HOC does nothing. And you’re going to change that, soon.</p><p>In general, <strong>HOCs excel at abstracting logic or styling.</strong> They allow you to avoid unnecessary code duplication. If you find yourself repeating certain JSX or logic patterns in your component, you might be able to abstract them away using HOCs.</p><p>For example, if you have a page for your web site or a screen for your React Native app, most pages or screens have the same layout. They all share elements such as headers, footers or formatting containers.</p><h3>Making Your HOC Useful</h3><p>You can add styling abilities to our HOC and call it withLayout instead of MyHOC.</p><p>Start by adding a test that verifies that your HOC adds a layout to your component.</p><pre>import { render, screen } from &#39;@testing-library/react&#39;;<br>import { describe, expect, test } from &#39;vitest&#39;;<br><br>import withLayout from &#39;./with-layout&#39;;<br><br>function MyComponent({ title = &#39;Hello&#39; }) {<br>  return &lt;p&gt;{title}&lt;/p&gt;;<br>}<br><br>describe(&#39;withLayout&#39;, () =&gt; {<br>  test(&#39;given a component: returns the component with a default title&#39;, () =&gt; {<br>    const WrappedComponent = withLayout(MyComponent);<br><br>    render(&lt;WrappedComponent /&gt;);<br><br>    expect(screen.getByText(&#39;Hello&#39;)).toHaveTextContent(&#39;Hello&#39;);<br>  });<br><br>  test(&#39;given a component: renders the layout around the component&#39;, () =&gt; {<br>    const WrappedComponent = withLayout(MyComponent);<br><br>    render(&lt;WrappedComponent /&gt;);<br><br>    expect(screen.getByRole(&#39;heading&#39;)).toHaveTextContent(/some title/i);<br>    expect(screen.getByRole(&#39;main&#39;)).toContainElement(screen.getByText(&#39;Hello&#39;));<br>    expect(screen.getByRole(&#39;contentinfo&#39;)).toHaveTextContent(/some footer/i);<br>  });<br>});</pre><p>Watch your test fail, then create a layout component.</p><pre>export function Layout({ children }) {<br>  return (<br>    &lt;div&gt;<br>      &lt;header&gt;<br>        &lt;h1&gt;Some Title&lt;/h1&gt;<br>      &lt;/header&gt;<br><br>      &lt;main&gt;<br>        {children}<br>      &lt;/main&gt;<br><br>      &lt;footer&gt;<br>        &lt;p&gt;Some footer&lt;/p&gt;<br>      &lt;/footer&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Layouts can vary depending on the app and framework that you’re using. In React Native app, you find yourself writing similar layout HOCs using <a href="https://reactnavigation.org/docs/en/handling-iphonex.html">React Navigation’s </a><a href="https://reactnavigation.org/docs/en/handling-iphonex.html">&lt;SafeAreaView /&gt;</a>. In a Remix app, you won&#39;t need a layout HOC because you can export a layout component from your root.tsx file.</p><p>Now, make your test pass by using the Layout component in your HOC.</p><pre>import { Layout } from &#39;./layout&#39;;<br><br>export default Component =&gt; () =&gt; (<br>  &lt;Layout&gt;<br>    &lt;Component /&gt;<br>  &lt;/Layout&gt;<br>);</pre><p>Your tests should both pass now.</p><pre>✓ app/with-layout.test.jsx (2)<br>   ✓ withLayout (2)<br>     ✓ given a component: returns the component with a default title<br>     ✓ given a component: renders the layout around the component<br><br> Test Files  1 passed (1)<br>      Tests  2 passed (2)<br>   Start at  16:05:39<br>   Duration  128ms<br><br><br> PASS  Waiting for file changes...<br>       press h to show help, press q to quit</pre><p>Notice how the withLayout HOC now takes in a component and then returns a function because before this change it actually was NOT a higher-order component.</p><p>This also shows the most common misconception about HOCs. Many developers answer the question of “What is a higher-order component” with “it’s a <em>component</em> that takes in a React component and returns it”.</p><p>They probably think of something like this.</p><pre>// Wrong! ❌<br>function NotAHigherOrderComponent({ Component }) {<br>  return (<br>    &lt;div&gt;<br>      &lt;h1&gt;Header added by NotAHigherOrderComponent&lt;/h1&gt;<br>      &lt;Component /&gt;<br>    &lt;/div&gt;<br>  );<br>}<br><br>function MyComponent() {<br>  return &lt;p&gt;Hello, I am a regular component.&lt;/p&gt;;<br>}<br><br>function App() {<br>  return (<br>    &lt;div&gt;<br>      &lt;NotAHigherOrderComponent Component={MyComponent} /&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>What you see above is a React component that takes in another React component as a prop.</p><p>But that’s is NOT a higher-order component because HOCs are functions and NOT components. You can NOT render a HOC.</p><p>Looking back at the your withLayout HOC, it contains a bug. Can you spot it?</p><p>If not, that is okay. You can write the following test to expose the error.</p><pre>describe(&#39;withLayout&#39;, () =&gt; {<br>  // ... your other tests<br><br>  test(&#39;given props for the wrapped component: passes on the props to the wrapped component&#39;, () =&gt; {<br>    const WrappedComponent = withLayout(MyComponent);<br>    const customTitle = &#39;Custom Title&#39;;<br><br>    render(&lt;WrappedComponent title={customTitle} /&gt;);<br><br>    expect(screen.getByText(customTitle)).toHaveTextContent(customTitle);<br>  });<br>});</pre><p>The new test fails.</p><pre>❯ app/with-layout.test.jsx (3)<br>   ❯ withLayout (3)<br>     ✓ given a component: returns the component with a default title<br>     ✓ given a component: renders the layout around the component<br>     × given props for the wrapped component: passes on the props to the wrapped component</pre><p>Now your tests pass because your HOC correctly passes on the props to the wrapped component.</p><pre>✓ app/with-layout.test.jsx (3)<br>   ✓ withLayout (3)<br>     ✓ given a component: returns the component with a default title<br>     ✓ given a component: renders the layout around the component<br>     ✓ given props for the wrapped component: passes on the props to the wrapped component<br><br> Test Files  1 passed (1)<br>      Tests  3 passed (3)<br>   Start at  16:55:45<br>   Duration  139ms<br><br><br> PASS  Waiting for file changes...<br>       press h to show help, press q to quit</pre><p>However, the abstraction capabilities of HOCs wouldn’t be as useful if they didn’t have another key feature. <a href="https://slack-redir.net/link?url=https%3A%2F%2Fmedium.com%2Fjavascript-scene%2Fdo-react-hooks-replace-higher-order-components-hocs-7ae4a08b7b58">Eric Elliott describes</a> it like this:</p><blockquote><em>“The primary benefit of HOCs is not what they enable (there are other ways to do it); it’s how they compose together at the page root level.”</em></blockquote><p>In other words, <strong>the key to using HOCs well is to know how and when you want to compose them</strong>. You you write a test to demonstrate the “how”. <em>Spoiler:</em> it is fundamentally function composition.</p><p>Here is a test that shows how you compose HOCs.</p><pre>import { render, screen } from &#39;@testing-library/react&#39;;<br>import { describe, expect, test } from &#39;vitest&#39;;<br><br>import withLayout from &#39;./with-layout&#39;;<br><br>function MyComponent({ title }) {<br>  return &lt;p&gt;{title}&lt;/p&gt;;<br>}<br><br>describe(&#39;withLayout&#39;, () =&gt; {<br>  // ... your other tests<br><br>  test(&#39;given used in composition with other HOCs: passes on the props of the other HOCs&#39;, () =&gt; {<br>    const compose = (...fns) =&gt; x =&gt; fns.reduceRight((y, f) =&gt; f(y), x);<br>    const withTitle = Component =&gt; props =&gt; (<br>      &lt;Component title=&quot;foo&quot; {...props} /&gt;<br>    );<br>    const ComposedComponent = compose(<br>      withLayout,<br>      withTitle<br>    )(MyComponent);<br><br>    render(&lt;ComposedComponent /&gt;);<br><br>    expect(screen.getByText(&#39;foo&#39;)).toHaveTextContent(&#39;foo&#39;);<br>  });<br>});</pre><p>This test already passes.</p><p>You compose withLayout with withTitle. withTitle is a HOC that injects a title prop to a component.</p><h3>Configuring HOCs</h3><p>It is common for HOCs to accept configuration objects. You probably encounter this when using <a href="https://react-redux.js.org/api/connect">React Redux’ </a><a href="https://react-redux.js.org/api/connect">connect</a> with mapStateToProps. (In fact, it accepts two more arguments: mapDispatchToProps and mergeProps.)</p><p>Assume that some pages should render without the header, so you modify your layout component to take in a prop that let’s you show and hide the header.</p><pre>export function Layout({ children, showHeader = true }) {<br>  return (<br>    &lt;div&gt;<br>      {showHeader &amp;&amp; (<br>        &lt;header&gt;<br>          &lt;h1&gt;Some Title&lt;/h1&gt;<br>        &lt;/header&gt;<br>      )}<br><br>      &lt;main&gt;{children}&lt;/main&gt;<br><br>      &lt;footer&gt;<br>        &lt;p&gt;Some footer&lt;/p&gt;<br>      &lt;/footer&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Now write a test that allows you to modify your HOC. You’ll also need to modify your existing tests to accommodate the fact that your HOC now takes in a configuration object.</p><pre>import { render, screen } from &#39;@testing-library/react&#39;;<br>import { describe, expect, test } from &#39;vitest&#39;;<br><br>import withLayout from &#39;./with-layout&#39;;<br><br>function MyComponent({ title = &#39;Hello&#39; }) {<br>  return &lt;p&gt;{title}&lt;/p&gt;;<br>}<br><br>describe(&#39;withLayout&#39;, () =&gt; {<br>  test(&#39;given a component: returns the component with a default title&#39;, () =&gt; {<br>    const WrappedComponent = withLayout()(MyComponent);<br><br>    render(&lt;WrappedComponent /&gt;);<br><br>    expect(screen.getByText(&#39;Hello&#39;)).toHaveTextContent(&#39;Hello&#39;);<br>  });<br><br>  test(&#39;given a component: renders the layout around the component&#39;, () =&gt; {<br>    const WrappedComponent = withLayout()(MyComponent);<br><br>    render(&lt;WrappedComponent /&gt;);<br><br>    expect(screen.getByRole(&#39;heading&#39;)).toHaveTextContent(/some title/i);<br>    expect(screen.getByRole(&#39;main&#39;)).toContainElement(<br>      screen.getByText(&#39;Hello&#39;),<br>    );<br>    expect(screen.getByRole(&#39;contentinfo&#39;)).toHaveTextContent(/some footer/i);<br>  });<br><br>  test(&#39;given props for the wrapped component: passes on the props to the wrapped component&#39;, () =&gt; {<br>    const WrappedComponent = withLayout()(MyComponent);<br>    const customTitle = &#39;Custom Title&#39;;<br><br>    render(&lt;WrappedComponent title={customTitle} /&gt;);<br><br>    expect(screen.getByText(customTitle)).toHaveTextContent(customTitle);<br>  });<br><br>  test(&#39;given used in composition with other HOCs: passes on the props of the other HOCs&#39;, () =&gt; {<br>    const compose =<br>      (...fns) =&gt;<br>      x =&gt;<br>        fns.reduceRight((y, f) =&gt; f(y), x);<br>    const withTitle = Component =&gt; props =&gt; (<br>      &lt;Component title=&quot;foo&quot; {...props} /&gt;<br>    );<br>    const ComposedComponent = compose(withLayout(), withTitle)(MyComponent);<br><br>    render(&lt;ComposedComponent /&gt;);<br><br>    expect(screen.getByText(&#39;foo&#39;)).toHaveTextContent(&#39;foo&#39;);<br>  });<br><br>  test(&#39;given a component and NOT rendering the header: does NOT render the header&#39;, () =&gt; {<br>    const WrappedComponent = withLayout({ showHeader: false })(MyComponent);<br><br>    render(&lt;WrappedComponent /&gt;);<br><br>    expect(screen.queryByRole(&#39;heading&#39;)).toBeNull();<br>  });<br>});</pre><p>Watch all your tests fail because your component still lacks the configuration object. Add it to make them pass.</p><pre>import { Layout } from &#39;./layout&#39;;<br><br>export default ({ showHeader = true } = {}) =&gt;<br>  Component =&gt;<br>  props =&gt; (<br>    &lt;Layout showHeader={showHeader}&gt;<br>      &lt;Component {...props} /&gt;<br>    &lt;/Layout&gt;<br>  );</pre><p>To answer the question of <em>when</em> to use composition for HOCs, remember what I told you learned earlier. HOCs are excellent if you want to abstract away common logic between many components. You chose to give your function a layout functionality because that is one area that most screens of your application will share. Using compose you can define a HOC that you can use to wrap all your pages with.</p><h3>Real-World Example</h3><p>Here is a real-world example of a SignInForm container component. See if you understand it, then read the explanation to check if you were correct.</p><pre>import { withFormik } from &#39;formik&#39;;<br>import compose from &#39;ramda/src/compose.js&#39;;<br>import { connect } from &#39;react-redux&#39;;<br>import { withRouter } from &#39;react-router&#39;;<br>import SignInComponent from &#39;./sign-in-form-component.js&#39;;<br>import { isAuthenticating, signIn } from &#39;./user-authentication-reducer.js&#39;;<br>import { signInValidationSchema } from &#39;./validation-schema.js&#39;;<br><br>const initialFormValues = { email: &#39;&#39;, password: &#39;&#39; };<br><br>const mapStateToProps = state =&gt; ({ loading: isAuthenticating(state) });<br><br>const formikConfig = {<br>  handleSubmit: ({ email, password }, { props: { signIn } }) =&gt; {<br>    signIn({ email, password });<br>  },<br>  mapPropsToValues: () =&gt; initialFormValues,<br>  validationSchema: signInValidationSchema,<br>};<br><br>export default compose(<br>  withRouter,<br>  connect(<br>    mapStateToProps,<br>    { signIn }<br>  ),<br>  withFormik(formikConfig),<br>)(SignInComponent);</pre><p>In the example above, you composed 3 different HOCs.</p><ol><li>withRouter is a HOC from React Router DOM. It injects the history object, which you can use to navigate to the password reset screen, when the user clicks the &quot;Forgot Password&quot; button.</li><li>connect is a HOC from React Redux. You use it to connect your component to your Redux store. You inject the loading prop and the signIn action creator.</li><li>withFormik is a HOC from Formik. Formik let&#39;s you control local form state and handles form validation for you.</li></ol><p>Sometimes you need to <a href="https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over">copy over</a> static properties such as propTypes, defaultProps and <a href="https://nextjs.org/learn/basics/fetching-data-for-pages">getStaticProps (if you are using Next.js)</a> from the inner component to the resulting component. Here is a Higher-Order HOC (a function that returns a HOC), which does this for you.</p><pre>import hoistNonReactStatics from &#39;hoist-non-react-statics&#39;;<br><br>const hoistStatics = higherOrderComponent =&gt; Component =&gt; {<br> const WrappedComponent = higherOrderComponent(Component);<br> hoistNonReactStatics(WrappedComponent, Component);<br> return WrappedComponent;<br>};</pre><p><strong>BTW:</strong> <a href="https://reactjs.org/docs/higher-order-components.html#refs-arent-passed-through">When using HOCs you need to treat </a><a href="https://reactjs.org/docs/higher-order-components.html#refs-arent-passed-through">refs special, too.</a> If you need to pass refs through a component hierarchy, you should probably be using a hook for the ref instead of a HOC.</p><blockquote>Need to hire a senior React developer? <a href="https://www.reactsquad.io/schedule-a-call">Book a call</a> with ReactSquad to get matched with one in 48 hours.</blockquote><h3>HOC Composition</h3><p>You know from function composition that you can only compose functions whose types line up. Similarly, you need to pay attention to the order in which you compose your HOCs. One HOC can inject props that another might depend on. If the one that depends on the props gets injected before the prop injecting HOC, your app might break.</p><pre>const formatTitleProp = ({ title, ...otherProps }) =&gt; ({<br>  title: title.toUpperCase(),<br>  ...otherProps,<br>});<br><br>const withTitle = Component =&gt; props =&gt; &lt;Component title=&quot;Hello&quot; {...props} /&gt;;<br><br>const withFormattedTitle = Component =&gt; props =&gt; (<br>  &lt;Component {...formatTitleProp(props)} /&gt;<br>);<br><br>const breakingApp = compose(withFormattedTitle, withTitle)(App); // 🔴 Breaks!<br><br>const workingApp = compose(withTitle, withFormattedTitle)(App); // ✅ Correct order!</pre><p>If you switch the order of HOCs in the real-world example above, it will break, too. withFormik(formikConfig) depends on signIn being defined, and transformProps depends on both history and the formikBag props.</p><p>HOCs with implicit dependencies on each other may be a code smell. In some cases, it may be better to make those dependencies explicit, by importing the shared functionality into the components that need them, or taking the dependency as a configuration parameter of the HOC. It’s probably ok to implicitly depend on something that’s pretty universal to all your pages, such as your store provider.</p><p>Now you can confidently:</p><ul><li>Identify HOCs in your codebase.</li><li>Write your own HOCs to abstract common logic and styling across components.</li><li>Compose multiple HOCs to achieve more complex functionalities.</li></ul><p>For a deeper dive into React, subscribe to <a href="https://www.youtube.com/@janhesters">my YouTube channel</a>!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=db1075df4617" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Multiple Environments with AWS Amplify]]></title>
            <link>https://medium.com/@jan.hesters/multiple-environments-with-aws-amplify-8ef98e51e896?source=rss-bf465a0206e8------2</link>
            <guid isPermaLink="false">https://medium.com/p/8ef98e51e896</guid>
            <category><![CDATA[team]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[aws-amplify]]></category>
            <category><![CDATA[git]]></category>
            <category><![CDATA[environment]]></category>
            <dc:creator><![CDATA[Jan Hesters]]></dc:creator>
            <pubDate>Tue, 27 Aug 2019 11:33:08 GMT</pubDate>
            <atom:updated>2019-08-27T11:33:08.584Z</atom:updated>
            <content:encoded><![CDATA[<p>In this tutorial, you are going to learn <strong>how to use Amplify’s </strong><strong>env command</strong>. We will walk through an example where two people collaborate on an Amplify project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QjG2eK84QRRDs7M4t7ZuHQ.jpeg" /></figure><p><strong>Note: </strong>This article was first posted on <a href="https://janhesters.com/">my blog</a>. I publish each article two weeks later on Medium. You might want to <a href="https://janhesters.com/newsletter-announcement/">subscribe to my newsletter</a> because you will get content like this earlier! 💌</p><p><strong>Another note:</strong> This article is a tutorial for developers who are familiar with the basics of Amplify. Do you want to learn how to accelerate the creation of your projects using Amplify 🚀? I recommend beginners to check out <a href="https://twitter.com/dabit3">Nader Dabit</a>’s <a href="https://egghead.io/courses/building-serverless-web-applications-with-react-aws-amplify">free course on egghead</a>, or Amplify’s <a href="https://aws-amplify.github.io/docs/js/start">‘Getting Started’</a>.</p><p>If you use Amplify in your team, you will have to know how to use <a href="https://aws-amplify.github.io/docs/cli-toolchain/quickstart#environments--teams">the </a><a href="https://aws-amplify.github.io/docs/cli-toolchain/quickstart#environments--teams">env command</a> properly. Otherwise, your collaboration will be a mess. Done right, Amplify allows easy and effective collaboration on projects.</p><p>There are two ways you can share an Amplify project.</p><ol><li>Each team member works <strong>in their own “sandbox”</strong> aka. their local version of the environment.</li><li>The team <strong>shares the same development environment.</strong></li></ol><p>The first approach is recommended. Let’s walk through each procedure, so you understand both and can decide which you prefer. You need to be <a href="https://egghead.io/courses/practical-git-for-everyday-professional-use">familiar with Git</a> to follow this tutorial.</p><h3>1. Sandboxes</h3><p>We start with the sandbox approach. Create a folder for this tutorial called multiple-env/.</p><pre>mkdir multiple-env &amp;&amp; cd multiple-env</pre><p>Initialize the React project.</p><pre>npx create-react-app env-one &amp;&amp; cd env-one</pre><p><a href="https://github.com/facebook/create-react-app">Create React App</a> automatically initializes a Git repository for us. The folder layout should <em>later</em> look like this.</p><pre>└──multiple-env/<br>   ├── env-one/<br>   └── env-two/</pre><p>Imagine env-two/ being on your co-worker&#39;s computer. Here we&#39;ll use the two apps on your computer to simulate multiple environments.</p><h3>1. a) You</h3><p>Initialize Amplify in your first environment and add authentication. <strong>Note:</strong> In some of these code examples, there will be <em>several CLI commands</em> that you have to run. Remember to <strong><em>double-check that you catch all</em></strong> and don’t miss any.</p><pre>amplify initNote: It is recommended to run this command from the root of your app directory<br>? Enter a name for the project<br>env-one<br>? Enter a name for the environmentprod? Choose your default editor:<br>Visual Studio Code<br>? Choose the type of app that you\&#39;re building<br>javascript<br>Please tell us about your project<br>? What javascript framework are you using<br>react<br>? Source Directory Path:<br>src<br>? Distribution Directory Path:<br>build<br>? Build Command:<br>npm run-script build<br>? Start Command:<br>npm run-script start<br>Using default provider  awscloudformation<br><br>For more information on AWS Profiles, see:<br>https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html<br><br>? Do you want to use an AWS profile?<br>Yes<br>? Please choose the profile you want to use<br>your-amplify-cli-user<br><br>✔ Successfully created initial AWS cloud resources for deployments.<br>✔ Initialized provider successfully.<br>Initialized your environment successfully.<br><br>Your project has been successfully initialized and connected to the cloud!<br><br>amplify add authDo you want to use the default authentication and security configuration?<br>Default configuration<br>Warning: you will not be able to edit these selections.<br>How do you want users to be able to sign in?<br>Email<br>Do you want to configure advanced settings?<br>No, I am done.</pre><p>Nothing fancy going on here. Just make sure to name the first environment prod, master or something along those lines.</p><p>Push your changes to the cloud.</p><pre>amplify push</pre><p>If you run amplify status, you will see that your changes have been deployed.</p><pre>amplify status<br><br>Current Environment: prod<br><br>| Category | Resource name  | Operation | Provider plugin   |<br>| -------- | -------------- | --------- | ----------------- |<br>| Auth     | envoneauth | No Change | awscloudformation |</pre><p>Add the changes to git.</p><pre>git add --all &amp;&amp; git commit -m &quot;Initialize Amplify and add authentication.&quot;</pre><p>Set up a remote repository on GitHub or your version control provider of choice and upload your files.</p><pre>git remote add origin https://github.com/janhesters/envtutorial.git<br>git push -u origin master</pre><h3>1. b) Your Co-Worker</h3><p>You might want to use a second editor and terminal window to simulate the collaboration.</p><p>Clone the remote repository.</p><pre>git clone https://github.com/janhesters/envtutorial.git env-two &amp;&amp; cd env-two &amp;&amp; yarn</pre><p>If you run amplify status now, it will throw an error.</p><pre>amplify status<br><br>Current Environment: undefined</pre><p>Initialize the master branch using the prod environment.</p><pre>amplify init<br>Note: It is recommended to run this command from the root of your app directory<br>? Do you want to use an existing environment?<br>Yes<br>? Choose the environment you would like to use:<br>prod<br>? Choose your default editor:<br>Visual Studio Code<br>Using default provider  awscloudformation<br><br>For more information on AWS Profiles, see:<br>https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html<br><br>? Do you want to use an AWS profile?<br>Yes<br>? Please choose the profile you want to use<br>your-amplify-cli-user<br><br>✔ Initialized provider successfully.<br>Initialized your environment successfully.<br><br>Your project has been successfully initialized and connected to the cloud!</pre><p>In a real-world scenario, your collaborators might have different CLI profiles. Make sure everyone has the permissions to access the relevant resources.</p><p>Now, your co-worker has the environment, too.</p><pre>amplify status<br><br>Current Environment: prod<br><br>| Category | Resource name | Operation | Provider plugin   |<br>| -------- | ------------- | --------- | ----------------- |<br>| Auth     | envoneauth    | No Change | awscloudformation |</pre><h3>1. c) You</h3><p>Change back to env-one/. We want to add another feature.</p><p><strong>Amplify environments work </strong><a href="https://aws-amplify.github.io/docs/cli-toolchain/quickstart#quick-tips"><strong>hand in hand</strong></a><strong> with Git.</strong> We can create our sandbox by creating a new git branch, and running amplify init within it.</p><pre>git checkout -b sandbox &amp;&amp; amplify init<br>Switched to a new branch &#39;sandbox&#39;<br>Note: It is recommended to run this command from the root of your app directory<br>? Do you want to use an existing environment?<br>No<br>? Enter a name for the environment<br>sandbox<br>Using default provider awscloudformation<br><br>For more information on AWS Profiles, see:<br>https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html<br><br>? Do you want to use an AWS profile?<br>Yes<br>? Please choose the profile you want to use<br>your-amplify-cli-user</pre><p>Typically, you would give the branch a more meaningful name than &quot;sandbox&quot;. Alternatively to amplify init we could&#39;ve also used amplify env add.</p><p>The changes for this environment aren’t deployed yet.</p><pre>amplify status<br><br>Current Environment: sandboxone<br><br>| Category | Resource name | Operation | Provider plugin   |<br>| -------- | ------------- | --------- | ----------------- |<br>| Auth     | envonesandbox | Create    | awscloudformation |</pre><p>Note how it says our current environment is sandboxone. To list all environments run amplify env list.</p><pre>amplify env list<br><br>| Environments |<br>| ------------ |<br>| prod         |<br>| *sandboxone  |</pre><p>In this list, our current environment is marked with a star (*).</p><p>Let’s also add an API to the sandbox and deploy it.</p><pre>amplify add api? Please select from one of the below mentioned services<br>GraphQL<br>? Provide API name:<br>AppSyncEnvOneSandbox<br>? Choose an authorization type for the API<br>Amazon Cognito User Pool<br>Use a Cognito user pool configured as a part of this project<br>? Do you have an annotated GraphQL schema?<br>No<br>? Do you want a guided schema creation?<br>Yes<br>? What best describes your project:<br>Single object with fields (e.g., “Todo” with ID, name, description)<br>? Do you want to edit the schema now?<br>No<br><br>amplify push<br>git add --all &amp;&amp; git commit -m &quot;Add API.&quot;</pre><p>The sandbox now has an API as well as authentication. If we made our changes and want to merge them into the production environment, we can do that by using Git.</p><pre>git checkout master<br>amplify env checkout prod<br>git merge sandbox<br>amplify push<br>git add --all &amp;&amp; git commit -m &quot;Merge API into production.&quot;</pre><p>If you are done using your sandbox environment, you can delete it by running amplify env remove.</p><pre>amplify env remove sandbox<br>? Do you also want to remove all the resources of the environment from the cloud?<br>Yes</pre><h3>1. d) Your Co-Worker</h3><p>Your co-worker can get the changes by running amplify env pull. cd into env-two/ and do that.</p><pre>git pull &amp;&amp; amplify env pull</pre><p>When those commands are done your co-worker is synced up with the master branch.</p><pre>amplify status<br><br>Current Environment: prod<br><br>| Category | Resource name        | Operation | Provider plugin   |<br>| -------- | -------------------- | --------- | ----------------- |<br>| Auth     | envoneauth           | No Change | awscloudformation |<br>| Api      | AppSyncEnvOneSandbox | No Change | awscloudformation |</pre><p>Good job! 👏🏻 Now you know how to collaborate using sandboxes. Next, we’ll explore collaboration with …</p><h3>2. Sharing backends</h3><p>Sharing backends is as easy as using the sandbox. <a href="https://aws-amplify.github.io/docs/cli-toolchain/quickstart#team-members-working-on-their-own-sandbox-environments-recommended">I like to mix both approaches.</a> Meaning each team member develops using their custom sandbox, but before changes get merged into production, they get merged into the development environment.</p><h3>2. a) You</h3><p>Similar to the sandbox approach, create a dev branch with Git and an environment with Amplify in env-one/.</p><pre>git checkout -b dev &amp;&amp; amplify init<br>Switched to a new branch &#39;dev&#39;<br>Note: It is recommended to run this command from the root of your app directory<br>? Do you want to use an existing environment?<br>No<br>? Enter a name for the environment<br>dev<br>Using default provider  awscloudformation<br><br>For more information on AWS Profiles, see:<br>https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html<br><br>? Do you want to use an AWS profile?<br>Yes<br>? Please choose the profile you want to use<br>your-amplify-cli-user</pre><p>We might want to <a href="https://janhesters.com/tracking-and-email-reminders-in-aws-amplify/">track our users using Amplify’s analytics</a>, so let’s add that to our dev branch.</p><pre>amplify add analytics<br>Using service: Pinpoint, provided by: awscloudformation<br>? Provide your pinpoint resource name:<br>AnaliticsDev<br>Adding analytics would add the Auth category to the project if not already added.<br>? Apps need authorization to send analytics events. Do you want to allow guests and unauthenticated users to send analytics events? (we recommend you allow this when getting started)<br>No<br>Authorize only authenticated users to send analytics events. Use &quot;amplify update auth&quot; to modify this behavior.<br>Successfully updated auth resource locally.<br>Successfully added resource AnaliticsDev locally</pre><p>Push your changes to the cloud and GitHub.</p><pre>amplify push<br>git add --all &amp;&amp; git commit -m &quot;Add analytics.&quot;<br>git push --set-upstream origin dev</pre><h3>2. b) You Co-Worker</h3><p>Your co-worker, in env-two/, can get the remote branch by creating his local version and then fetching it.</p><pre>git checkout -b dev &amp;&amp; git fetch origin dev &amp;&amp; git pull origin dev</pre><p>Your co-worker can list all environments to see both the prod and the dev branch.</p><p>Moving code from dev to prod works the same way as we showed earlier when we moved from sandbox to prod.</p><p>If you liked this tutorial, you might want to read <a href="https://janhesters.com/tags/amplify/">my other articles about Amplify</a> because they cover advanced use cases and make you an Amplify expert.</p><h3>Summary</h3><p><strong><em>Remember:</em></strong><em> Use Amplify’s </em><em>env command </em><strong><em>parallel</em></strong><em> to Git.</em></p><p>We understood the two ways — sandbox and shared environments — to use env by looking at an example for each.</p><p>To clean up run amplify delete.</p><p><strong>Note: </strong>This article was first posted on <a href="https://janhesters.com/">my blog</a>. I publish each article two weeks later on Medium. You might want to <a href="https://janhesters.com/newsletter-announcement/">subscribe to my newsletter</a>because you will get content like this earlier! 💌</p><blockquote>Interested in JavaScript, React or Amplify consulting for your team, or mentoring for yourself? Have another opportunity for me? <a href="https://twitter.com/janhesters">My DMs are open.</a></blockquote><p><em>Originally published at </em><a href="https://janhesters.com/multiple-environments-with-aws-amplify/"><em>https://janhesters.com</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8ef98e51e896" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Lambda Triggers & Read-Only Permissions with AWS Amplify]]></title>
            <link>https://medium.com/@jan.hesters/lambda-triggers-read-only-permissions-with-aws-amplify-3d1e30fc2154?source=rss-bf465a0206e8------2</link>
            <guid isPermaLink="false">https://medium.com/p/3d1e30fc2154</guid>
            <category><![CDATA[trigger]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[amplify]]></category>
            <category><![CDATA[readonly]]></category>
            <category><![CDATA[aws]]></category>
            <dc:creator><![CDATA[Jan Hesters]]></dc:creator>
            <pubDate>Tue, 20 Aug 2019 09:16:58 GMT</pubDate>
            <atom:updated>2019-08-20T09:16:58.451Z</atom:updated>
            <content:encoded><![CDATA[<p>In this tutorial, you are going to learn how you can trigger a Lambda function on authentication events with AWS Amplify. We are going to add users to a group to make specific data read-only for everyone but its owner.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7CZnt_ICVvhKGLJEMT-qNA.jpeg" /></figure><p><strong>Note: </strong>This article was first posted on <a href="https://janhesters.com/">my blog</a>. I publish each article two weeks later on Medium. You might want to <a href="https://janhesters.com/newsletter-announcement/">subscribe to my newsletter</a>because you will get content like this earlier! 💌</p><p><strong>Another note:</strong> This article is a tutorial for intermediate developers who are familiar with the basics of Amplify. Do you want to learn how to accelerate the creation of your projects using Amplify 🚀? I recommend beginners to check out <a href="https://twitter.com/dabit3">Nader Dabit</a>’s <a href="https://egghead.io/courses/building-serverless-web-applications-with-react-aws-amplify">free course on egghead</a>, or Amplify’s <a href="https://aws-amplify.github.io/docs/js/start">‘Getting Started’</a>.</p><p>An everyday use case for data is that some data is read-only, and other is not. <a href="https://github.com/aws-amplify/amplify-cli/issues/1277">Currently</a>, the only way- Amplify lets you declare different permissions for users is by <a href="https://aws-amplify.github.io/docs/cli-toolchain/graphql#usage-1">using static groups</a> in Cognito and using attributes on the schema. We are going to leverage <a href="https://aws.amazon.com/blogs/mobile/amplify-framework-adds-supports-for-aws-lambda-triggers-in-auth-and-storage-categories/">Amplify’s new support for Lambda triggers</a> to add users to a static group upon sign up.</p><p>Let’s go! 👨🏻‍💻 I’m going to assume you have a new project ready created with amplify init.</p><p>Start by adding authentication to it. (Make sure you have the latest version of the CLI installed! 👉🏻 npm install -g @aws-amplify/cli)</p><pre>amplify add auth<br>Using service: Cognito, provided by: awscloudformation<br><br> The current configured provider is Amazon Cognito.<br><br>? Do you want to use the default authentication and security configuration?<br>Manual configuration<br>? Select the authentication/authorization services that you want to use:<br>User Sign-Up, Sign-In, connected with AWS IAM controls (Enables per-user Storage features for images or other content, Analytics, and more)<br>? Please provide a friendly name for your resource that will be used to label this category in the project:<br>authtrigger<br>? Please enter a name for your identity pool.<br>triggeridentitypool<br>? Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM)<br>No<br>? Do you want to enable 3rd party authentication providers in your identity pool?<br>No<br>? Please provide a name for your user pool:<br>triggeruserpool<br>Warning: you will not be able to edit these selections.<br>? How do you want users to be able to sign in?<br>Email and Phone Number<br>? Multifactor authentication (MFA) user login options:<br>OFF<br>? Email based user registration/forgot password:<br>Enabled (Requires per-user email entry at registration)<br>? Please specify an email verification subject:<br>Your verification code<br>? Please specify an email verification message:<br>Your verification code is {####}<br>? Do you want to override the default password policy for this User Pool?<br>No<br>Warning: you will not be able to edit these selections.<br>? What attributes are required for signing up? (Press &lt;space&gt; to select, &lt;a&gt; to toggle all, &lt;i&gt; to invert selection)<br>Email<br>? Specify the app\&#39;s refresh token expiration period (in days):<br>30<br>? Do you want to specify the user attributes this app can read and write?<br>No<br>? Do you want to enable any of the following capabilities?Add User to Group? Do you want to use an OAuth flow?No? Do you want to configure Lambda Triggers for Cognito?Yes? Which triggers do you want to enable for Cognito (Press &lt;space&gt; to select, &lt;a&gt; to toggle all, &lt;i&gt; to invert selection)Post Confirmation? What functionality do you want to use for Post Confirmation (Press &lt;space&gt; to select, &lt;a&gt; to toggle all, &lt;i&gt; to invert selection)Add User To Group? Enter the name of the group to which users will be added.everyoneSuccesfully added the Lambda function locally? Do you want to edit your add-to-group function now?NoSuccessfully added resource authtrigger locally</pre><p>The steps are pretty self-explanatory. We define a Lambda function that triggers after a user confirms her/his sign-up and add the user to the everyone group.</p><p>Next, add an API.</p><pre>amplify add api<br>? Please select from one of the below mentioned services<br>GraphQL<br>? Provide API name:<br>AppSyncTrigger<br>? Choose an authorization type for the API<br>Amazon Cognito User Pool<br>Use a Cognito user pool configured as a part of this project<br>? Do you have an annotated GraphQL schema?<br>No<br>? Do you want a guided schema creation?<br>Yes<br>? What best describes your project:<br>Single object with fields (e.g., “Todo” with ID, name, description)<br>? Do you want to edit the schema now?<br>Yes</pre><p>Here is how the schema should look like.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8507bfd73bb050f6667895cf0c176be7/href">https://medium.com/media/8507bfd73bb050f6667895cf0c176be7/href</a></iframe><p>Since we didn’t specify any operations for the owner, he is free to execute any query or mutation on the todos he owns. Every other user is only able to use the get and list queries on his todos. The available values for the operations key besides read are create, delete and update. Additionally, we add an optional owner attribute to the todo schema because we use it to verify that we are only allowed to read other users&#39; todos. If you make it mandatory by adding a ! you will have to specify the owner manually when you create the todo. Leaving out the ! allows amplify to populate that field for you automatically.</p><p>Push your changes.</p><pre>amplify push</pre><p>Now, create two users and add a couple of todos for each of them. You don’t have to do that through your code. If you are coding along and want to save time, you can use the Cognito console to create the users and the AppSync console to execute the GraphQL operations. Remember when using the console to add users, the function will NOT be triggered. You will have to add the user to the &quot;everyone&quot; group manually.</p><pre>mutation create {<br>  createTodo(<em>input</em>: { <em>title</em>: &quot;Buy groceries&quot; }) {<br>    id<br>    title<br>    owner<br>  }<br>}</pre><p>If you list the todos, you should see the todos of all users.</p><pre>query list {<br>  listTodos {<br>    items {<br>      id<br>      title<br>      owner<br>    }<br>  }<br>}</pre><p>But if you try to mutate a todo that is not your own, you should get an error.</p><pre>mutation update {<br>  updateTodo(<br>    <em>input</em>: { <em>id</em>: &quot;id-of-other-users-todo&quot;, <em>title</em>: &quot;different title&quot; }<br>  ) {<br>    id<br>    title<br>    owner<br>  }<br>}</pre><p>The error looks something like this.</p><pre>{<br>  &quot;data&quot;: {<br>    &quot;updateTodo&quot;: null<br>  },<br>  &quot;errors&quot;: [<br>    {<br>      &quot;path&quot;: [&quot;updateTodo&quot;],<br>      &quot;data&quot;: {<br>        &quot;id&quot;: &quot;09e9963f-bed8-432c-b55c-8eb798c50967&quot;,<br>        &quot;name&quot;: &quot;Foo&quot;,<br>        &quot;owner&quot;: &quot;eb8a7fa3-463f-4117-aca2-165419cff879&quot;<br>      },<br>      &quot;errorType&quot;: &quot;DynamoDB:ConditionalCheckFailedException&quot;,<br>      &quot;errorInfo&quot;: null,<br>      &quot;locations&quot;: [<br>        {<br>          &quot;line&quot;: 38,<br>          &quot;column&quot;: 3,<br>          &quot;sourceName&quot;: null<br>        }<br>      ],<br>      &quot;message&quot;: &quot;The conditional request failed (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ConditionalCheckFailedException; Request ID: RO6DBNS6K8D7611RSL9HVRTSUVVV4KQNSO5AEMVJF66Q9ASUAAJG)&quot;<br>    }<br>  ]<br>}</pre><p>Very good! 🤓 We implemented read-only permissions, and our users get added to a static Cognito group using Lambda triggers.</p><p><strong>Note: </strong>This article was first posted on <a href="https://janhesters.com/">my blog</a>. I publish each article two weeks later on Medium. You might want to <a href="https://janhesters.com/newsletter-announcement/">subscribe to my newsletter</a>because you will get content like this earlier! 💌</p><p><em>Originally published at </em><a href="https://janhesters.com/lambda-triggers-and-read-only-permissions/"><em>https://janhesters.com</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3d1e30fc2154" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Testing Lambda Functions (feat. Amplify)]]></title>
            <link>https://medium.com/@jan.hesters/testing-lambda-functions-feat-amplify-c03696cd436?source=rss-bf465a0206e8------2</link>
            <guid isPermaLink="false">https://medium.com/p/c03696cd436</guid>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[unit-testing]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[amplify]]></category>
            <dc:creator><![CDATA[Jan Hesters]]></dc:creator>
            <pubDate>Tue, 13 Aug 2019 10:38:09 GMT</pubDate>
            <atom:updated>2019-08-13T10:38:09.201Z</atom:updated>
            <content:encoded><![CDATA[<p>You are going to learn <strong>how to write unit tests for Lambda functions</strong>. We are going to <strong>write integration tests</strong>, too, and <strong>use test-driven development</strong> to write our tests. We will <strong>break our express app into small modules</strong> to make its units composable and testable.</p><figure><a href="https://geromekevin.com/testing-lamba-functions/"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w5DNWesQoDk4ZpiJkiUezA.jpeg" /></a></figure><p><strong>Note: </strong>This article was first posted on <a href="https://janhesters.com/">my blog</a>. I publish each article two weeks later on Medium. You might want to <a href="https://janhesters.com/newsletter-announcement/">subscribe to my newsletter</a> because you will get content like this earlier! 💌</p><p><strong>Another note:</strong> This article is a tutorial for intermediate developers who are familiar with the basics of Amplify or Lambda functions. Do you want to learn how to accelerate the creation of your projects using Amplify 🚀? I recommend beginners to check out <a href="https://twitter.com/dabit3">Nader Dabit</a>’s <a href="https://egghead.io/courses/building-serverless-web-applications-with-react-aws-amplify">free course on egghead</a>, or Amplify’s <a href="https://aws-amplify.github.io/docs/js/start">‘Getting Started’</a>.</p><p>I saw Lambda functions without tests and spaghetti code. In fact, I’m guilty of having written several of those myself. It’s part of the learning process as a developer to get better and write cleaner and more maintainable code as you gain experience.</p><p>You are going to learn the techniques I use to test and simplify Lambda functions. Simplifying means to isolate your program logic into separate, functional units, which makes it more composable and testable. We are going to use <a href="https://github.com/ericelliott/riteway">RITEway</a> for unit tests, <a href="https://github.com/visionmedia/supertest">Supertest</a> for integration tests and <a href="https://aws-amplify.github.io/docs/js/start">AWS Amplify</a> to generate our Lambda functions.</p><p>Why these three tools? RITEway has a genius API that forces your tests to answer <a href="https://medium.com/javascript-scene/what-every-unit-test-needs-f6cd34d9836d">the five questions every unit test must answer</a>. Supertest makes HTTP assertions in Node.js easy. And if you read this article, I won’t have to tell you how amazing Amplify is to manage AWS resources. Just <a href="https://geromekevin.com/">check out the other articles of my blog</a>.</p><h3>Unit Tests</h3><p>I assume you have a project with Amplify initialized ready. Create a new Lambda function using Amplify.</p><pre>amplify add function<br>? Provide a friendly name for your resource to be used as a label for this category in the project:<br>lambdaTest<br>? Provide the AWS Lambda function name:<br>lambdaTest<br>? Choose the function template that you want to use:<br>Serverless express function (Integration with Amazon API Gateway)<br>? Do you want to access other resources created in this project from your Lambda function?<br>Yes<br>? Select the category (Press &lt;space&gt; to select, &lt;a&gt; to toggle all, &lt;i&gt; to invert selection)<br><br>You can access the following resource attributes as environment variables from your Lambda function<br>var environment = process.env.ENV<br>var region = process.env.REGION<br><br>? Do you want to edit the local lambda function now?<br>No</pre><p>Consequently, add RITEway and Supertest alongside <a href="https://www.npmjs.com/package/tap-color">tap-color</a>. The ladder is for formatting our test output so that it looks pretty.</p><pre>cd amplify/backend/function/lambdaTest/src/ &amp;&amp; npm install --save-dev riteway tap-color supertest &amp;&amp; npm i</pre><p>Make sure to add a testing script to your package.json.</p><pre>&quot;test&quot;: &quot;riteway &#39;**/*.test.js&#39; | tap-color&quot;</pre><p>We will need some functional programming helpers to compose our code. Either install a functional library like <a href="https://ramdajs.com/">Ramda</a> or create a file src/fp/index.js and add the following functions.</p><p><strong>Note:</strong> If the following functions scare you, level up by reading <a href="https://mostly-adequate.gitbooks.io/mostly-adequate-guide/">“Professor Frisby’s Mostly Adequate Guide to Functional Programming”</a>. I also explain the basics of currying in <a href="https://janhesters.com/understanding-arrow-functions/">“Understanding Arrow Functions”</a>. Nevertheless, for the sake of this tutorial, it’s okay if you don’t get all of this article’s code. Understanding the techniques is more important than the actual implementation.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dbc656f5220946eb7931726e61b56649/href">https://medium.com/media/dbc656f5220946eb7931726e61b56649/href</a></iframe><p>I’m going to use Ramda. With the help of tap and pipe, we can write applyMiddleware in src/middleware/index.js, which lets us elegantly compose our middleware.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a2e95cdf20bd3f0da55fc5a760c79085/href">https://medium.com/media/a2e95cdf20bd3f0da55fc5a760c79085/href</a></iframe><p><strong>Note:</strong> If you want to learn how you can functionally write your own custom middleware, you might want to read last weeks article <a href="https://janhesters.com/how-to-access-the-user-in-lambda-functions-with-amplify/">“How to Access the User in Lambda Functions (with Amplify)”</a>.</p><p>Using our test frameworks, we want to write some unit tests in src/routes/routes.test.js. We will write one for our &quot;/items&quot; GET route and one for the listener that logs out on which port the server is running on.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/664b2566527cb1fec2be3fa749537dd4/href">https://medium.com/media/664b2566527cb1fec2be3fa749537dd4/href</a></iframe><p>In true TDD fashion, we have to watch the tests fail. Write some dummy functions in src/routes/index.js.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ca3e05465aec82b22718312ca8ef0dde/href">https://medium.com/media/ca3e05465aec82b22718312ca8ef0dde/href</a></iframe><p>Watch the tests fail by running npm test. Afterwards, make the tests pass.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e225c81995de7a013bce41430c4c1789/href">https://medium.com/media/e225c81995de7a013bce41430c4c1789/href</a></iframe><p>Now we can use them in our function. Note how we also deleted the comments and routes generated by Amplify.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/601d8f6cb420485a60991dacfaf6d269/href">https://medium.com/media/601d8f6cb420485a60991dacfaf6d269/href</a></iframe><p>I love how clean this code is 👌🏻. You can locally run your Express server by invoking the Lambda function. But before that, we need to modify src/event.json to ping the /items route with a GET request.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0e143e6d0850bc4f00fe8298d28586c6/href">https://medium.com/media/0e143e6d0850bc4f00fe8298d28586c6/href</a></iframe><p>Moreover, you can pass keys like &quot;query&quot; to simulate URLs with &quot;?&quot; in them and &quot;body&quot; or &quot;headers&quot;.</p><p>Now invoke the function.</p><pre>amplify function invoke lambdaTest<br>Using service: Lambda, provided by: awscloudformation<br>? Provide the name of the script file that contains your handler function:<br>index.js<br>? Provide the name of the handler function to invoke:<br>handler<br><br>Running &quot;lambda_invoke:default&quot; (lambda_invoke) task<br><br>Listening on port 3000.<br>EVENT: {&quot;httpMethod&quot;:&quot;GET&quot;,&quot;path&quot;:&quot;/items&quot;}<br><br>Success!  Message:<br>------------------<br>{<br>    body: &quot;{\&quot;success\&quot;:\&quot;get call succeeded\&quot;,\&quot;items\&quot;:[\&quot;vacuum cleaner\&quot;,\&quot;knife\&quot;,\&quot;computer\&quot;]}&quot;,<br>    headers: {<br>        &quot;access-control-allow-headers&quot;: &quot;Origin, X-Requested-With, Content-Type, Accept&quot;,<br>        &quot;access-control-allow-origin&quot;: &quot;*&quot;,<br>        connection: &quot;close&quot;,<br>        &quot;content-length&quot;: &quot;78&quot;,<br>        &quot;content-type&quot;: &quot;application/json; charset=utf-8&quot;,<br>        date: &quot;Sat, 13 Jul 2019 21:29:34 GMT&quot;,<br>        etag: &quot;W/\&quot;4e-XNTYS3kI7G7czAiqg07A+69mhoQ\&quot;&quot;,<br>        &quot;x-powered-by&quot;: &quot;Express&quot;<br>    },<br>    isBase64Encoded: false,<br>    statusCode: 200<br>}</pre><p>We won’t use these manual tests for the rest of the article. I just wanted to show them to you as another option to try out your functions on the fly. Sometimes you want to do that. However, it’s usually better to have your tests automatically confirm that your code works instead of you doing it manually. Let’s do that using …</p><h3>Integration Tests</h3><p>Testing Lambda functions involves I/O. We mocked res in our unit test for getItems, but generally, you want to avoid mocking because it is a code smell. While I&#39;m going to explain how and why I test what I test, you should read <a href="https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a">&quot;Mocking Is a Code Smell&quot;</a> by Eric Elliott because he explains testing asynchronous code in more detail.</p><p>Let’s rewrite &quot;/items&quot; to be &quot;/addresses&quot; which gets a list of users and returns their addresses. We are going to use the free <a href="https://jsonplaceholder.typicode.com/">JSONPlaceholder</a> API for this.</p><p>To force us to write modular code, we want to write the GET route for /addresses using asyncPipe. A pipe always expects the data types to line up. The function that is passed to the route gets two arguments: request and response. We will lift them into a generic object context via a function we call liftReqRes. Using an object has the advantage that we can pass values through the pipe by attaching them to keys. If you don&#39;t understand this yet, wait until you see the code. The code will clear things up for you.</p><p>We start with the functional test using Supertest in src/app.test.js.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6370c3020634fb82913b03686ff26e6a/href">https://medium.com/media/6370c3020634fb82913b03686ff26e6a/href</a></iframe><p>Rename our route in app.js and routes/index.js to &quot;addresses&quot; and move handleListen to src/index.js. Otherwise, every test would cause our server to run, and the tests wouldn&#39;t stop.</p><p>Here is how app.js looks now.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bbc54d69898019a48a1b7076accb412a/href">https://medium.com/media/bbc54d69898019a48a1b7076accb412a/href</a></iframe><p>And here is src/index.js with app.listen.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2bf6977cceccf9beeec7b4b07da718b6/href">https://medium.com/media/2bf6977cceccf9beeec7b4b07da718b6/href</a></iframe><p>Let’s implement the functions needed to compose getAddresses. First up is liftReqRes.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5fbea22d20d5f4cb0353b3f85dc54e3a/href">https://medium.com/media/5fbea22d20d5f4cb0353b3f85dc54e3a/href</a></iframe><p>Watch the test fail, then make it pass by implementing liftReqRes.</p><pre>const liftReqRes = (request, response) =&gt; ({ request, response });</pre><p>We are going to execute our GET request with axios.get. Install <a href="https://github.com/axios/axios">axios</a> by running npm install axios in your function&#39;s directory. We need to install a library for requests since Lambda functions don&#39;t have fetch. You could also install node-fetch or a polyfill if you prefer fetch over axios.</p><p>If axios.get is successful, the response has the <a href="https://jsonplaceholder.typicode.com/users">following shape</a>:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/485f02da8d109876e878f996c84f63be/href">https://medium.com/media/485f02da8d109876e878f996c84f63be/href</a></iframe><p>Here the data key comes from axios and not from the placeholder API.</p><pre>const getAddressesFromData = R.pipe(<br>  R.prop(&#39;data&#39;),<br>  R.pluck(&#39;address&#39;)<br>);</pre><p>getAddressesFromData expects an object which&#39;s data key contains an array of objects. It returns a new array containing the values that belong to each of these objects&#39; address keys.</p><p>Subsequently, we are going to write the fetch function.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/aa983f6151a87eefdc6da22b57d9dab5/href">https://medium.com/media/aa983f6151a87eefdc6da22b57d9dab5/href</a></iframe><p>We chain getAddressesFromData on our promise using .then. Notice how we also perform a type lift again.</p><p>We’ll also need to return a JSON response to our users. We can do that using res.json.</p><pre>const jsonAddresses = ({ res, addresses }) =&gt; res.json({ addresses });</pre><p>Wait a minute! <strong>Did we write three functions without unit tests?</strong> 😱</p><p>Yes, we did. 😏</p><p>The trick about testing a Lambda function — and async code in general — is to choose what to test and how. Generally, you only need integration tests for functions that have side effects. BUT, if you compose an async function with pure functions, you want to write unit tests for the pure functions. This way, when the integration test fails, but the unit tests pass you at least know where the error is NOT located. If both the integration test and the unit tests fail, the unit tests will identify the error for you.</p><p>fetchAddresses is the function containing the I/O, no unit tests needed here.</p><p>So why did we not test getAddressesFromData and jsonAddresses?</p><p>You can certainly make a solid argument for writing unit tests for them, too. But in this case, I chose not to. getAddressesFromData is composed solely out of well-tested Ramda functions with little specialization. Therefore, there is almost nothing that could go wrong. jsonAddresses, on the other hand, is also very simple and essential for our integration test. If that part fails, it will probably have a distinct error. In conclusion, not much is gained by writing unit tests for these two functions.</p><p>Notice how, depending on your function, you might not want to write any unit tests and just integration tests.</p><p>Now compose these functions to get our getAddresses function.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a9aa1c859adc7d09339e2913471e8287/href">https://medium.com/media/a9aa1c859adc7d09339e2913471e8287/href</a></iframe><p>Run npm test.</p><pre>TAP version 13<br># /addresses<br>Missing x-apigateway-event or x-apigateway-context header(s)<br>ok 1 Given a GET request: should return an array of addresses<br>ok 2 Given a GET request: should return an array of addresses<br># handleListen()<br>ok 3 Given a port of 8000 and a log() function: should call log() with &#39;Listening on port 8000.&#39;<br># liftReqRes<br>ok 4 Given a function, and two arguments (request and response): should lift the arguments into an object and invoke the function with it<br>1..4<br># tests 4<br># pass  4<br># ok</pre><p>Our integration tests and unit tests pass now! 🚀</p><p>If you liked this article, you might also like <a href="https://janhesters.com/how-to-use-aws-appsync-in-lambda-functions/">“How To Use AWS AppSync in Lambda Functions”</a> because you are going to learn how to connect your Lambda functions to your AppSync API, making it more powerful.</p><h3>Summary</h3><p>We learned how to use function composition to make our code more modular. We wrote integration tests with Supertest and unit tests with RITEway for the Lambda function.</p><p><strong>Note: </strong>This article was first posted on <a href="https://janhesters.com/">my blog</a>. I publish each article two weeks later on Medium. You might want to <a href="https://janhesters.com/newsletter-announcement/">subscribe to my newsletter</a> because you will get content like this earlier! 💌</p><p><em>Originally published at </em><a href="https://janhesters.com/testing-lamba-functions/"><em>https://janhesters.com</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c03696cd436" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>