<?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:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Code Frontend]]></title><description><![CDATA[Become a Better Web Developer]]></description><link>https://codefrontend.com/</link><image><url>https://codefrontend.com/favicon.png</url><title>Code Frontend</title><link>https://codefrontend.com/</link></image><generator>Ghost 5.37</generator><lastBuildDate>Sat, 11 Apr 2026 05:28:00 GMT</lastBuildDate><atom:link href="https://codefrontend.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 & KV]]></title><description><![CDATA[Step-by-step instructions for setting up a Shopify app to work with Cloudflare Pages infrastructure.]]></description><link>https://codefrontend.com/deploy-shopify-apps-on-cloudflare/</link><guid isPermaLink="false">65903be376398697f2766043</guid><category><![CDATA[Shopify Apps]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Sun, 31 Dec 2023 16:17:42 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2023/12/featured-image-shopify-remix-cloudflare-app.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://codefrontend.com/content/images/2023/12/featured-image-shopify-remix-cloudflare-app.jpg" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV"><p>Cloudflare has become my go-to platform for hosting Remix and Next.js apps. It&apos;s affordable, offers a generous free tier, and has a strong brand. I use Remix to build a Shopify app, so hosting it on Cloudflare pages seemed like a natural fit.</p><p>However, there aren&apos;t many examples of how to deploy Shopify apps on Cloudflare, and relevant documentation is scarce, so it took me two days to integrate everything and get the local dev environment working smoothly.</p><p>These examples have been invaluable in helping me understand what needed to be done:</p><ul><li><a href="https://github.com/cloudy9101/shopify-remix-cfpages?ref=code-frontend">https://github.com/cloudy9101/shopify-remix-cfpages</a></li><li><a href="https://github.com/rozenmd/d1-drizzle-remix-example?ref=code-frontend">https://github.com/rozenmd/d1-drizzle-remix-example</a></li><li><a href="https://github.com/dan-gamble/cloudflare-workers-remix-shopify?ref=code-frontend">https://github.com/dan-gamble/cloudflare-workers-remix-shopify</a></li></ul><p>Instead of building another template that quickly becomes outdated, I will share my complete setup process step-by-step so you can understand it and adapt it yourself.</p><h2 id="why-use-cloudflare-for-shopify-apps">Why Use Cloudflare for Shopify Apps?</h2><p><a href="https://pages.dev/?ref=code-frontend">Cloudflare Pages</a> offer serverless edge runtime, which lets you distribute your backend code close to your users without the initial cold start delay. This lets your app scale cost-efficiently while staying fast.</p><p>You will want to use a serverless database when you use edge functions. Otherwise, you&apos;re negating any gains from the edge functions. Cloudflare offers an SQLite-based solution for that called <a href="https://developers.cloudflare.com/d1/?ref=code-frontend">D1 Database</a>, which is currently in public beta.</p><p>For storing Shopify sessions, we can use Cloudflare <a href="https://developers.cloudflare.com/kv/?ref=code-frontend">Workers KV</a>, a key-value store that caches data on Cloudflare&apos;s CDN, ensuring extremely quick reads.</p><p>Cloudflare has everything we need to make a Shopify app super fast, and I love that everything is in a single place and uses a single CLI. </p><h2 id="step-0-create-a-partner-account-and-a-development-store">Step 0: Create a Partner Account and a Development Store</h2><p>First, you&apos;ll need to sign up for a Shopify partner account if you don&apos;t already have one:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.shopify.com/partners?ref=code-frontend"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Become a Shopify Partner Today - Shopify Partners</div><div class="kg-bookmark-description">Shopify Partners range from global enterprises to startups and entrepreneurs. Increase your earnings, enhance your skills, and expand your network as part of Shopify&#x2019;s thriving global community of agency, app, consulting, and technology partners.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://cdn.shopify.com/static/shopify-favicon.png" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV"><span class="kg-bookmark-author">Shopify</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cdn.shopify.com/shopifycloud/brochure/assets/partners/partners-social-9d0aa9ce10ddd099215bb908a7a3d2fa5048a119941072260c685e20bd8f1445.jpg" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV"></div></a></figure><p>Then, you&apos;ll need to create a development store through your partner dashboard:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image-1.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1001" height="249" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-1.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-1.png 1000w, https://codefrontend.com/content/images/2023/12/image-1.png 1001w" sizes="(min-width: 720px) 720px"><figcaption>Click &quot;Create development store&quot;</figcaption></figure><p>Just make sure you&apos;re creating a test store for an app:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/12/image-2.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1211" height="1167" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-2.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-2.png 1000w, https://codefrontend.com/content/images/2023/12/image-2.png 1211w" sizes="(min-width: 720px) 720px"></figure><h2 id="step-1-initialize-a-shopify-app-with-remix">Step 1: Initialize a Shopify App with Remix</h2><p>To set up the initial code, you must create a Shopify partner account and follow the steps in the Shopify documentation here: <a href="https://shopify.dev/docs/apps/getting-started/create?ref=code-frontend">https://shopify.dev/docs/apps/getting-started/create</a>.</p><p>The CLI may ask you to authenticate first, but in the end, you should end up with your terminal looking like this:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="971" height="482" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image.png 600w, https://codefrontend.com/content/images/2023/12/image.png 971w" sizes="(min-width: 720px) 720px"><figcaption>Shopify CLI output</figcaption></figure><p>When you run <code>npm run dev</code> for the first time, you will be asked to create a new application and connect it to a development store:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/12/image-3.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="907" height="861" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-3.png 600w, https://codefrontend.com/content/images/2023/12/image-3.png 907w" sizes="(min-width: 720px) 720px"></figure><p>Now quit the development server by pressing <code>q</code> or <code>ctrl+c</code> in your command line. We will customize the build tasks to use Cloudflare&apos;s <a href="https://developers.cloudflare.com/workers/wrangler/?ref=code-frontend">Wrangler</a> for local development.</p><h2 id="step-2-update-config-files-for-cloudflare">Step 2: Update Config Files for Cloudflare</h2><p>We had a choice of using either a Shopify CLI or Remix CLI to bootstrap our application. While the Remix CLI lets you generate the necessary configuration for hosting on Cloudflare, it doesn&apos;t bootstrap a Shopify app.</p><p>I think adding the Cloudflare configuration on top of a bootstrapped Shopify app is easier, and we can use <a href="https://developers.cloudflare.com/pages/framework-guides/deploy-a-remix-site/?ref=code-frontend">the documentation</a> and the <a href="https://github.com/remix-run/remix/tree/main/templates/cloudflare-pages?ref=code-frontend">Remix+Cloudflare Pages template</a> to help us.</p><h3 id="install-dependencies-and-update-packagejson">Install dependencies and update package.json</h3><p>First, get the necessary Cloudflare dependencies from npm:</p><pre><code>npm i @remix-run/cloudflare @remix-run/cloudflare-pages @shopify/shopify-app-session-storage-kv @remix-run/css-bundle
npm i -D @cloudflare/workers-types wrangler</code></pre><p>Then, we need to add these to <code>package.json</code>:</p><pre><code class="language-json">{
  &quot;engines&quot;: {
    &quot;node&quot;: &quot;&gt;=18.0.0&quot;
  },
  &quot;sideEffects&quot;: false,
  &quot;type&quot;: &quot;module&quot;,
  ... 
 }</code></pre><p>These lines are required to run your app on Cloudflare Pages. Additionally, replace the <code>start</code> script with this:</p><pre><code>&quot;start&quot;: &quot;wrangler pages dev ./public --live-reload --kv=SESSION&quot;,</code></pre><p>We will be using <code>wrangler pages dev</code> for local development and <code>--kv=SESSION</code> binds the local storage version of Cloudflare KV to the <code>context.env.SESSION</code> environment variable. We will make sure this binding is the same on prod later on. </p><p>Finally, feel free to delete <code>predev</code>, <code>setup</code> and <code>prisma</code> scripts, as we will be using <a href="https://orm.drizzle.team/?ref=code-frontend">Drizzle ORM</a> instead of Prisma.</p><h3 id="update-typescript-configuration">Update Typescript Configuration</h3><p>We need to ensure our app is compatible with ESModules. Replace your <code>tsconfig.json</code> content with this code:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
  &quot;include&quot;: [&quot;remix.env.d.ts&quot;, &quot;**/*.ts&quot;, &quot;**/*.tsx&quot;],
  &quot;compilerOptions&quot;: {
    &quot;lib&quot;: [&quot;DOM&quot;, &quot;DOM.Iterable&quot;, &quot;ES2022&quot;],
    &quot;strict&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;removeComments&quot;: false,
    &quot;resolveJsonModule&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;allowJs&quot;: true,
    &quot;jsx&quot;: &quot;react-jsx&quot;,
    &quot;moduleResolution&quot;: &quot;Bundler&quot;,
    &quot;target&quot;: &quot;ES2022&quot;,
    &quot;baseUrl&quot;: &quot;.&quot;,
    &quot;paths&quot;: {
      &quot;~/*&quot;: [&quot;./app/*&quot;]
    },
    &quot;types&quot;: [&quot;node&quot;, &quot;@shopify/app-bridge-types&quot;]
  }
}
</code></pre><figcaption>file: <code>./tsconfig.json</code></figcaption></figure><p>We&apos;ve changed the module resolution to <code>Bundler</code> instead of <code>Node16</code>, added <code>esModuleInterop</code> and bumped target and lib to ES2022.</p><p>We&apos;ve also included <code>remix.env.d.ts</code>, but we haven&apos;t yet created it, so do that now in your root directory:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">/// &lt;reference types=&quot;@remix-run/dev&quot; /&gt;
/// &lt;reference types=&quot;@remix-run/cloudflare&quot; /&gt;
/// &lt;reference types=&quot;@cloudflare/workers-types&quot; /&gt;
</code></pre><figcaption>file: <code>./remix.env.d.ts</code></figcaption></figure><h3 id="extend-the-remix-server">Extend the Remix server</h3><p>Create a file called <code>server.ts</code> in your root directory with this code:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import { logDevReady } from &quot;@remix-run/cloudflare&quot;;
import { createPagesFunctionHandler } from &quot;@remix-run/cloudflare-pages&quot;;
import * as build from &quot;@remix-run/dev/server-build&quot;;

if (process.env.NODE_ENV === &quot;development&quot;) {
  logDevReady(build);
}

export const onRequest = createPagesFunctionHandler({
  build,
  getLoadContext: (context) =&gt; ({ env: context.env }),
  mode: build.mode,
});
</code></pre><figcaption>file: <code>./server.ts</code></figcaption></figure><p>The important bit is the <code>getLoadContext</code> line because it forwards the environment variables to our Remix loaders and actions.</p><p>We also need to update <code>remix.config.js</code> with a custom startup command to use <code>wrangler pages dev</code> for local development. Replace its contents with this:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">import fs from &quot;node:fs&quot;;

// Related: https://github.com/remix-run/remix/issues/2835#issuecomment-1144102176
// Replace the HOST env var with SHOPIFY_APP_URL so that it doesn&apos;t break the remix server. The CLI will eventually
// stop passing in HOST, so we can remove this workaround after the next major release.
if (
  process.env.HOST &amp;&amp;
  (!process.env.SHOPIFY_APP_URL ||
    process.env.SHOPIFY_APP_URL === process.env.HOST)
) {
  process.env.SHOPIFY_APP_URL = process.env.HOST;
  delete process.env.HOST;
}

// Binds env vars for local development with Cloudflare pages
if (process.env.NODE_ENV === &quot;development&quot;) {
  fs.writeFileSync(
    &quot;.dev.vars&quot;,
    `SHOPIFY_APP_URL=${process.env.SHOPIFY_APP_URL}\nSHOPIFY_API_KEY=${process.env.SHOPIFY_API_KEY}\nSHOPIFY_API_SECRET=${process.env.SHOPIFY_API_SECRET}\nSCOPES=${process.env.SCOPES}\n`,
  );
}

/** @type {import(&apos;@remix-run/dev&apos;).AppConfig} */
export default {
  ignoredRouteFiles: [&quot;**/.*&quot;],
  server: &quot;./server.ts&quot;,
  appDirectory: &quot;app&quot;,
  serverBuildPath: &quot;functions/[[path]].js&quot;,
  serverConditions: [&quot;workerd&quot;, &quot;worker&quot;, &quot;browser&quot;],
  serverDependenciesToBundle: &quot;all&quot;,
  serverMainFields: [&quot;browser&quot;, &quot;module&quot;, &quot;main&quot;],
  serverMinify: true,
  serverModuleFormat: &quot;esm&quot;,
  serverPlatform: &quot;neutral&quot;,
  dev: {
    command: `npm start -- --port=${process.env.PORT}`,
    manual: true,
    port: process.env.HMR_SERVER_PORT || 8002,
  },
  future: {},
};
</code></pre><figcaption>file: <code>./remix.config.js</code></figcaption></figure><p>A lot of the configuration settings come from the Cloudflare+Remix template: <a href="https://github.com/remix-run/remix/blob/main/templates/cloudflare-pages/remix.config.js?ref=code-frontend">https://github.com/remix-run/remix/blob/main/templates/cloudflare-pages/remix.config.js</a>. However, I&apos;ve added a few important bits.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F44D;</div><div class="kg-callout-text">Feel free to skip the explanation below if you&apos;re just copy-pasting.</div></div><p>The <code>shopify app dev</code> command opens a tunnel to your local server, updates the app URL in your partner dashboard, and sets the new URL along with the API key, secret, and scope as environment variables.</p><p>The problem is that Cloudflare workers don&apos;t receive environment variables - they must be bound. This is done by adding them to <code>.dev.vars</code> file, and that&apos;s what the <code>fs.writeFileSync</code> line does - it writes the latest env variable values to the <code>.dev.vars</code> file every time the server is started.</p><p>Additionally, I&apos;ve updated the <code>command</code> used by <code>remix dev</code> to call our <code>npm start</code> which uses Wrangler to start the local Cloudflare development environment on the port the Cloudflare tunnel expects.</p><p>Finally, because we use the ESModules, we need to change the syntax of the default export.</p><p>With this configuration, you will be able to start your server, and you&apos;ll see that it&apos;s using <code>wrangler</code> under the hood and it&apos;s being passed our environment variables as bindings:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image-4.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="972" height="346" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-4.png 600w, https://codefrontend.com/content/images/2023/12/image-4.png 972w" sizes="(min-width: 720px) 720px"><figcaption>Make sure to set URLs to update automatically.</figcaption></figure><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/12/image-5.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1082" height="376" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-5.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-5.png 1000w, https://codefrontend.com/content/images/2023/12/image-5.png 1082w" sizes="(min-width: 720px) 720px"></figure><p>Don&apos;t worry if you&apos;re getting errors when starting your app. We still need to take care of the Shopify part, but this configuration will enable us to deploy and run our project on Cloudflare.</p><h2 id="step-3-update-shopify-setup-for-cloudflare">Step 3: Update Shopify Setup for Cloudflare</h2><p>We don&apos;t have access to <code>process.env</code> anymore, but our Shopify configuration relies on it. We need to change that, so let&apos;s update the <code>shopify.server.ts</code> with this content:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import type { AppLoadContext } from &quot;@remix-run/cloudflare&quot;;
import { restResources } from &quot;@shopify/shopify-api/rest/admin/2023-10&quot;;
import {
  AppDistribution,
  DeliveryMethod,
  LATEST_API_VERSION,
  shopifyApp,
} from &quot;@shopify/shopify-app-remix/server&quot;;
import { KVSessionStorage } from &quot;@shopify/shopify-app-session-storage-kv&quot;;

declare module &quot;@remix-run/cloudflare&quot; {
  interface AppLoadContext {
    env: {
      SHOPIFY_API_KEY: string;
      SHOPIFY_API_SECRET: string;
      SHOPIFY_APP_URL: string;
      SCOPES: string;
      SHOP_CUSTOM_DOMAIN: string;
      SESSION: KVNamespace;
      DB: D1Database;
    };
  }
}

export const initShopify = (context: AppLoadContext) =&gt; {
  const shopify = shopifyApp({
    apiKey: context.env.SHOPIFY_API_KEY,
    apiSecretKey: context.env.SHOPIFY_API_SECRET || &quot;&quot;,
    apiVersion: LATEST_API_VERSION,
    scopes: context.env.SCOPES?.split(&quot;,&quot;),
    appUrl: context.env.SHOPIFY_APP_URL || &quot;&quot;,
    authPathPrefix: &quot;/auth&quot;,
    sessionStorage: new KVSessionStorage(context.env.SESSION),
    distribution: AppDistribution.AppStore,
    restResources,
    webhooks: {
      APP_UNINSTALLED: {
        deliveryMethod: DeliveryMethod.Http,
        callbackUrl: &quot;/webhooks&quot;,
      },
    },
    hooks: {
      afterAuth: async ({ session }) =&gt; {
        shopify.registerWebhooks({ session });
      },
    },
    future: {
      v3_webhookAdminContext: true,
      v3_authenticatePublic: true,
    },
    ...(context.env.SHOP_CUSTOM_DOMAIN
      ? { customShopDomains: [context.env.SHOP_CUSTOM_DOMAIN] }
      : {}),
  });

  return shopify;
};
</code></pre><figcaption>file: <code>./app/shopify.server.ts</code></figcaption></figure><p>We wrap our Shopify object in a function that receives a context with all our environment variables. We&apos;ll be calling this function from our loaders and actions.</p><p>Additionally, I&apos;ve replaced the <code>sessionStorage</code> with <code>KVSessionStorage</code> and pass it the bound id of the key-value store namespace. The <code>declare module &quot;@remix-run/cloudflare&quot;</code> section helps us eliminate warnings due to the default context type being <code>unknown</code>.</p><h3 id="update-the-usages-of-shopify">Update the usages of Shopify</h3><p>Now that we changed the <code>shopify.server.ts</code> file, any existing usages will break and need to be updated. Below are all of the changes you need to make.</p><h4 id="in-appentryservertsx">In <code>app/entry.server.tsx</code></h4><p>Replace the contents with:</p><figure class="kg-card kg-code-card"><pre><code>import type { AppLoadContext, EntryContext } from &quot;@remix-run/cloudflare&quot;;
import { RemixServer } from &quot;@remix-run/react&quot;;
import isbot from &quot;isbot&quot;;
import { renderToReadableStream } from &quot;react-dom/server&quot;;
import { initShopify } from &quot;~/shopify.server&quot;;

export default async function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext,
  context: AppLoadContext,
) {
  const shopify = await initShopify(context);
  shopify.addDocumentResponseHeaders(request, responseHeaders);

  const body = await renderToReadableStream(
    &lt;RemixServer context={remixContext} url={request.url} /&gt;,
    {
      signal: request.signal,
      onError(error: unknown) {
        // Log streaming rendering errors from inside the shell
        console.error(error);
        responseStatusCode = 500;
      },
    },
  );
  if (isbot(request.headers.get(&quot;user-agent&quot;))) {
    await body.allReady;
  }

  responseHeaders.set(&quot;Content-Type&quot;, &quot;text/html&quot;);
  return new Response(body, {
    headers: responseHeaders,
    status: responseStatusCode,
  });
}
</code></pre><figcaption>file: <code>./app/entry.server.tsx</code></figcaption></figure><p>It&apos;s mostly the same as in Remix+Cloudflare template, but I&apos;ve updated the <code>handleRequest</code> to accept context as the last argument and pass it to <code>initShopify</code>.</p><h4 id="in-approottsx">In <code>app/root.tsx</code></h4><p>Add the code necessary for <a href="https://remix.run/docs/en/main/styling/bundling?ref=code-frontend">css bundling</a>:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import type { LinksFunction } from &quot;@remix-run/cloudflare&quot;;
import { cssBundleHref } from &quot;@remix-run/css-bundle&quot;;

export const links: LinksFunction = () =&gt; [
  ...(cssBundleHref ? [{ rel: &quot;stylesheet&quot;, href: cssBundleHref }] : []),
];

...</code></pre><figcaption>file: <code>./app/root.tsx</code></figcaption></figure><h4 id="in-approutes">In <code>app/routes/*</code></h4><p>We&apos;ll follow this pattern to fix most of the issues in routes:</p><ul><li>Get the <code>authenticate</code> method off of the Shopify object created with our new function.</li><li>Change the <code>node</code> imports to <code>cloudflare</code> imports.</li></ul><p>Do that in <code>app/routes/auth.$.tsx</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import type { LoaderFunctionArgs } from &quot;@remix-run/cloudflare&quot;;
import { initShopify } from &quot;../shopify.server&quot;;

export const loader = async ({ request, context }: LoaderFunctionArgs) =&gt; {
  await initShopify(context).authenticate.admin(request);

  return null;
};
</code></pre><figcaption>file: <code>./app/routes/auth.$.tsx</code></figcaption></figure><p>Do the same as above in <code>app/routes/webhooks.tsx</code>, but additionally remove the usage of <code>db</code> and instead remove session directly through <code>shopify</code>, or simply replace the contents with this:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import type { ActionFunctionArgs } from &quot;@remix-run/cloudflare&quot;;
import { initShopify } from &quot;../shopify.server&quot;;

export const action = async ({ request, context }: ActionFunctionArgs) =&gt; {
  const shopify = initShopify(context);
  const { topic, session, admin } = await shopify.authenticate.webhook(request);

  if (!admin) {
    // The admin context isn&apos;t returned if the webhook fired after a shop was uninstalled.
    throw new Response();
  }

  switch (topic) {
    case &quot;APP_UNINSTALLED&quot;:
      if (session) {
        await shopify.sessionStorage.deleteSession(session.id);
      }

      break;
    case &quot;CUSTOMERS_DATA_REQUEST&quot;:
    case &quot;CUSTOMERS_REDACT&quot;:
    case &quot;SHOP_REDACT&quot;:
    default:
      throw new Response(&quot;Unhandled webhook topic&quot;, { status: 404 });
  }

  throw new Response();
};</code></pre><figcaption>file: <code>./app/routes/webhooks.tsx</code></figcaption></figure><p>Do similar changes in <code>app/routes/app.tsx</code> to fix the loader, making sure to change <code>process.env</code> to <code>context.env</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import type {
  HeadersFunction,
  LoaderFunctionArgs,
} from &quot;@remix-run/cloudflare&quot;;
import { json } from &quot;@remix-run/cloudflare&quot;;
import { initShopify } from &quot;../shopify.server&quot;;

export const loader = async ({ request, context }: LoaderFunctionArgs) =&gt; {
  await initShopify(context).authenticate.admin(request);

  return json({ apiKey: context.env.SHOPIFY_API_KEY || &quot;&quot; });
};

...</code></pre><figcaption>file: <code>./app/routes/app.tsx</code></figcaption></figure><p>And similarly in <code>app/routes/app._index.tsx</code> to fix the loader and the action:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import type {
  ActionFunctionArgs,
  LoaderFunctionArgs,
} from &quot;@remix-run/cloudflare&quot;;
import { json } from &quot;@remix-run/cloudflare&quot;;
import { initShopify } from &quot;../shopify.server&quot;;

export const loader = async ({ request, context }: LoaderFunctionArgs) =&gt; {
  await initShopify(context).authenticate.admin(request);

  return null;
};

export const action = async ({ request, context }: ActionFunctionArgs) =&gt; {
  const { admin } = await initShopify(context).authenticate.admin(request);
  ...
}</code></pre><figcaption>file: <code>/app/routes/app._index.tsx</code></figcaption></figure><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4AD;</div><div class="kg-callout-text">If <code>responseJson</code> is giving you type errors, just set it to <code>any</code>:<br><code>const responseJson = await response.json&lt;any&gt;();</code></div></div><p>Finally, fix the <code>login</code> by taking it from the result of <code>initShopify</code> and replacing <code>node</code> imports with <code>cloudflare</code> in <code>app/routes/_index/route.tsx</code> and <code>app/routes/auth.login/route.tsx</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import type { LoaderFunctionArgs } from &quot;@remix-run/cloudflare&quot;;
import { json, redirect } from &quot;@remix-run/cloudflare&quot;;
import { initShopify } from &quot;~/shopify.server&quot;;

export const loader = async ({ request, context }: LoaderFunctionArgs) =&gt; {
  ...
  const shopify = initShopify(context);
  return json({ showForm: Boolean(shopify.login) });
};</code></pre><figcaption>file: <code>./app/routes/_index/route.tsx</code></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-ts">import type {
  ActionFunctionArgs,
  LoaderFunctionArgs,
} from &quot;@remix-run/cloudflare&quot;;
import { json } from &quot;@remix-run/cloudflare&quot;;
import { initShopify } from &quot;~/shopify.server&quot;;

export const loader = async ({ request, context }: LoaderFunctionArgs) =&gt; {
  const errors = loginErrorMessage(await initShopify(context).login(request));
  ...
};

export const action = async ({ request, context }: ActionFunctionArgs) =&gt; {
  const errors = loginErrorMessage(await initShopify(context).login(request));
  ...
};</code></pre><figcaption>file: <code>./app/routes/auth.login/route.tsx</code></figcaption></figure><p>Now, if you run <code>npm run dev</code> you should be able to visit the given preview URL, install your app, and see your app working:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image-6.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1605" height="702" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-6.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-6.png 1000w, https://codefrontend.com/content/images/size/w1600/2023/12/image-6.png 1600w, https://codefrontend.com/content/images/2023/12/image-6.png 1605w" sizes="(min-width: 720px) 720px"><figcaption>Working Shopify demo app</figcaption></figure><h2 id="step-4-using-drizzle-with-d1">Step 4: Using Drizzle with D1</h2><p>First, we need to create a new D1 database using instructions in <a href="https://developers.cloudflare.com/d1/get-started/?ref=code-frontend">cloudflare docs</a>:</p><figure class="kg-card kg-code-card"><pre><code>npx wrangler d1 create dev-remix-cf-demo</code></pre><figcaption>Command to run, I suggest prefixing your database with <code>dev</code> or <code>prod</code></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image-7.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="638" height="162" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-7.png 600w, https://codefrontend.com/content/images/2023/12/image-7.png 638w"><figcaption>Wrangler CLI output</figcaption></figure><p>The CLI gives us credentials to use to connect to the database. We need to create a <code>wrangler.toml</code> config file in our root directory and paste them there (replace with your values):</p><figure class="kg-card kg-code-card"><pre><code class="language-toml">compatibility_date = &quot;2022-11-07&quot;

[[d1_databases]]
binding = &quot;DB&quot;
database_name = &quot;dev-remix-cf-demo&quot;
database_id = &quot;53df5d71-de80-4391-a75f-081ae2058028&quot;
preview_database_id = &quot;DB&quot;</code></pre><figcaption>file: <code>./wrangler.toml</code></figcaption></figure><p>I&apos;ve added two extra lines, <code>compatibility_date</code> so we don&apos;t need to pass it as CLI arguments and <code>preview_database_id</code> which must match our binding so that D1 works with the local version of Cloudflare Pages.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4AD;</div><div class="kg-callout-text">When developing locally, the local DB will be a copy of your remote database, but modifying it won&apos;t change the remote version unless you explicitly pass <code>--remote</code> to your <code>wrangler</code> command. More info here: <a href="https://developers.cloudflare.com/d1/learning/local-development/?ref=code-frontend">https://developers.cloudflare.com/d1/learning/local-development/</a></div></div><h4 id="add-drizzle-orm">Add Drizzle ORM</h4><p>We&apos;ll loosely follow instructions in <a href="https://orm.drizzle.team/docs/get-started-sqlite?ref=code-frontend">drizzle docs</a>. First, let&apos;s install the necessary packages:</p><pre><code>npm i drizzle-orm
npm i -D drizzle-kit</code></pre><p>Then, let&apos;s create a new folder <code>drizzle</code> in the root directory and create <code>schema.ts</code> file inside with some tables to test our setup with:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import { sqliteTable, text } from &quot;drizzle-orm/sqlite-core&quot;;

export const test_table = sqliteTable(&quot;test_table&quot;, {
  test_data: text(&quot;test_data&quot;).notNull(),
});
</code></pre><figcaption>file: <code>./drizzle/schema.ts</code></figcaption></figure><p>And we also need to create a config file for drizzle called <code>drizzle.config.ts</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import type { Config } from &quot;drizzle-kit&quot;;

export default {
  driver: &quot;d1&quot;,
  schema: &quot;./drizzle/schema.ts&quot;,
  out: &quot;./drizzle/migrations&quot;,
} satisfies Config;
</code></pre><figcaption>file: <code>./drizzle.config.ts</code></figcaption></figure><p>Finally, we need to create a way to access the database from our app, so let&apos;s modify the <code>app/db.server.ts</code> file:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import type { AppLoadContext } from &quot;@remix-run/cloudflare&quot;;
import { drizzle } from &quot;drizzle-orm/d1&quot;;

export function initDB(context: AppLoadContext) {
  return drizzle(context.env.DB);
}

export * as schema from &quot;../drizzle/schema&quot;;
</code></pre><figcaption>file: <code>./app/db.server.ts</code></figcaption></figure><p>You&apos;ll notice that we use the same approach to get binding as we did with <code>initShopify</code>. I also like to re-export schema from this file so it&apos;s easier to access it in the app.</p><h4 id="using-drizzle-in-the-app">Using drizzle in the app</h4><p>We can now test our setup by writing and reading some test data.</p><p>In <code>app/routes/app._index.tsx</code> let&apos;s insert some test data in the action and read it in the loader:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import { initDB, schema } from &quot;~/db.server&quot;;

export const loader = async ({ request, context }: LoaderFunctionArgs) =&gt; {
  await initShopify(context).authenticate.admin(request);

  const test_data = await initDB(context)
    .select()
    .from(schema.test_table)
    .all();

  return json({ test_data });
};

export const action = async ({ request, context }: ActionFunctionArgs) =&gt; {
  const { admin } = await initShopify(context).authenticate.admin(request);
  await initDB(context)
    .insert(schema.test_table)
    .values({ test_data: &quot;testing&quot; });
  ...
}</code></pre><figcaption>file: <code>./app/routes/app._index.tsx</code></figcaption></figure><p>Let&apos;s output the test data somewhere we can see. Get the loader data at the top of the <code>Index</code> component and output the <code>test_data</code> somewhere on the page:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">export default function Index() {
  const { test_data } = useLoaderData&lt;typeof loader&gt;();
  
  ...
  
  return (
    ...
    &lt;Text as=&quot;h2&quot; variant=&quot;headingMd&quot;&gt;
      Congrats on creating a new Shopify app &#x1F389;
    &lt;/Text&gt;
    &lt;Text as=&quot;p&quot;&gt;{JSON.stringify(test_data)}&lt;/Text&gt;
    ...
  );
}</code></pre><figcaption>file: <code>./app/routes/app._index.tsx</code></figcaption></figure><p>Before we test the app, we need to create the tables in the database. First, let&apos;s create the initial migration:</p><pre><code>npx drizzle-kit generate:sqlite</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image-8.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="611" height="172" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-8.png 600w, https://codefrontend.com/content/images/2023/12/image-8.png 611w"><figcaption>CLI output</figcaption></figure><p>Note the migration name, and run the command to update the wrangler database:</p><pre><code>npx wrangler d1 execute dev-remix-cf-demo --local --file=./drizzle/migrations/0000_classy_kronos.sql</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image-9.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1078" height="89" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-9.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-9.png 1000w, https://codefrontend.com/content/images/2023/12/image-9.png 1078w" sizes="(min-width: 720px) 720px"><figcaption>CLI output</figcaption></figure><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4AD;</div><div class="kg-callout-text">Unfortunately, the CLI doesn&apos;t allow executing multiple migrations at once, so when you have multiple migration files, you still need to execute them one by one. You can write a small script to do that if it becomes bothersome.</div></div><p>Finally, let&apos;s run <code>npm run dev</code> to see our local database in action by clicking &quot;Generate product.&quot;</p><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4AD;</div><div class="kg-callout-text">If you&apos;re getting errors regarding scope when you press the &quot;Generate product&quot; button, make sure in your <code>shopify.app.toml</code> the <code>scopes</code> are set to: <code>scopes = &quot;write_products&quot;</code>. Afterwards run <code>npm run shopify app config push</code>.</div></div><p>You should see new values appearing, and they&apos;ll still be available locally if you restart your application:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/12/image-10.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1600" height="737" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-10.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-10.png 1000w, https://codefrontend.com/content/images/2023/12/image-10.png 1600w" sizes="(min-width: 720px) 720px"></figure><h2 id="step-5-deploying-your-shopify-app">Step 5: Deploying Your Shopify App</h2><p>We got the local dev environment working, but we still need to deploy the production application. This will involve deploying the database, configuring the KV store, and deploying the application itself.</p><h4 id="deploying-the-d1-database">Deploying the D1 database</h4><p>Even though we created tables and some data locally, the remote D1 database still has no tables or data. To initialize them, we need to run the migrations without the <code>--local</code> flag:</p><pre><code>npx wrangler d1 execute dev-remix-cf-demo --file=./drizzle/migrations/0000_classy_kronos.sql</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image-11.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1014" height="103" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-11.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-11.png 1000w, https://codefrontend.com/content/images/2023/12/image-11.png 1014w" sizes="(min-width: 720px) 720px"><figcaption>CLI output for production database</figcaption></figure><p>If you visit your Cloudflare dashboard you should see the new tables appear:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image-12.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1522" height="737" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-12.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-12.png 1000w, https://codefrontend.com/content/images/2023/12/image-12.png 1522w" sizes="(min-width: 720px) 720px"><figcaption>Workers &amp; Pages -&gt; D1 -&gt; [your database name]</figcaption></figure><h4 id="deploying-the-application">Deploying the application</h4><p>Go to Workers &amp; Pages -&gt; Overview and click &quot;Create application&quot; then go to &quot;Pages&quot; tab.</p><p>The simplest way from here is to push your application to a new GitHub repository and then &quot;Connect to Git&quot; in the Cloudflare dashboard. Everything will be taken care of automatically.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image-13.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1503" height="954" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-13.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-13.png 1000w, https://codefrontend.com/content/images/2023/12/image-13.png 1503w" sizes="(min-width: 720px) 720px"><figcaption>Application overview dashboard</figcaption></figure><p>Alternatively, we can deploy through Wrangler CLI by pushing our static build files. To do that, create a new Cloudflare project first:</p><pre><code>npx wrangler pages project create</code></pre><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/12/image-14.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1290" height="138" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-14.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-14.png 1000w, https://codefrontend.com/content/images/2023/12/image-14.png 1290w" sizes="(min-width: 720px) 720px"></figure><p>Then, we can use <code>wrangler pages deploy</code> to push our static files. It is helpful to create a script in package.json for this:</p><figure class="kg-card kg-code-card"><pre><code>&quot;pages:deploy&quot;: &quot;npm run build &amp;&amp; wrangler pages deploy ./public&quot;,</code></pre><figcaption>Will create a production deployment</figcaption></figure><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/12/image-15.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="730" height="464" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-15.png 600w, https://codefrontend.com/content/images/2023/12/image-15.png 730w" sizes="(min-width: 720px) 720px"></figure><p>Running the command will create a new project in the Cloudflare dashboard:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/12/image-16.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1056" height="562" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-16.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-16.png 1000w, https://codefrontend.com/content/images/2023/12/image-16.png 1056w" sizes="(min-width: 720px) 720px"></figure><p>Note the application URL. We will need to set it in our environment variables.</p><h4 id="configure-kv-store-and-d1-bindings">Configure KV store and D1 bindings</h4><p>Go to &quot;Workers &amp; Pages&quot; -&gt; &quot;KV&quot; and &quot;Create a namespace&quot; with a name you&apos;ll easily identify: </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image-20.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1079" height="512" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-20.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-20.png 1000w, https://codefrontend.com/content/images/2023/12/image-20.png 1079w" sizes="(min-width: 720px) 720px"><figcaption>View after creating a new KV namespace</figcaption></figure><p>Now go to &quot;Overview,&quot; select your app, and go to &quot;Settings&quot; -&gt; &quot;Functions.&quot; Scroll down to &quot;KV namespace bindings&quot; and set <code>SESSION</code> to the new namespace. It will be available on <code>context.env.SESSION</code> just like in the development environment:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/12/image-21.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1188" height="306" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-21.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-21.png 1000w, https://codefrontend.com/content/images/2023/12/image-21.png 1188w" sizes="(min-width: 720px) 720px"></figure><p>Further down below, bind your D1 database to <code>DB</code> variable:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/12/image-22.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1192" height="292" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-22.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-22.png 1000w, https://codefrontend.com/content/images/2023/12/image-22.png 1192w" sizes="(min-width: 720px) 720px"></figure><h4 id="bind-shopify-environment-variables">Bind Shopify environment variables</h4><p>As the last bit of configuration in Cloudflare, we need to add Shopify env var bindings to our pages workers.</p><p>Go to your app&apos;s &quot;Settings&quot; -&gt; &quot;Environment variables&quot; and for production environment set the variables as they are defined in <code>.dev.vars</code> file or run <code>npx shopify app env show</code> to see them in console. As <code>SHOPIFY_APP_URL</code> set &#xA0;the URL from above, with <code>https://</code> prefix:</p><figure class="kg-card kg-code-card"><pre><code>SHOPIFY_APP_URL=https://remix-cf-demo-app.pages.dev
SHOPIFY_API_KEY=abcd12345...
SHOPIFY_API_SECRET=abcd12345...
SCOPES=write_products
</code></pre><figcaption>You will have different values</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image-17.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1196" height="343" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-17.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-17.png 1000w, https://codefrontend.com/content/images/2023/12/image-17.png 1196w" sizes="(min-width: 720px) 720px"><figcaption>This is what your settings should look like.</figcaption></figure><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4AD;</div><div class="kg-callout-text">If you want to have preview environments, I suggest you create separate Shopify apps just for testing and deploy them separately as if they were production applications, either on separate Cloudflare projects or as your preview deployments here (may be harder to achieve that if you&apos;re deploying directly from GitHub though).</div></div><h4 id="change-app-urls-in-shopify-partner-dashboard">Change app URLs in Shopify partner dashboard</h4><p>Finally, we need to update our app settings in the partner dashboard. Change the app URL and callback URLs to use the new Cloudflare pages URL:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image-18.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1521" height="949" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-18.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-18.png 1000w, https://codefrontend.com/content/images/2023/12/image-18.png 1521w" sizes="(min-width: 720px) 720px"><figcaption>App settings at Shopify partners dashboard</figcaption></figure><p>If we re-deploy the application by running <code>npm run pages:deploy</code> we should be able to see the production version of our application.</p><p>In your dashboard&apos;s overview page click on &quot;Select store&quot; which will let you install the production app on your development store:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/12/image-19.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="597" height="256"></figure><p>And if we test it in our development store, you&apos;ll see the requests going to the production URLs:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/12/image-23.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="2000" height="660" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-23.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-23.png 1000w, https://codefrontend.com/content/images/size/w1600/2023/12/image-23.png 1600w, https://codefrontend.com/content/images/size/w2400/2023/12/image-23.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>We can also verify that the production database is being used in our Cloudflare dashboard&apos;s D1 section:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/12/image-24.png" class="kg-image" alt="Deploy a Remix+Drizzle Shopify App on Cloudflare Pages, D1 &amp; KV" loading="lazy" width="1643" height="541" srcset="https://codefrontend.com/content/images/size/w600/2023/12/image-24.png 600w, https://codefrontend.com/content/images/size/w1000/2023/12/image-24.png 1000w, https://codefrontend.com/content/images/size/w1600/2023/12/image-24.png 1600w, https://codefrontend.com/content/images/2023/12/image-24.png 1643w" sizes="(min-width: 720px) 720px"><figcaption>Rows are being updated with test data</figcaption></figure><h3 id="final-cleanup">Final cleanup</h3><p>Now that we&apos;re not using prisma anymore, we can uninstall the related packages and remove <code>prisma</code> directory.</p><p>We won&apos;t be deploying containers, so we can remove docker files.</p><p>Also, because we&apos;re changing the <code>.dev.vars</code> file automatically, we can add it to <code>.gitignore</code> and ignore our new build paths:</p><figure class="kg-card kg-code-card"><pre><code>node_modules

/.cache
/functions/\[\[path\]\].js
/functions/\[\[path\]\].js.map
/functions/metafile.*
/functions/version.txt
/public/build
.dev.vars
</code></pre><figcaption>file: <code>./.gitignore</code></figcaption></figure><h2 id="template-repository">Template Repository</h2><p>Setting up Shopify app development is tricky without instructions. This article should help. I&apos;ve also created a repository for the template with all of the code from the article here: <a href="https://github.com/vincaslt/remix-cf-pages-d1-drizzle-shopify-app?ref=code-frontend">https://github.com/vincaslt/remix-cf-pages-d1-drizzle-shopify-app</a></p>]]></content:encoded></item><item><title><![CDATA[Copy To Clipboard In JavaScript and React]]></title><description><![CDATA[Learn to copy text to the clipboard in JavaScript and React with examples.]]></description><link>https://codefrontend.com/copy-to-clipboard-in-js/</link><guid isPermaLink="false">6429577401a0c9042f0cfa10</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Quick Guide]]></category><category><![CDATA[ReactJS]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Mon, 03 Apr 2023 08:21:39 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2023/04/copy-to-clipboard-article-cover.jpg" medium="image"/><content:encoded><![CDATA[<h2 id="copy-to-clipboard-in-javascript">Copy to Clipboard in JavaScript</h2><img src="https://codefrontend.com/content/images/2023/04/copy-to-clipboard-article-cover.jpg" alt="Copy To Clipboard In JavaScript and React"><p>To copy text into the clipboard using JavaScript, you can use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard?ref=code-frontend">Clipboard API</a>:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">async function copyToClipboard(text) {
  try {
    await navigator.clipboard.writeText(text);
    /* &#x2705; Copied successfully */
  } catch (e) {
    /* &#x274C; Failed to copy (insufficient permissions) */
  }
}</code></pre><figcaption>Copy to clipboard example in JavaScript.</figcaption></figure><p>The <code>navigator.clipboard.writeText</code> function accepts the text to copy, and returns a Promise:</p><ul><li><strong>resolved </strong>- if the text was copied successfully,</li><li><strong>rejected</strong> - if the user hasn&apos;t given the <code>&quot;clipboard-write&quot;</code> permission.</li></ul><h2 id="using-clipboard-api-in-react">Using Clipboard API in React</h2><p>You can use the above JavaScript code to implement copying to the clipboard in React, but it&apos;s helpful to create a custom hook:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">function useCopyToClipboard() {
  const [result, setResult] = useState&lt;
    null | { state: &apos;success&apos; } | { state: &apos;error&apos;; message: string }
  &gt;(null);

  const copy = async (text: string) =&gt; {
    try {
      await navigator.clipboard.writeText(text);
      setResult({ state: &apos;success&apos; });
    } catch (e) {
      setResult({ state: &apos;error&apos;, message: e.message });
      throw e;
    } finally {
      // &#x1F447; Show the result feedback for 2 seconds
      setTimeout(() =&gt; {
        setResult(null);
      }, 2000);
    }
  };

  // &#x1F447; We want the result as a tuple
  return [copy, result] as const;
}</code></pre><figcaption>A custom ReactJS hook to copy text to the clipboard in TypeScript.</figcaption></figure><p>The hook returns a <a href="https://www.typescriptlang.org/docs/handbook/basic-types.html?ref=code-frontend#tuple">tuple</a> with the function to copy text into the clipboard and an object describing the result:</p><ul><li><strong>null</strong> - no text copied recently;</li><li><strong>&quot;success&quot;</strong> - text copied successfully;</li><li><strong>&quot;error&quot;</strong> - operation failed with the error <code>message</code>.</li></ul><p>You can use the <code>useCopyToClipboard</code> hook like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">export function Example() {
  const [inputText, setInputText] = useState(&apos;&apos;);
  
  // &#x1F447; Using our custom hook
  const [copyToClipboard, copyResult] = useCopyToClipboard();

  const handleChangeInput = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setInputText(e.target.value);
  };

  const handleClickCopy = () =&gt; {
    // Copy the text from the input field into the clipboard
    copyToClipboard(inputText);
  };

  return (
    &lt;div&gt;
      &lt;input value={inputText} onChange={handleChangeInput} /&gt;
      &lt;button onClick={handleClickCopy}&gt;Copy to clipboard&lt;/button&gt;
      &lt;div&gt;
      	// &#x1F447; Showing the results in the UI (for 2 seconds)
        {copyResult?.state === &apos;success&apos; &amp;&amp; &apos;Copied successfully!&apos;}
        {copyResult?.state === &apos;error&apos; &amp;&amp; `Error: ${copyResult.message}`}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}</code></pre><figcaption>Example usage of the custom <code>useCopyToClipboard</code> hook.</figcaption></figure><p>The copy operation has no inherent feedback in the UI, so it&apos;s a good idea to provide it yourself, you can do that by checking the <code>copyResult</code> value.</p><p>Here&apos;s what it looks like:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/04/copy-to-clipboard-demo.gif" class="kg-image" alt="Copy To Clipboard In JavaScript and React" loading="lazy" width="544" height="186"><figcaption>Demo using the custom <code>useCopyToClipboard</code> React hook.</figcaption></figure><h3 id="copy-to-clipboard-button-in-react">Copy to clipboard button in React</h3><p>You can also create a <code>CopyToClipboard</code> button component in React that accepts a <code>text</code> prop and handles showing the feedback messages in the UI. Here&apos;s an example that uses <a href="https://react-hot-toast.com/?ref=code-frontend">react-hot-toast</a>:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">import toast from &apos;react-hot-toast&apos;;

type Props = React.HTMLAttributes&lt;HTMLButtonElement&gt; &amp; {
  text: string;
};

function CopyToClipboard({ text, children = &apos;Copy&apos;, ...rest }: Props) {
  const handleClickCopy = async () =&gt; {
    try {
      await navigator.clipboard.writeText(text);
      // &#x1F447; Using react-hot-toast to provide feedback
      toast.success(&apos;Copied!&apos;);
    } catch (e) {
      toast.error(`Error: ${e.message}`);
      throw e;
    }
  };

  return (
    &lt;button onClick={handleClickCopy} {...rest}&gt;
      {children}
    &lt;/button&gt;
  );
}</code></pre><figcaption>Example copy to clipboard button component in React and TypeScript.</figcaption></figure><p>Feel free to use the native <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/alert?ref=code-frontend">alert function</a> instead of the toast messages. In that case, replace the toast calls with <code>alert(&apos;your message&apos;)</code>. However, if you use <code>react-hot-toast</code> you also need to add the <code>Toaster</code> component. Here&apos;s an example:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">export function Example() {
  const [inputText, setInputText] = React.useState(&apos;&apos;);

  const handleChangeInput = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setInputText(e.target.value);
  };

  return (
    &lt;div&gt;
      {/* &#x1F447; Don&apos;t forget to add this */}
      &lt;Toaster /&gt;
      &lt;input value={inputText} onChange={handleChangeInput} /&gt;
      &lt;CopyToClipboard text={inputText} /&gt;
    &lt;/div&gt;
  );
}</code></pre><figcaption>Example using the <code>CopyToClipboard</code> button.</figcaption></figure><p>Here&apos;s what it looks like now:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/04/copy-to-clipboard-button.gif" class="kg-image" alt="Copy To Clipboard In JavaScript and React" loading="lazy" width="544" height="186"><figcaption>Demo of the <code>CopyToClipboard</code> button.</figcaption></figure><h2 id="conclusion">Conclusion</h2><p>The simplest way to copy text to the clipboard in JavaScript is by using the Clipboard API. It has good support in modern browsers and it&apos;s easy to use.</p><p>However, in case you need to support older browsers you can use the <a href="https://www.npmjs.com/package/copy-to-clipboard?ref=code-frontend">copy-to-clipboard</a> npm package, which falls back on using <code>execCommand</code> in case the browser doesn&apos;t have access to <code>navigator.clipboard</code> object.</p><p>Read about how to get values from the input fields in React next:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://codefrontend.com/reactjs-get-input-value/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">How to Get the Value From Input Field in React</div><div class="kg-bookmark-description">The answer to the first question beginners have - how to get a value from an input element.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://codefrontend.com/content/images/size/w256h256/2022/07/code-frontend-logo-3.png" alt="Copy To Clipboard In JavaScript and React"><span class="kg-bookmark-author">Code Frontend</span><span class="kg-bookmark-publisher">Vincas Stonys</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://codefrontend.com/content/images/2022/09/get-input-value-in-react.jpeg" alt="Copy To Clipboard In JavaScript and React"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Want to Build a Shopify App? Here’s What You Should Know]]></title><description><![CDATA[Here's everything I've learned from building my first Shopify app. It's harder than you think.]]></description><link>https://codefrontend.com/building-a-shopify-app/</link><guid isPermaLink="false">6410a46cb9568255ad18fa71</guid><category><![CDATA[Deep Dive]]></category><category><![CDATA[Shopify Apps]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Sun, 19 Mar 2023 15:03:02 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2023/03/shopify-apps-article-cover.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://codefrontend.com/content/images/2023/03/shopify-apps-article-cover.jpg" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know"><p>Recently I decided I wanted to build a plugin or an app for an existing platform. I&apos;d been using Shopify for over a year already, so it was a no-brainer which platform it should be. </p><p>I&apos;d never built a Shopify app before, but now that I&apos;ve left my job and have more time, I spent a week building a small Shopify app and released it on the <a href="https://apps.shopify.com/vistonwp-wordpress-feed?ref=code-frontend">App Store</a>. Mainly, I just wanted to see what it would take.</p><p>I&apos;m going to share my whole experience, but here&apos;s what you can expect:</p><ul><li>Everything that I&apos;ve learned building the app;</li><li>What it took to get it reviewed and published on the App Store;</li><li>Pros and cons as I see them;</li><li>My resources and recommendations.</li></ul><p>If you&apos;re at least somewhat interested in building Shopify apps - <strong>this article is a must-read</strong>.</p><h2 id="what-was-the-app">What was the app?</h2><p>Over the last year, I was running a <a href="https://vistontea.com/?ref=code-frontend">tea shop</a> on Shopify and a <a href="https://blog.vistontea.com/?ref=code-frontend">tea blog</a> on WordPress. I wanted to show a section on my shop&apos;s homepage with the latest blog posts.</p><p>Unfortunately, there was no good way to do that out of the box. The only app that solved this charged $5/month that I didn&apos;t want to pay, and wasn&apos;t exactly what I wanted anyway.</p><p>So I decided to build an app to pull the latest WordPress posts and integrate them with the Shopify theme editor. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/03/image-1.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="1520" height="674" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-1.png 600w, https://codefrontend.com/content/images/size/w1000/2023/03/image-1.png 1000w, https://codefrontend.com/content/images/2023/03/image-1.png 1520w" sizes="(min-width: 720px) 720px"><figcaption>The section I added to my store using the app I&apos;ve built.</figcaption></figure><p>If you&apos;re interested in checking it out on the app store, here it is &#x1F447;</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://apps.shopify.com/vistonwp-wordpress-feed?ref=code-frontend"><div class="kg-bookmark-content"><div class="kg-bookmark-title">VistonWP &#x2011; WordPress Feed - Show a feed of the latest posts from your WordPress... | Shopify App Store</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://apps.shopify.com/cdn/shopifycloud/shopify_app_store/assets/merchant/favicon-7264cd52115ee17f2d4ad8f30580c39ea5dd8171c08b25d6ca3d3f4862411d6c.png" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know"><span class="kg-bookmark-author">Shopify App Store</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cdn.shopify.com/app-store/listing_images/44ab5be902b9afab06702de7bd295cd1/icon/COiegOLAv_0CEAE=.png" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know"></div></a></figure><p>Now let&apos;s dive into how I built it.</p><h2 id="setting-up-the-app">Setting up the app</h2><p>I followed the official <a href="https://shopify.dev/docs/apps/getting-started/create?ref=code-frontend">&quot;<a href="https://shopify.dev/docs/apps/getting-started/create?ref=code-frontend">getting started</a>&quot;</a> guide to bootstrap an example JavaScript application with <a href="https://shopify.dev/docs/apps/tools/cli?ref=code-frontend">Shopify CLI</a>. It was just a matter of running:</p><pre><code>npm init @shopify/app@latest</code></pre><p>For local development, you need to set up <a href="https://ngrok.com/?ref=code-frontend">ngrok</a> and then Shopify CLI automatically updates the URLs in the app settings that are used to install the app into your store.</p><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F937;&#x200D;&#x2642;&#xFE0F;</div><div class="kg-callout-text">It seems to me that production and development URLs are being shared, so it isn&apos;t clear to me if booting up a development server would mess something up on production.</div></div><p>What you end up with is a demo app that populates products and shows how many there are. It includes authentication, a basic backend server, and an embedded React app that uses their UI library <a href="https://polaris.shopify.com/?ref=code-frontend">Polaris</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/03/image-2.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="2000" height="1524" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-2.png 600w, https://codefrontend.com/content/images/size/w1000/2023/03/image-2.png 1000w, https://codefrontend.com/content/images/size/w1600/2023/03/image-2.png 1600w, https://codefrontend.com/content/images/2023/03/image-2.png 2008w" sizes="(min-width: 720px) 720px"><figcaption>The demo app (from Shopify docs).</figcaption></figure><p>I didn&apos;t need to do any wiring up myself, but I deleted whatever files and code from the demo app I didn&apos;t need.</p><h2 id="adding-theme-extensions">Adding theme extensions</h2><p>What didn&apos;t come with the demo was the theme extensions.</p><p>There are two kinds of theme extensions - <a href="https://shopify.dev/docs/themes/architecture/sections/app-blocks?ref=code-frontend">app blocks</a> and <a href="https://shopify.dev/docs/apps/online-store/theme-app-extensions/extensions-framework?ref=code-frontend#app-embed-blocks">app embed blocks</a>:</p><p><strong>App embed blocks</strong> are scripts that are global for the whole page, such as overlays or code snippets for analytics.</p><p><strong>App blocks</strong> are page sections that shop owners can add through the theme editor. </p><p>I wanted to create an app block merchants could use to add a section with the latest articles. If you&apos;re not familiar with Shopify&apos;s theme editor, here&apos;s what I&apos;m talking about:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/03/image-3.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="2000" height="1183" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-3.png 600w, https://codefrontend.com/content/images/size/w1000/2023/03/image-3.png 1000w, https://codefrontend.com/content/images/size/w1600/2023/03/image-3.png 1600w, https://codefrontend.com/content/images/2023/03/image-3.png 2142w" sizes="(min-width: 720px) 720px"><figcaption>App blocks in the theme editor.</figcaption></figure><p>Each app block can define settings that alter the appearance, and even behavior of the app:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/03/image-4.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="1007" height="1295" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-4.png 600w, https://codefrontend.com/content/images/size/w1000/2023/03/image-4.png 1000w, https://codefrontend.com/content/images/2023/03/image-4.png 1007w" sizes="(min-width: 720px) 720px"><figcaption>Settings for the Shopify theme sections and App Blocks. These are the ones for my app.</figcaption></figure><p>I created a new theme extension by following <a href="https://shopify.dev/docs/apps/online-store/theme-app-extensions/getting-started?ref=code-frontend">the docs</a> and running a CLI command:</p><pre><code>npm run shopify app generate extension</code></pre><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4CC;</div><div class="kg-callout-text">It took me some time to realize, but theme extensions aren&apos;t exactly part of your app. You must host the Shopify app yourself, but Shopify hosts your theme extensions on their CDN separately.</div></div><p>Once you start your app in development by running <code>npm run dev</code> you&apos;ll get a link to the extension settings where you&apos;ll need to create a new version. It will be a <strong>draft</strong> at first, but you can later publish it.</p><p>In the same window, you need to enable development store previews:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/03/image-5.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="1198" height="351" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-5.png 600w, https://codefrontend.com/content/images/size/w1000/2023/03/image-5.png 1000w, https://codefrontend.com/content/images/2023/03/image-5.png 1198w" sizes="(min-width: 720px) 720px"></figure><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F926;&#x200D;&#x2642;&#xFE0F;</div><div class="kg-callout-text">Initially, I thought I needed to create a draft every time I change something but turns out the extensions are hot-reloaded. Sometimes you might need to manually refresh the page or restart the server, though.</div></div><h2 id="liquid-templates">Liquid templates</h2><p>I wasn&apos;t familiar with <a href="https://shopify.github.io/liquid/?shpxid=eb179131-7D86-4FF7-5FD3-11D03C5561D2&amp;ref=code-frontend">liquid</a> - the templating language from Shopify, but it was very easy to pick up and I didn&apos;t need to do anything fancy anyway. I created a new file <code>blocks/wp-feed.liquid</code> to start with.</p><p>Theme extensions must define a schema and some markup, like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-liquid">&lt;div id=&quot;wp-feed-app&quot;&gt;
  My app section contents.
&lt;/div&gt;

{% schema %}
{
  &quot;name&quot;: &quot;Wordpress Feed&quot;,
  &quot;target&quot;: &quot;section&quot;,
  &quot;stylesheet&quot;: &quot;wp-feed.css&quot;,
  &quot;javascript&quot;: &quot;wp-feed.js&quot;,
  &quot;settings&quot;: [
    { &quot;label&quot;: &quot;Section heading&quot;, &quot;id&quot;: &quot;heading&quot;, &quot;type&quot;: &quot;text&quot; },
    { &quot;label&quot;: &quot;Show section heading&quot;, &quot;id&quot;: &quot;show_heading&quot;, &quot;type&quot;: &quot;checkbox&quot;, &quot;default&quot;: true },
    {
      &quot;type&quot;: &quot;range&quot;,
      &quot;id&quot;: &quot;num_excerpt_lines&quot;,
      &quot;min&quot;: 1,
      &quot;max&quot;: 6,
      &quot;step&quot;: 1,
      &quot;label&quot;: &quot;Max excerpt lines&quot;,
      &quot;default&quot;: 3
    },
    {
      &quot;label&quot;: &quot;Blog URL&quot;,
      &quot;id&quot;: &quot;url&quot;,
      &quot;type&quot;: &quot;url&quot;,
      &quot;info&quot;: &quot;Overrides the global URL setting for this feed only.&quot;
    },
  ]
}
{% endschema %}
</code></pre><figcaption>Example schema based on my app.</figcaption></figure><p>The <strong>schema</strong> defines:</p><ul><li>The app name to show in the editor,</li><li>The target which is always <code>section</code> for app blocks,</li><li>The <a href="https://shopify.dev/docs/themes/architecture/settings?ref=code-frontend">settings</a> to show in the theme editor,</li><li>Links to your JavaScript and CSS files.</li></ul><p>Here&apos;s an example of how I used the settings in my templates:</p><figure class="kg-card kg-code-card"><pre><code class="language-liquid">&lt;div
  id=&quot;wp-feed-app&quot;
  class=&quot;wp-feed-app_section&quot;
&gt;
  {% if block.settings.show_heading %}
    &lt;div
      class=&quot;wp-feed-app_heading-wrapper&quot;
    &gt;
      &lt;h2 class=&quot;wp-feed-app_heading h1&quot;&gt;
        {{ block.settings.heading }}
      &lt;/h2&gt;
    &lt;/div&gt;
  {% endif %}

  &lt;div id=&quot;wp-feed-app_feed&quot; class=&quot;wp-feed-app_feed&quot;&gt;
    {% # injected: dynamically generated articles %}
  &lt;/div&gt;
&lt;/div&gt;

{% # ...schema... %}</code></pre><figcaption>The markup of my app block using settings from schema.</figcaption></figure><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4CC;</div><div class="kg-callout-text">The templates are server-side rendered, meaning they&apos;re generated once when the site is loaded. The theme editor reloads the page every time a setting is changed.</div></div><p>Now I had to fetch the WordPress articles to create a feed.</p><h2 id="fetching-wordpress-posts">Fetching WordPress posts</h2><p>WordPress has a headless API that you can access. Basically, you can issue a GET request to <code>/wp-json</code> on any WordPress blog, slap on some filters, and it will return a JSON response with posts.</p><p>In my case, it was similar to this one:</p><pre><code>/wp-json/wp/v2/posts?_embed&amp;_fields=title,link,excerpt,featured_media,_embedded,_links&amp;order=desc&amp;per_page=3</code></pre><p>Now I needed to figure out how to call this API from my app block.</p><p>First, I created a new file <code>assets/wp-feed.js</code>. Remember the line <code>&quot;javascript&quot;: &quot;wp-feed.js&quot;</code> in the schema? This is the file.</p><p>Now there&apos;s a small problem...</p><h3 id="accessing-settings-in-javascript-files">Accessing settings in JavaScript files</h3><p>Shopify will host your assets on its CDN and will add a single <code>&lt;script&gt;</code> tag across all instances of this app block. This means that the file isn&apos;t generated on the server and doesn&apos;t have access to app block settings.</p><p>This causes problems because I need to access the blog URL and the number of blog posts to fetch. I came up with this solution:</p><figure class="kg-card kg-code-card"><pre><code class="language-liquid">&lt;div
  id=&quot;wp-feed-app&quot;
  data-url=&quot;{{ block.settings.url }}&quot;
  class=&quot;wp-feed-app_section&quot;
&gt;
  ...
&lt;/div&gt;</code></pre><figcaption>Setting <code>data</code> attributes for settings</figcaption></figure><p>I set a <code>data-url</code> attribute on the root element of my app block and then in my JavaScript code I can access it like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">(() =&gt; {
  const rootEl = document.getElementById(&apos;wp-feed-app&apos;);
  const url = rootEl.getAttribute(&apos;data-url&apos;)?.replace(/\/$/, &apos;&apos;) || &apos;&apos;;
})();</code></pre><figcaption>Accessing app block settings in JavaScript.</figcaption></figure><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Since the JavaScript code will be dropped into HTML as-is, you should wrap it with an <a href="https://developer.mozilla.org/en-US/docs/Glossary/IIFE?ref=code-frontend">immediately-invoked function expression</a> (IIFE) so it doesn&apos;t interfere with any existing code on the page.</div></div><p>Now I simply used natively-available <code>fetch</code> function to get the latest posts in the app block. Here&apos;s the end result:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">function getPostFromJson(post) {
  const {
    link,
    title: { rendered: title },
    excerpt: { rendered: excerpt },
    featured_media: mediaId,
    _embedded: { &apos;wp:featuredmedia&apos;: allMedia },
  } = post;

  const image = allMedia.find((media) =&gt; media.id === mediaId);

  return {
    link,
    title,
    excerpt,
    image,
  };
}

async function fetchPosts() {
  return fetch(
    `${url}/wp-json/wp/v2/posts?_embed&amp;_fields=title,link,excerpt,featured_media,_embedded,_links&amp;order=desc&amp;per_page=${numPosts}`,
    {
      method: &apos;GET&apos;,
      headers: {
        &apos;Content-Type&apos;: &apos;application/json&apos;,
      },
    }
  )
    .then((data) =&gt; data.json())
    .then((posts) =&gt; posts.map(getPostFromJson));
}</code></pre><figcaption>Code to fetch posts from a WordPress blog.</figcaption></figure><h2 id="rendering-articles">Rendering articles</h2><p>App block scripts should be as light as possible and shouldn&apos;t needlessly introduce 3rd party libraries. Every extra byte adds to the load time of the store, and <a href="https://www.portent.com/blog/analytics/research-site-speed-hurting-everyones-revenue.htm?ref=code-frontend">time is money</a>.</p><p>I needed to render the articles, but it had to be plain JS, I couldn&apos;t create a React component.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F910;</div><div class="kg-callout-text">I must admit, I&apos;ve never built anything significant with plain JavaScript, I&apos;ve always used a framework like Angular or React... or Etx.js.</div></div><p>Lately, I&apos;ve been reading an interesting book called <a href="https://amz.run/6Ubo?ref=code-frontend"><em>&quot;Frameworkless Front-End Development: Do You Control Your Dependencies Or Are They Controlling You?&quot;</em></a> and it seemed like I could use its advice here.</p><h3 id="the-html-template-tag">The HTML template tag</h3><p>The closest you can get to reusable components in plain HTML are <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template?ref=code-frontend">template tags</a>.</p><p>They can contain any HTML code you want, and they go through the same processing as regular HTML code, but their contents are never rendered on the screen. Here&apos;s what I ended up with:</p><figure class="kg-card kg-code-card"><pre><code class="language-liquid">&lt;template id=&quot;wp-feed-app_article&quot;&gt;
  &lt;article class=&quot;wp-feed-app_article&quot;&gt;
    {% if block.settings.show_image %}
      &lt;figure&gt;
        &lt;a href=&quot;{% # injected: post url %}&quot; target=&quot;_blank&quot; class=&quot;wp-feed-app_article__image-link&quot;&gt;
          &lt;img
            src=&quot;{% # injected: image url %}&quot;
            loading=&quot;lazy&quot;
            alt=&quot;{% # injected: image alt text %}&quot;
            height=&quot;200&quot;
            width=&quot;200&quot;
          &gt;
        &lt;/a&gt;
      &lt;/figure&gt;
    {% endif %}
    &lt;div&gt;
      {% if block.settings.show_title %}
        &lt;h3 class=&quot;wp-feed-app_article__title&quot;&gt;
          &lt;a href=&quot;{% # injected: post url %}&quot; about=&quot;_blank&quot; class=&quot;wp-feed-app_article__title-link&quot;&gt;
            {% # injected: post title %}
          &lt;/a&gt;
        &lt;/h3&gt;
      {% endif %}

      {% if block.settings.show_excerpt %}
        &lt;p class=&quot;wp-feed-app_article__description&quot;&gt;
          {% # injected: post excerpt %}
        &lt;/p&gt;
      {% endif %}
    &lt;/div&gt;
  &lt;/article&gt;
&lt;/template&gt;</code></pre><figcaption>My app&apos;s template for WordPress posts.</figcaption></figure><p><em>&apos;If the contents aren&apos;t rendered, how is this helpful?&apos;</em> You might ask.</p><p>Well, we can use JavaScript to <em>clone</em> the contents of the template into virtual DOM, update the bits we need and then append them to a DOM element to render it. Here&apos;s what I mean:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">let template; // To only need to query once, but it&apos;s optional.

function clonePostTemplate() {
  if (!template) {
    template = document.getElementById(&apos;wp-feed-app_article&apos;);
  }
  return template.content.firstElementChild.cloneNode(true);
}</code></pre><figcaption>Cloning the element in the HTML template.</figcaption></figure><p>Once I had the element in vDOM, I updated the title, description, image, and the article URL:</p><pre><code class="language-js">function getPostElement(post) {
  const el = clonePostTemplate();

  const imageLinkEl = el.querySelector(&apos;.wp-feed-app_article__image-link&apos;);
  if (imageLinkEl) {
    imageLinkEl.setAttribute(&apos;href&apos;, post.link);
  }

  const titleLinkEl = el.querySelector(&apos;.wp-feed-app_article__title-link&apos;);
  if (titleLinkEl) {
    titleLinkEl.setAttribute(&apos;href&apos;, post.link);
    titleLinkEl.innerHTML = post.title;
    titleLinkEl.innerHTML = titleLinkEl.textContent.trim();
  }

  const descriptionEl = el.querySelector(
    &apos;.wp-feed-app_article__description&apos;
  );
  if (descriptionEl) {
    descriptionEl.innerHTML = post.excerpt;
    descriptionEl.innerHTML = descriptionEl.textContent.trim();
  }

  const imageEl = el.querySelector(&apos;img&apos;);
  if (imageEl) {
    imageEl.setAttribute(&apos;src&apos;, post.image.source_url);
    imageEl.setAttribute(&apos;alt&apos;, post.image.alt_text);
    imageEl.setAttribute(&apos;width&apos;, &apos;100%&apos;);
    imageEl.setAttribute(&apos;height&apos;, &apos;auto&apos;);
  }

  return el;
}</code></pre><p>It&apos;s not as sexy as passing in props to React components, but it gets the job done. The only thing left now is to actually render them on the screen:</p><pre><code class="language-js">const feedEl = document.getElementById(&apos;wp-feed-app_feed&apos;);

posts.map(getPostElement).forEach((element) =&gt; {
  feedEl.appendChild(element);
});</code></pre><h3 id="image-caveats">Image caveats</h3><p>Shopify requires you to explicitly define image width and height, but I load the images dynamically, so I&apos;d get this error:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/03/image-6.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="855" height="55" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-6.png 600w, https://codefrontend.com/content/images/2023/03/image-6.png 855w" sizes="(min-width: 720px) 720px"></figure><p> I simply added random <code>width</code> and <code>height</code>:</p><pre><code class="language-liquid">&lt;img
  src=&quot;{% # injected: image url %}&quot;
  loading=&quot;lazy&quot;
  alt=&quot;{% # injected: image alt text %}&quot;
  height=&quot;200&quot;
  width=&quot;200&quot;
&gt;</code></pre><p>Then I reset them when I render the image:</p><pre><code class="language-js">imageEl.setAttribute(&apos;width&apos;, &apos;100%&apos;);
imageEl.setAttribute(&apos;height&apos;, &apos;auto&apos;);</code></pre><p>It is also a good idea to set <code>loading=&quot;lazy&quot;</code> on the image, so it only starts loading the images when the user scrolls to them. Most browsers support this.</p><h3 id="style-caveats">Style caveats</h3><p>The <code>assets/wp-feed.css</code> file is a regular CSS file and there&apos;s nothing interesting about it. However, I wanted to let the user control the breakpoints for media queries because they may be different for every theme.</p><p>The problem is that the asset files come from CDN so I couldn&apos;t use liquid code in them. And it&apos;s not JavaScript, so the <code>data-url</code> workaround doesn&apos;t work.</p><p>Fortunately, there&apos;s a way to write CSS in the liquid templates using the <code>{% style %}</code> tag:</p><pre><code class="language-liquid">{% style %}
@media screen and (min-width: {{ block.settings.md_breakpoint }}px) {
  .wp-feed-app_article {
    grid-column: span calc(12 / {{ block.settings.num_cols_md }}) / span calc(12 / {{ block.settings.num_cols_md }});
  }
  .wp-feed-app_article:nth-child(-n+{{ grid_cols_md }})  {
    display: block;
  }
  .wp-feed-app_article:nth-child(n+{{ grid_cols_md }})  {
    display: none;
  }
}
{% endstyle %}</code></pre><p>This CSS will be rendered on the server, so it may slow down the site if abused, but it&apos;s good enough in my case. I can embed the settings values and the CSS will come as an inline <code>&lt;style&gt;</code> tag in my HTML:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/03/image-7.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="1482" height="1121" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-7.png 600w, https://codefrontend.com/content/images/size/w1000/2023/03/image-7.png 1000w, https://codefrontend.com/content/images/2023/03/image-7.png 1482w" sizes="(min-width: 720px) 720px"><figcaption>The style tag in my HTML code.</figcaption></figure><h2 id="the-admin-app">The admin app</h2><p>The next step was to create an app that embeds into the admin panel. I didn&apos;t really need anything besides the app block, but apparently, you still have to create the admin app, even if it simply holds the setup instructions.</p><p>I decided it would be nice to at least allow setting the default blog URL globally then.</p><h3 id="the-layout">The layout</h3><p>To build the user interface, I used the React components from the official Shopify design system <a href="https://polaris.shopify.com/components?ref=code-frontend">Polaris</a>. I used the <code>Page</code> component for the main container and added <code>Layout</code> with <code>AnnotatedSections</code> to mimic Shopify&apos;s own settings layout.</p><p>Here&apos;s a simplified version of what the layout code looked like:</p><pre><code class="language-jsx">&lt;Page title=&quot;App Settings&quot; divider&gt;
  &lt;Frame&gt;
    &lt;Layout&gt;
      &lt;Layout.AnnotatedSection
        id=&quot;storeDetails&quot;
        title=&quot;Global settings&quot;
        description=&quot;These settings will by default be used across all App Blocks in your theme.&quot;
      &gt;
        &lt;LegacyCard&gt;
          &lt;LegacyCard.Section&gt;
            &lt;TextField
              label=&quot;WordPress blog URL&quot;
              type=&quot;url&quot;
              placeholder=&quot;e.g. https://blog.myshopifystore.com&quot;
            /&gt;
          &lt;/LegacyCard.Section&gt;
          &lt;LegacyCard.Section
            title={
              &lt;Text variant=&quot;headingXs&quot; as=&quot;h3&quot;&gt;
                THEME CONFIGURATION
              &lt;/Text&gt;
            }
            subdued
          &gt;
            &lt;Columns columns=&quot;1fr auto&quot; gap=&quot;4&quot;&gt;
              &lt;Text variant=&quot;bodyMd&quot; as=&quot;p&quot;&gt;
                In the theme editor add an app section -{&apos; &apos;}
                &lt;em&gt;Wordpress Feed&lt;/em&gt;. &lt;br /&gt; You can also customize the
                appearance there.
              &lt;/Text&gt;
              &lt;Button&gt;Customize&lt;/Button&gt;
            &lt;/Columns&gt;
          &lt;/LegacyCard.Section&gt;
        &lt;/LegacyCard&gt;
      &lt;/Layout.AnnotatedSection&gt;
    &lt;/Layout&gt;
  &lt;/Frame&gt;
&lt;/Page&gt;</code></pre><p>After some back and forth with the reviewers later, I ended up with this final design:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/03/image-9.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="2000" height="1119" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-9.png 600w, https://codefrontend.com/content/images/size/w1000/2023/03/image-9.png 1000w, https://codefrontend.com/content/images/size/w1600/2023/03/image-9.png 1600w, https://codefrontend.com/content/images/2023/03/image-9.png 2166w" sizes="(min-width: 720px) 720px"><figcaption>The admin app design for my plugin.&#xA0;</figcaption></figure><p>Overall, there isn&apos;t anything particularly interesting in the layout code, but I enjoyed how quickly you can get good results if you use Shopify&apos;s design system, and I recommend you do the same if you build an embedded app.</p><h3 id="loading-and-saving-the-settings">Loading and saving the settings</h3><p>The demo app came with a hook for making authenticated queries to the backend server, so I simply used it to fetch my settings. It&apos;s based on <code>react-query</code>:</p><pre><code class="language-js">const { data: appSettingsData, refetch } = useAppQuery({
  url: &apos;/api/app-settings&apos;,
  reactQueryOptions: {
    onSuccess: (data) =&gt; {
      setUrlText(data.url);
      setUiState(&apos;idle&apos;);
    },
    onError: () =&gt; {
      setToastProps({
        content: &apos;There was an error loading app settings&apos;,
        error: true,
      });
    },
    select: (data) =&gt; ({
      ...data,
      url: data.url ?? &apos;&apos;,
    }),
  },
});</code></pre><p>I also kept it simple for saving the settings and used the native <code>fetch</code> API:</p><pre><code class="language-js">const handleClickSave = async () =&gt; {
  setUiState(&apos;saving&apos;);
  const response = await fetch(&apos;/api/app-settings&apos;, {
    method: &apos;PUT&apos;,
    headers: {
      &apos;Content-Type&apos;: &apos;application/json&apos;,
    },
    body: JSON.stringify({
      url: urlText,
    }),
  });

  await refetch();

  if (response.ok) {
    setToastProps({
      content: &apos;App settings were saved successfully&apos;,
    });
  } else {
    setToastProps({
      content: &apos;There was an error saving app settings&apos;,
      error: true,
    });
  }

  setUiState(&apos;idle&apos;);
};</code></pre><p>The toasts were already set up in the example app, so I reused those, and to manage my loading states I simply added some local state:</p><pre><code class="language-js">const [uiState, setUiState] = useState(&apos;loading&apos;); // loading | idle | saving</code></pre><p>While the settings were loading I would show a skeleton instead of an input field:</p><pre><code class="language-jsx">{uiState === &apos;loading&apos; ? (
  &lt;SkeletonBodyText /&gt;
) : (
  &lt;TextField
    label=&quot;WordPress blog URL&quot;
    type=&quot;url&quot;
    value={urlText ?? &apos;&apos;}
    onChange={handleChangeUrl}
    placeholder=&quot;e.g. https://blog.myshopifystore.com&quot;
  /&gt;
)}</code></pre><p>Also, I needed a button to save the settings, and Polaris, conveniently, had a <code>ContextualSaveBar</code> I could toggle when the URL in the input field didn&apos;t match the one that came from the backend query:</p><pre><code class="language-jsx">{hasUnsavedChanges &amp;&amp; (
  &lt;ContextualSaveBar
    alignContentFlush
    message=&quot;Unsaved changes&quot;
    saveAction={{
      onAction: handleClickSave,
      loading: uiState === &apos;saving&apos;,
    }}
    discardAction={{
      onAction: handleClickDismiss,
    }}
  /&gt;
)}</code></pre><h3 id="redirecting-to-the-current-theme">Redirecting to the current theme</h3><p>As I was researching existing apps that solved my problem, I saw some apps had a button that opened the theme editor for the current theme. I wanted that as well. It was trickier to add than I thought.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/03/image-21.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="751" height="184" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-21.png 600w, https://codefrontend.com/content/images/2023/03/image-21.png 751w" sizes="(min-width: 720px) 720px"><figcaption>The button redirects to Theme editor.</figcaption></figure><p>The Shopify docs are a bit... hmm... <em>verbose</em>. That&apos;s fine, but it makes it hard to find something specific when you&apos;re not sure what exactly you&apos;re looking for.</p><p>I did some reverse engineering, by inspecting another app&apos;s minified source code to find this line:</p><pre><code class="language-js">window.open(&quot;https://&quot;.concat(u, &quot;/admin/themes/&quot;).concat(p, &quot;/editor&quot;), &quot;_blank&quot;)</code></pre><p>I searched the docs for <code>/admin/themes/</code> and found this hidden deep in some deprecated API reference:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/03/image-8.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="822" height="285" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-8.png 600w, https://codefrontend.com/content/images/2023/03/image-8.png 822w" sizes="(min-width: 720px) 720px"><figcaption>The URL was <code>/admin/themes/current/editor</code>.</figcaption></figure><p>Now the question was how to redirect the user to this URL.</p><p>Apparently, Shopify&apos;s app bridge uses <code>redux</code> under the hood and exposes the <code>dispatch</code> method and some action creators. I ended up implementing the redirect like this:</p><pre><code class="language-js">import { useAppBridge } from &apos;@shopify/app-bridge-react&apos;;
import { toAdminPath } from &apos;@shopify/app-bridge/actions/Navigation/Redirect&apos;;

export default function HomePage() {
  const app = useAppBridge();

  const handleClickCustomize = () =&gt; {
    app.dispatch(
      toAdminPath({
        path: &apos;/admin/themes/current/editor&apos;,
        newContext: true,
      })
    );
  };

  // ...
}</code></pre><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F937;&#x200D;&#x2642;&#xFE0F;</div><div class="kg-callout-text">I later discovered a section in docs that explains this. Turns out it was called <a href="https://shopify.dev/docs/apps/online-store/theme-app-extensions/extensions-framework?ref=code-frontend#example-url">deep linking</a>.</div></div><h2 id="the-backend-server">The backend server</h2><p>The example app came with an ExpressJS server with authentication already wired up. I left the authentication in place and removed all other endpoints.</p><p>I added the two new endpoints I needed:</p><pre><code class="language-js">app.put(&apos;/api/app-settings&apos;, async (req, res) =&gt; {
  // ...
});

app.get(&apos;/api/app-settings&apos;, async (req, res) =&gt; {
  // ...
});</code></pre><p>I really didn&apos;t want to set up a database just to store the blog URL, so I looked for a way to do it using the Shopify API. I found that every app can store some <em>metafields</em> through a GraphQL API.</p><p>To get and set the metafields I had to query my app installation:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">const APP_DATA_QUERY = `
  query GetAppData($namespace: String, $key: String!) {
    currentAppInstallation {
      id
      metafield(namespace: $namespace, key: $key) {
        id
        key
        value
        namespace
      }
    }
  }
`;

const session = res.locals.shopify.session;
const client = new shopify.api.clients.Graphql({ session });

const currentAppInstallation = await client
  .query({
    data: {
      query: APP_DATA_QUERY,
      variables: {
        namespace: &apos;app_settings&apos;,
        key: &apos;blog_url&apos;,
      },
    },
  })
  .then(({ body }) =&gt; body.data.currentAppInstallation);</code></pre><figcaption>Querying app metafield values.</figcaption></figure><p>This is enough for the <strong>GET</strong> endpoint and I can return the metafield value:</p><pre><code class="language-js">res.status(200).send({ url: currentAppInstallation.metafield?.value });</code></pre><p>For the PUT endpoint, I need to update the existing value:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">const SET_METAFIELD_MUTATION = `
    mutation CreateAppDataMetafield($metafieldsSetInput: [MetafieldsSetInput!]!) {
      metafieldsSet(metafields: $metafieldsSetInput) {
        metafields {
          id
          key
          value
          namespace
        }
      }
    }
  `;

await client.query({
  data: {
    query: SET_METAFIELD_MUTATION,
    variables: {
      metafieldsSetInput: [
        {
          namespace: &apos;app_settings&apos;,
          key: &apos;blog_url&apos;,
          type: &apos;single_line_text_field&apos;,
          value: url,
          // &#x1F447; Here&apos;s why I had to query the app installation previously
          ownerId: currentAppInstallation.id,
        },
      ],
    },
  },
});</code></pre><figcaption>Updating app metafield values.</figcaption></figure><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F937;&#x200D;&#x2642;&#xFE0F;</div><div class="kg-callout-text">For some reason metafields did not accept empty strings or null values, so when <code>url</code> is empty or <code>null</code> I called the <code>metafieldDelete</code> mutation instead.</div></div><p>This was enough to have a working backend server.</p><h3 id="deployment">Deployment</h3><p>The deployment was fairly straightforward. I simply followed <a href="https://shopify.dev/docs/apps/deployment/web?ref=code-frontend">the instructions</a> for deployment on <a href="https://fly.io/?ref=code-frontend">fly.io</a>. The only confusing bit is that API docs use the term <strong>API key</strong>, and in the app settings, it&apos;s <strong>client id</strong>.</p><p>I also had to make sure to update the API routes to point to the deployed version:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/03/image-10.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="1214" height="455" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-10.png 600w, https://codefrontend.com/content/images/size/w1000/2023/03/image-10.png 1000w, https://codefrontend.com/content/images/2023/03/image-10.png 1214w" sizes="(min-width: 720px) 720px"></figure><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F937;&#x200D;&#x2642;&#xFE0F;</div><div class="kg-callout-text"><strong>Note:</strong> the URLs reset when you boot up the development server. I&apos;ve no idea if it could mess something up on production.</div></div><h2 id="submitting-to-the-app-store">Submitting to the app store</h2><p>It took me a whole day just to fill in the forms for review and create the app store page. The form is <em>huge</em>, but not complicated.</p><p>I wrote the description, outlined the features, and created the visuals in Canva. The most annoying part was that I had to set up a privacy policy page.</p><p>My solution was to simply create a <a href="https://viston.notion.site/VistonWP-WordPress-Feed-App-572e8b0fffef40abb3527bdd3da91cfd?ref=code-frontend">page on Notion</a>, copy the privacy policy from some other app and adjust it a little:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/03/image-11.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="1006" height="1170" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-11.png 600w, https://codefrontend.com/content/images/size/w1000/2023/03/image-11.png 1000w, https://codefrontend.com/content/images/2023/03/image-11.png 1006w" sizes="(min-width: 720px) 720px"></figure><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F624;</div><div class="kg-callout-text">Keep in mind you have to pay a <strong>$99 fee</strong> before you can submit your app.</div></div><h3 id="pitfalls"><strong>Pitfalls</strong></h3><p>I got rejected from the store twice, here&apos;s what you should know if you want to avoid my mistakes.</p><h3 id="gdpr-webhooks">GDPR webhooks</h3><p>My app doesn&apos;t manage any user data, so I ignored this requirement. Turns out you still need to host the webhooks:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/03/image-13.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="583" height="136"></figure><p>Luckily, the demo app had them set up, and I simply needed to add the URLs to the app settings:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/03/image-14.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="1211" height="346" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-14.png 600w, https://codefrontend.com/content/images/size/w1000/2023/03/image-14.png 1000w, https://codefrontend.com/content/images/2023/03/image-14.png 1211w" sizes="(min-width: 720px) 720px"><figcaption>GDPR webhook endpoints.</figcaption></figure><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4CC;</div><div class="kg-callout-text">If you do manage user data, you would need to delete it in the webhooks upon request. In my case, I didn&apos;t need to touch them, they automatically respond with 200 OK.</div></div><h3 id="no-generic-name">No generic name</h3><p>I didn&apos;t care about building a brand, so I initially named the app <code>WordPress Feed</code>. Turns out you must come up with a name no matter what:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/03/image-15.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="612" height="226" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-15.png 600w, https://codefrontend.com/content/images/2023/03/image-15.png 612w"></figure><h3 id="screenshot-requirements">Screenshot requirements</h3><p>The screenshots that you put on your store must not include a header for some reason:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/03/image-16.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="586" height="188"></figure><p>I just fixed it in Canva.</p><h3 id="add-instructions-for-users">Add instructions for users</h3><p>Shopify requires you to over-explain how to use your app in the app&apos;s UI: </p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/03/image-17.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="593" height="500"></figure><p>I added a detailed guide on how to set up the app, basically teaching users how to use app blocks. Additionally, I recorded a short <a href="https://www.loom.com/share/46e7482ae41b44afbad2a56a53ea4de4?ref=code-frontend">loom walkthrough video</a> and added it to the UI:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/03/image-18.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="1259" height="774" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-18.png 600w, https://codefrontend.com/content/images/size/w1000/2023/03/image-18.png 1000w, https://codefrontend.com/content/images/2023/03/image-18.png 1259w" sizes="(min-width: 720px) 720px"></figure><h3 id="lighthouse-tests">Lighthouse tests</h3><p>There is nothing in the form itself that mentions lighthouse tests, so it&apos;s easy to miss this requirement. You need to run the tests in your devtools and calculate the impact on performance:</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/03/image-19.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="616" height="142" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-19.png 600w, https://codefrontend.com/content/images/2023/03/image-19.png 616w"></figure><h2 id="final-thoughts">Final Thoughts</h2><p>After a week of reviews, I finally got this message:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/03/image-20.png" class="kg-image" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know" loading="lazy" width="861" height="651" srcset="https://codefrontend.com/content/images/size/w600/2023/03/image-20.png 600w, https://codefrontend.com/content/images/2023/03/image-20.png 861w" sizes="(min-width: 720px) 720px"><figcaption>My app was approved!</figcaption></figure><p>They were kind enough to update the app URL, and here it is now: <a href="https://apps.shopify.com/vistonwp-wordpress-feed?ref=code-frontend">https://apps.shopify.com/vistonwp-wordpress-feed</a></p><p>You might ask -<em> is it worth building apps for Shopify</em>?</p><p>For me, it was. I wanted to see the whole process and I also learned a lot. I also got to write about my experience &#x1F609;. Ultimately, it depends on what you want to get out of it. Here are the pros/cons as I see them to help you decide for yourself:</p><h3 id="pros">Pros</h3><ul><li><strong>Low-friction way to learn full-stack development.</strong> You get to try building an embedded SPA, a backend server, build client-side scripts, work with templates, possibly interact with databases and help people in the process.</li><li><strong>Get to leverage an existing platform and tools.</strong> Building with Polaris was quick and the demo app had the framework already set up, so I could focus on building my app.</li><li><strong>Can monetize quite easily</strong>, if that&apos;s what you want.</li><li><strong>Built-in distribution channel through the marketplace</strong>, so you don&apos;t need to market your app as much. </li></ul><h3 id="cons">Cons</h3><ul><li><strong>It costs $99 to submit your app for review.</strong></li><li><strong>The review process was long and had a few pitfalls</strong>, even for my simple app.</li><li><strong>Limited flexibility in tooling.</strong> If you like what Shopify gives you, awesome, but if you want to use something else, like NextJS or another backend framework - you&apos;re on your own.</li></ul><h3 id="resources">Resources</h3><p>I researched very little on how to build Shopify apps but found an excellent blog written by <a href="https://twitter.com/hipreetam93?ref=code-frontend">Preetam Nath</a>. If you&apos;re serious about building a Shopify app, I highly recommend reading his guide:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.preetamnath.com/blog/shopify-micro-saas?ref=code-frontend"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Guide: Make Money Building Shopify Micro-SaaS Apps</div><div class="kg-bookmark-description">A long-form guide on building your first micro-SaaS business on the Shopify platform.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://uploads-ssl.webflow.com/5e0849b50d37a13ecd286934/5e085e467436085625deef43_favicon-32.png" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know"></div></div><div class="kg-bookmark-thumbnail"><img src="https://uploads-ssl.webflow.com/5e085291ed2a2769a872e587/5f9e463b47740110e94b8e47_shopify%20micro%20saas%20guide%20build%20apps%20make%20money.png" alt="Want to Build a Shopify App? Here&#x2019;s What You Should Know"></div></a></figure><p>And that&apos;s it!</p><p>If you enjoyed this long post, let me know in the comments or on <a href="https://twitter.com/VincasStonys?ref=code-frontend">Twitter</a> (DMs open), and feel free to share it!</p>]]></content:encoded></item><item><title><![CDATA[Technical SEO Basics for React Developers]]></title><description><![CDATA[Here's a practical introduction to everything you need to know about SEO as a web developer.]]></description><link>https://codefrontend.com/introduction-to-seo-for-react-developers/</link><guid isPermaLink="false">632891ec4389f73e27914aa3</guid><category><![CDATA[Deep Dive]]></category><category><![CDATA[ReactJS]]></category><category><![CDATA[Quick Guide]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Sat, 11 Feb 2023 16:45:37 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2023/02/technical-seo-cover.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://codefrontend.com/content/images/2023/02/technical-seo-cover.jpeg" alt="Technical SEO Basics for React Developers"><p>Many developers, even experienced ones, don&apos;t know the first thing about search engine optimization (SEO). And for a long time, I didn&apos;t know either. SEO is mostly invisible, so it&apos;s easy to ignore, even though you shouldn&apos;t.</p><p>Google is often the most important source of traffic for businesses and as a developer, it&apos;s <em>your</em> job to take care of the technical side of SEO. </p><blockquote class="kg-blockquote-alt">Every respectable web developer must know at least the basics of SEO.</blockquote><p>Throughout my years of building web apps, I&apos;ve learned a thing or two about making them rank well on Google, and I&apos;m here to break it all down for you.</p><p>Here&apos;s a 12-step technical SEO checklist to go through before launching any website.</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/02/seo-checklist-for-developers-1.jpg" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/02/seo-checklist-for-developers-1.jpg 600w, https://codefrontend.com/content/images/size/w1000/2023/02/seo-checklist-for-developers-1.jpg 1000w, https://codefrontend.com/content/images/2023/02/seo-checklist-for-developers-1.jpg 1600w" sizes="(min-width: 720px) 720px"></figure><h2 id="1-add-title-and-description">1) Add Title and Description</h2><p>Title and description are the first things that the user is going to see in the search results.</p><p>Nobody will click your link if it says &quot;React App&quot;, and if nobody clicks on your link, the click-through rate will drop, lowering your Google ranking. Needless to say, title and description are essential for SEO.</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/02/title-and-description.jpg" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/02/title-and-description.jpg 600w, https://codefrontend.com/content/images/size/w1000/2023/02/title-and-description.jpg 1000w, https://codefrontend.com/content/images/2023/02/title-and-description.jpg 1600w" sizes="(min-width: 720px) 720px"></figure><p>Fortunately, it&apos;s simple to add them:</p><ol><li>In your HTML file (usually <code>index.html</code>) inside the <code>&lt;head&gt;</code> tag add a <code>&lt;title&gt;</code> tag with the title of your page.</li><li>Similarly, inside the <code>&lt;head&gt;</code> add a <code>&lt;meta name=&quot;description&quot; content=&quot;...&quot;&gt;</code> tag where <code>content</code> is your page description.</li></ol><p>Here&apos;s what it will look like:</p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;html&gt;
  &lt;head&gt;
    &lt;meta
      name=&quot;description&quot;
      content=&quot;Daily web development tips and guides for JavaScript...&quot;
    /&gt;
    &lt;title&gt;Code Frontend - Become a Better Web Developer&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    ...
  &lt;/body&gt;
&lt;/html&gt;</code></pre><figcaption>Example code for adding SEO title and description.</figcaption></figure><p>The example above will set a global title for any page in your React app, but you might want to customize them for every page. In that case, I suggest you look into the <a href="https://github.com/nfl/react-helmet?ref=code-frontend">react-helmet</a> library.</p><p><strong>Some recommendations:</strong></p><ul><li>Make the title descriptive but under 75 characters, so it&apos;s not clipped. I recommend around 60 to be safe.</li><li>Keep the description below 160 characters.</li></ul><p>Ultimately, Google will decide what to show in the search results, so don&apos;t be surprised if it&apos;s different than what you provided. Google views it as a recommendation only.</p><h2 id="2-add-open-graph-meta-tags">2) Add Open Graph Meta Tags</h2><p>Even though OG tags won&apos;t impact your search results directly, they will impact the appearance of your links on social media, which can impact traffic and in turn your Google rankings.</p><p>Facebook, Twitter, or Linkedin, automatically add a title, image, and description when you share your site&apos;s links. You can preview them here: <a href="https://t.co/ByTXdU8Zxj?ref=code-frontend" rel="noopener noreferrer nofollow"><a href="http://socialsharepreview.com/?ref=code-frontend">http://socialsharepreview.com</a></a>.</p><p>That&apos;s what the OG meta tags let you customize.</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/02/og-meta-tags.jpg" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/02/og-meta-tags.jpg 600w, https://codefrontend.com/content/images/size/w1000/2023/02/og-meta-tags.jpg 1000w, https://codefrontend.com/content/images/2023/02/og-meta-tags.jpg 1600w" sizes="(min-width: 720px) 720px"></figure><p>Here&apos;s how to add them:</p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;html&gt;
  &lt;head&gt;
    &lt;meta property=&quot;og:title&quot; content=&quot;Code Frontend - Become a Better Web Developer&quot;&gt;
    &lt;meta property=&quot;og:image&quot; content=&quot;https://codefrontend.com/content/images/2022/07/code-frontend-social-image-1.png&quot;&gt;
    &lt;meta property=&quot;og:type&quot; content=&quot;website&quot;&gt;
    &lt;meta property=&quot;og:url&quot; content=&quot;https://codefrontend.com/&quot;&gt;
  &lt;/head&gt;
  ...
&lt;/html&gt;</code></pre><figcaption>Example code for adding OG meta tags.</figcaption></figure><p>Inside the <code>&lt;head&gt;</code> tag of your page add <code>&lt;meta&gt;</code> tags with the attributes <code>property=&quot;og:&lt;property&gt;&quot;</code> and <code>content=&quot;value&quot;</code>. The necessary properties as outlined at <a href="https://ogp.me/?ref=code-frontend">https://ogp.me</a> are:</p><ul><li><code>og:title</code> - the page title.</li><li><code>og:type</code> - pretty always <code>website</code>.</li><li><code>og:image</code> - URL of the image for your website links.</li><li><code>og:url</code> - canonical URL. Usually, the link of your page unless it&apos;s a copy of another page, in which case put the link to the original.</li></ul><h2 id="3-add-structured-data-markup">3) Add Structured Data Markup</h2><p>Recipe, review, and e-shop sites should all use structured data markup. It makes search results more detailed by attaching prices, ratings, and other metadata.</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/02/structured-data-markup.jpg" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/02/structured-data-markup.jpg 600w, https://codefrontend.com/content/images/size/w1000/2023/02/structured-data-markup.jpg 1000w, https://codefrontend.com/content/images/2023/02/structured-data-markup.jpg 1600w" sizes="(min-width: 720px) 720px"></figure><p>You don&apos;t need to write it by hand though, simply generate it at <a href="https://www.google.com/webmasters/markup-helper/u/0/?ref=code-frontend">https://www.google.com/webmasters/markup-helper/u/0/</a> or dynamically with <a href="https://amagiacademy.com/blog/posts/2020-09-05/structured-data-with-react-helmet?ref=code-frontend">react-helmet</a>.</p><p>The important bit is to wrap the schema with <code>&lt;script type=&quot;application/ld+json&quot;&gt;</code>. Here&apos;s what it might look like in React:</p><pre><code class="language-jsx">&lt;Helmet&gt;
  &lt;script type=&quot;application/ld+json&quot;&gt;
  {JSON.stringify({
    &quot;@context&quot;: &quot;https://schema.org/&quot;,
    &quot;@type&quot;: &quot;Recipe&quot;,
    &quot;name&quot;: &quot;Party Coffee Cake&quot;,
    &quot;author&quot;: {
      &quot;@type&quot;: &quot;Person&quot;,
      &quot;name&quot;: &quot;Mary Stone&quot;
    },
    &quot;datePublished&quot;: &quot;2018-03-10&quot;,
    &quot;description&quot;: &quot;This coffee cake is awesome and perfect for parties.&quot;,
    &quot;prepTime&quot;: &quot;PT20M&quot;
  })}
  &lt;/script&gt;
&lt;Helmet&gt;</code></pre><p>For more information, I suggest you read the google docs: <a href="https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data?ref=code-frontend">https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data</a></p><h2 id="4-add-sitemap-and-robotstxt">4) Add Sitemap and Robots.txt</h2><p>These files help google crawl your site more easily.</p><p>The sitemap lets Google know how to prioritize your links and <code>Robots.txt</code> allows you to block Google from visiting and indexing unimportant resources.</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/02/sitemap-and-robots.jpg" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/02/sitemap-and-robots.jpg 600w, https://codefrontend.com/content/images/size/w1000/2023/02/sitemap-and-robots.jpg 1000w, https://codefrontend.com/content/images/2023/02/sitemap-and-robots.jpg 1600w" sizes="(min-width: 720px) 720px"></figure><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4E3;</div><div class="kg-callout-text">Both sitemap and robots.txt file should be hosted at the root level of your domain, e.g. <a href="https://codefrontend.com/sitemap.xml">https://codefrontend.com/sitemap.xml</a> and <a href="https://codefrontend.com/robots.txt">https://codefrontend.com/robots.txt</a>.</div></div><h3 id="robotstxt">Robots.txt</h3><p>Here&apos;s what the <code>Robots.txt</code> file looks like for this blog: </p><figure class="kg-card kg-code-card"><pre><code class="language-txt">User-agent: *
Sitemap: https://codefrontend.com/sitemap.xml
Disallow: /ghost/
Disallow: /p/
Disallow: /email/
Disallow: /r/</code></pre><figcaption>Robots.txt for codefrontend.com</figcaption></figure><p>I don&apos;t want Google snooping around the admin panel of my blog, which is what the <code>Robots.txt</code> prevents with the <code>Disallow</code> rules. Available options are covered in Google docs: </p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://developers.google.com/search/docs/crawling-indexing/robots/create-robots-txt?ref=code-frontend"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Create and submit a robots.txt file | Google Search Central | Documentation | Google Developers</div><div class="kg-bookmark-description">A robots.txt file lives at the root of your site. Learn how to create a robots.txt file, see examples, and explore robots.txt rules.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.gstatic.com/devrel-devsite/prod/v1cfe30952218fac985c78c6c0da0de11fade09219719e8a9dbc367e6d5d7cee9/developers/images/favicon.png" alt="Technical SEO Basics for React Developers"><span class="kg-bookmark-author">Google Developers</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://developers.google.com/static/search/images/home-social-share-lockup.png" alt="Technical SEO Basics for React Developers"></div></a></figure><h3 id="sitemap">Sitemap</h3><p>The simplest version of a sitemap is just a text file with available URLs:</p><pre><code class="language-txt">https://www.example.com/file1.html
https://www.example.com/file2.html</code></pre><p>However, it&apos;s more common to use an XML version, because it allows adding some metadata, like <code>lastmod</code> - date of the last update, which is useful for blogs.</p><p>You can build the sitemap manually or generate it at <a href="https://www.xml-sitemaps.com/?ref=code-frontend">https://www.xml-sitemaps.com</a>. For a truly dynamic sitemap, you might need to create a server endpoint that generates it or generate it at build time.</p><p>Read more about sitemaps in Google docs: <a href="https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap?ref=code-frontend">https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap</a></p><h2 id="5-ensure-great-performance">5) Ensure Great Performance</h2><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/02/performance.jpg" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/02/performance.jpg 600w, https://codefrontend.com/content/images/size/w1000/2023/02/performance.jpg 1000w, https://codefrontend.com/content/images/2023/02/performance.jpg 1600w" sizes="(min-width: 720px) 720px"></figure><p>Google prefers websites with good loading speed and user experience.</p><p>Run an audit at <a href="https://t.co/xJUEl2ZoUn?ref=code-frontend" rel="noopener noreferrer nofollow"><a href="https://web.dev/measure/?ref=code-frontend"><a href="https://web.dev/measure/?ref=code-frontend">https://web.dev/measure/</a></a></a> which will tell you what you should improve. Page load time is the most important factor to consider.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/02/image.png" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1239" height="655" srcset="https://codefrontend.com/content/images/size/w600/2023/02/image.png 600w, https://codefrontend.com/content/images/size/w1000/2023/02/image.png 1000w, https://codefrontend.com/content/images/2023/02/image.png 1239w" sizes="(min-width: 720px) 720px"><figcaption>Lighthouse metrics for codefrontend.com</figcaption></figure><p>Here are some performance improvements you can do as a React developer:</p><ul><li><a href="https://reactjs.org/docs/code-splitting.html?ref=code-frontend">Code-splitting</a> can improve initial page load times.</li><li>Optimize images at build-time and lazy-load them at run-time.</li><li>Use defer and async attributes for non-critical scripts.</li><li>Pre-render your React code on the server.</li><li>Google seems to like <a href="https://amp.dev/?ref=code-frontend">AMP</a>.</li></ul><p>A lot of these performance optimizations are handled out of the box if you&apos;re using a React framework like <a href="https://nextjs.org/?ref=code-frontend">NextJS</a>.</p><h2 id="6-server-side-render">6) Server-Side Render</h2><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/02/server-side-rendering.jpg" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/02/server-side-rendering.jpg 600w, https://codefrontend.com/content/images/size/w1000/2023/02/server-side-rendering.jpg 1000w, https://codefrontend.com/content/images/2023/02/server-side-rendering.jpg 1600w" sizes="(min-width: 720px) 720px"></figure><p>Single-page applications are notoriously bad for SEO because they send a blank HTML page initially and generate it on the browser using JavaScript.</p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;title&gt;React App&lt;/title&gt;
    &lt;script type=&quot;module&quot; crossorigin src=&quot;/assets/index.js&quot;&gt;&lt;/script&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;/assets/index.css&quot;&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;noscript&gt;You need to enable JavaScript to run this app.&lt;/noscript&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre><figcaption>What the HTML code for React app usually looks like before scripts are run.</figcaption></figure><p>Google needs to wait for the scripts to run before it can parse content. Needless to say - it&apos;s not perfect at it.</p><p>For many apps, this doesn&apos;t matter, but for content-heavy websites, you should pre-render your React code on the server, and then <a href="https://beta.reactjs.org/reference/react-dom/client/hydrateRoot?ref=code-frontend">hydrate</a> it on the client. It&apos;s tricky to implement on your own, and that&apos;s what frameworks like NextJS, Remix, or Astro can help you with.</p><h2 id="7-ensure-correct-file-formats">7) Ensure Correct File Formats</h2><p>Large images are often the main reason your website loads slowly.</p><p>PNG images are very large, often more than 10x larger than web-optimized formats such as JPEG or WEBP with no perceived difference. SVG images usually contain metadata that can be safely removed to significantly reduce the image size.</p><p>You can manually compress your images at <a href="https://convertio.co/png-jpg/?ref=code-frontend">convertio.co/png-jpg/</a> or configure your bundler to do that for you. NextJS even has a custom <code>Image</code> component that you should use.</p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/02/image-formats.jpg" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/02/image-formats.jpg 600w, https://codefrontend.com/content/images/size/w1000/2023/02/image-formats.jpg 1000w, https://codefrontend.com/content/images/2023/02/image-formats.jpg 1600w" sizes="(min-width: 720px) 720px"></figure><h3 id="using-svgs">Using SVGs</h3><p>How images are used is also important.</p><p>For instance, you can add SVGs directly in your JSX, but that can significantly hurt page load times and performance because SVGs would be bundled together with your JavaScript code and can&apos;t be loaded asynchronously. </p><p>You should always prefer the <code>&lt;img&gt;</code> tag for images.</p><h3 id="responsive-images">Responsive images</h3><p>A great way to make sure images aren&apos;t slowing down your website is to use different image sizes for different screen sizes. For instance, you don&apos;t need a highly detailed image on mobile screens.</p><p>Normally, the <code>src</code> attribute on the <code>&lt;img&gt;</code> tag only allows a single resource, but you can provide <code>srcset</code> and <code>sizes</code> attributes to change the source based on the screen size.</p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;img
  srcset=&quot;image-480w.jpg 480w, image-800w.jpg 800w&quot;
  sizes=&quot;(max-width: 600px) 480px, 800px&quot;
  src=&quot;image-800w.jpg&quot;
/&gt;</code></pre><figcaption>Example code for responsive images.</figcaption></figure><p>A detailed article on how responsive images work can be found here &#x1F447;</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images?ref=code-frontend"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Responsive images - Learn web development | MDN</div><div class="kg-bookmark-description">That&#x2019;s a wrap for responsive images &#x2014; we hope you enjoyed playing with these new techniques. As a recap, there are two distinct problems we&#x2019;ve been discussing here:</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://developer.mozilla.org/favicon-48x48.cbbd161b.png" alt="Technical SEO Basics for React Developers"><span class="kg-bookmark-author">MDN Web Docs.logo-m{fill:var(--text-link)}</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://developer.mozilla.org/mdn-social-share.cd6c4a5a.png" alt="Technical SEO Basics for React Developers"></div></a></figure><h2 id="8-minify-your-code">8) Minify Your Code</h2><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/02/minified-code.jpg" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/02/minified-code.jpg 600w, https://codefrontend.com/content/images/size/w1000/2023/02/minified-code.jpg 1000w, https://codefrontend.com/content/images/2023/02/minified-code.jpg 1600w" sizes="(min-width: 720px) 720px"></figure><p>Minification significantly reduces the size of your code by removing spaces and replacing names with shorter alternatives. If you&apos;re using modern build tools like Vite, Parcel, or a framework like NextJS, this will be taken care of automatically.</p><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4E3;</div><div class="kg-callout-text">Don&apos;t forget to build your code for production.</div></div><p>In case you&apos;re maintaining your own Webpack configs or using plain JavaScript source files - make sure you minify your code.</p><h2 id="9-ensure-good-accessibility">9) Ensure Good Accessibility</h2><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/02/accessibility.jpg" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/02/accessibility.jpg 600w, https://codefrontend.com/content/images/size/w1000/2023/02/accessibility.jpg 1000w, https://codefrontend.com/content/images/2023/02/accessibility.jpg 1600w" sizes="(min-width: 720px) 720px"></figure><p>When you make your website accessible for mobile devices and screen readers, you naturally make it easier for Google to crawl and understand your content as well.</p><p>Accessibility is often invisible, it&apos;s things like:</p><ul><li><code>alt</code> attribute on <code>&lt;img&gt;</code> tags.</li><li><code>aria-label</code> attribute for image-only buttons.</li><li><code>role</code> for clickable non-button elements.</li><li><code>&lt;a&gt;</code> link tags instead of calls to <code>history.push()</code> for navigation.</li></ul><p>Here&apos;s more about accessibility in React docs &#x1F447;</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://reactjs.org/docs/accessibility.html?ref=code-frontend"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Accessibility &#x2013; React</div><div class="kg-bookmark-description">A JavaScript library for building user interfaces</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://reactjs.org/logo-180x180.png" alt="Technical SEO Basics for React Developers"><span class="kg-bookmark-author">React</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://reactjs.org/logo-og.png" alt="Technical SEO Basics for React Developers"></div></a></figure><h2 id="10-ensure-good-content-structure">10) Ensure Good Content Structure</h2><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/02/hierarchy-and-semantic-tags.jpg" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/02/hierarchy-and-semantic-tags.jpg 600w, https://codefrontend.com/content/images/size/w1000/2023/02/hierarchy-and-semantic-tags.jpg 1000w, https://codefrontend.com/content/images/2023/02/hierarchy-and-semantic-tags.jpg 1600w" sizes="(min-width: 720px) 720px"></figure><p>Using semantic tags and good content hierarchy will make it easier for Google to understand the meaning of your content. It can then display it as a list or FAQ, further improving your page ranking.</p><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4E3;</div><div class="kg-callout-text">A common beginner mistake is overusing <code>&lt;div&gt;</code> tags and underutilizing semantic tags like <code>&lt;article&gt;</code>, <code>&lt;nav&gt;</code>, <code>&lt;p&gt;</code>, and <a href="https://developer.mozilla.org/en-US/docs/Glossary/Semantics?ref=code-frontend#semantic_elements">others</a>.</div></div><p>Here are some tips:</p><ul><li>use HTML tags that make the most sense for your content;</li><li>use heading tags in hierarchical order, <code>&lt;h1&gt;</code> then <code>&lt;h2&gt;</code>, but not <code>&lt;h1&gt;</code> then <code>&lt;h3&gt;</code>.</li></ul><h2 id="11-ensure-good-url-structure">11) Ensure Good URL Structure</h2><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/02/url-structure.jpg" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/02/url-structure.jpg 600w, https://codefrontend.com/content/images/size/w1000/2023/02/url-structure.jpg 1000w, https://codefrontend.com/content/images/2023/02/url-structure.jpg 1600w" sizes="(min-width: 720px) 720px"></figure><p>Google likes short, clear, but descriptive URLs that make sense based on your content.</p><p>Make sure your pages are linked together in a logical, hierarchical way. If it makes sense for people, Google will like it as well.</p><p>Make sure that:</p><ul><li>your URLs aren&apos;t too long;</li><li>all duplicate pages have canonical links;</li><li>every page can be accessed from the root URL;</li><li>your URLs follow a tree-like hierarchy;</li><li>the URLs include the main keywords based on the content.</li></ul><p>Do not underestimate the importance of good URL structure. It can be the difference between getting #1 on Google or not even appearing on the 1st page of search results.</p><h2 id="12-use-https">12) Use HTTPS</h2><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2023/02/https.jpg" class="kg-image" alt="Technical SEO Basics for React Developers" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/02/https.jpg 600w, https://codefrontend.com/content/images/size/w1000/2023/02/https.jpg 1000w, https://codefrontend.com/content/images/2023/02/https.jpg 1600w" sizes="(min-width: 720px) 720px"></figure><p>I can&apos;t help but be surprised how some websites in 2023 aren&apos;t using HTTPS. There is no reason not to - it&apos;s simple and free to do. </p><blockquote class="kg-blockquote-alt">Any website, especially those that require login credentials, should use HTTPS - <a href="https://www.cloudflare.com/learning/ssl/what-is-https/?ref=code-frontend#:~:text=Any%20website%2C%20especially%20those%20that%20require%20login%20credentials%2C%20should%20use%20HTTPS">Cloudflare</a></blockquote><p>The apps we build with React usually include gathering data from our users, so HTTPS is a no-brainer. Besides, Google will deprioritize your website if it&apos;s unsecured.</p><p>I use <a href="https://cloudflare.com/?ref=code-frontend">Cloudflare</a> to get a certificate, manage my DNS records, and caching - all things beneficial for good SEO. I recommend you do the same.</p><h1 id="conclusion">Conclusion</h1><p>As web developers, we need to know a lot more than just coding, and technical SEO is one of those things.</p><p>Fortunately, it&apos;s just a bunch of boring optimizations, but they boost your website performance significantly, resulting in better user experience and Google rankings. </p><p>Reference this 12-step checklist before launching a new website and you&apos;ll have taken care of the most common issues. Download the slides as a PDF here &#x1F447;</p>
        <div class="kg-card kg-file-card ">
            <a class="kg-file-card-container" href="https://codefrontend.com/content/files/2023/02/Technical-SEO-Checklist-For-Developers.pdf" title="Download" download>
                <div class="kg-file-card-contents">
                    <div class="kg-file-card-title">Technical SEO Checklist For Developers</div>
                    <div class="kg-file-card-caption">The 12 things to take care of before launching a website.</div>
                    <div class="kg-file-card-metadata">
                        <div class="kg-file-card-filename">Technical SEO Checklist For Developers.pdf</div>
                        <div class="kg-file-card-filesize">2 MB</div>
                    </div>
                </div>
                <div class="kg-file-card-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg>
                </div>
            </a>
        </div>
        <hr><p>Let me know your SEO tips that could be helpful for React developers in the comments.</p>]]></content:encoded></item><item><title><![CDATA[How to Get the Value From Input Field in React]]></title><description><![CDATA[The answer to the first question beginners have - how to get a value from an input element.]]></description><link>https://codefrontend.com/reactjs-get-input-value/</link><guid isPermaLink="false">63208dbe69001703f783dacb</guid><category><![CDATA[Quick Guide]]></category><category><![CDATA[ReactJS]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Wed, 08 Feb 2023 12:32:20 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2022/09/get-input-value-in-react.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://codefrontend.com/content/images/2022/09/get-input-value-in-react.jpeg" alt="How to Get the Value From Input Field in React"><p>Usually the first thing a real-world app needs is a way to get user&apos;s input. This is commonly done using the HTML <code>&lt;input&gt;</code> element. But a beginner React developer may struggle to get the value of an input field. Here&apos;s how to do it:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">import { ChangeEvent, useState } from &quot;react&quot;;

function Example() {
  const [inputText, setInputText] = useState(&quot;&quot;);

  const handleChange = (e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    // &#x1F447; Store the input value to local state
    setInputText(e.target.value);
  };

  return (
    &lt;div&gt;
      &lt;input type=&quot;text&quot; onChange={handleChange} value={inputText} /&gt;

      {/* &#x1F447; Use the input value from state */}
      &lt;p&gt;Your input: {inputText}&lt;/p&gt;
    &lt;/div&gt;
  );
};</code></pre><figcaption>Get the value of an input in React.</figcaption></figure><ol><li>Create a state variable where you will store the input value.</li><li>Create a change handler function and set the input value in state with <code>e.target.value</code></li><li>Assign the change handler to the <code>onChange</code> prop on the input.</li><li>Pass the input value as the <code>value</code> prop to the input, or its value won&apos;t update.</li></ol><h2 id="conclusion">Conclusion</h2><p>This method of event handlers updating the parent component&apos;s state and the child elements using getting it as props is an example of React&apos;s <a href="https://www.educative.io/answers/what-is-unidirectional-data-flow-in-react?ranMID=47764&amp;ranEAID=PPkX79%2Fc*b0&amp;ranSiteID=PPkX79_c.b0-rVLsh0xwWconqgwtHbiymw&amp;aff=Vprj&amp;ref=code-frontend">uni-directional flow</a>.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4CC;</div><div class="kg-callout-text">Note: the input value will be string even for inputs with <code>type=&quot;number&quot;</code> so you may need to convert them to their respective types with <code>Number(e.target.value)</code>.</div></div>]]></content:encoded></item><item><title><![CDATA[How to Use Resize Observer in React]]></title><description><![CDATA[Learn to keep track of DOM element dimensions in JavaScript and React.]]></description><link>https://codefrontend.com/resize-observer-react/</link><guid isPermaLink="false">63e36dacc8819d9ffebf1c8a</guid><category><![CDATA[Quick Guide]]></category><category><![CDATA[ReactJS]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Wed, 08 Feb 2023 12:07:37 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2023/02/resize-observer-react-cover.jpeg" medium="image"/><content:encoded><![CDATA[<h2 id="using-the-resize-observer-in-javascript">Using the Resize Observer in Javascript</h2><img src="https://codefrontend.com/content/images/2023/02/resize-observer-react-cover.jpeg" alt="How to Use Resize Observer in React"><p>Unlike <code>window</code>, DOM elements don&apos;t have a <code>resize</code> event that you can attach an event listener on. To listen to resize events on a DOM element you need to use the ResizeObserver API:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">const myElement = document.querySelector(&apos;.myElement&apos;);

// &#x1F447; Callback runs when any of the observed elements is resized
const resizeObserver = new ResizeObserver((entries) =&gt; {
  // You can iterate all of the element entries observed
  for (const entry of entries) {
    // Do something on resize, access the element on `entry.target`
  }
});

// &#x1F447; Listen to resize events on the element with `myElement` class
resizeObserver.observe(myElement);

// Unobeserve the element to stop tracking its size
resizeObserver.unobserve(myElement);

// Or stop observing all elements
resizeObserve.disconnect();</code></pre><figcaption>Creating a resize observer in JavaScript.</figcaption></figure><ol><li>Create the resize observer with <code>new ResizeObserver(...)</code>.</li><li>Pass in the callback function to run when the observer elements are resized.</li><li>Start tracking the element&apos;s size with the <code>.observe(element)</code> function.</li></ol><h2 id="using-the-resize-observer-in-react">Using the Resize Observer in React</h2><p>The most common way to use resize observer in react is by using <code>useEffect</code> to start tracking an element&apos;s size when the component is mounted. Here&apos;s how to do it:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">function MyComponent() {
  const elementRef = useRef&lt;HTMLDivElement&gt;(null)

  // Start observing the element when the component is mounted
  useEffect(() =&gt; {
    const element = elementRef?.current;

    if (!element) return;

    const observer = new ResizeObserver(() =&gt; {
      // &#x1F449; Do something when the element is resized
    });

    observer.observe(element);
    return () =&gt; {
      // Cleanup the observer by unobserving all elements
      observer.disconnect();
    };
  }, [])

  // &#x1F447; Assign the ref to the element you&apos;re observing
  return &lt;div ref={elementRef}&gt;...&lt;/div&gt;
}</code></pre><figcaption>Basic ResizeObserver usage in React components with TypeScript.</figcaption></figure><ol><li>Create a ref object using the <code>useRef</code> hook and assign it to the element you want to observe.</li><li>Call the <code>useEffect</code> hook in your React component with a callback function <code>[]</code> as the dependency array.</li><li>Inside, create the resize observer using <code>new ResizeObserver(...)</code> pass it the handler function.</li><li>Start tracking the element&apos;s size with a call to <code>ResizeObserver.observe(element)</code>.</li><li>Return the cleanup function from <code>useEffect</code> that calls <code>ResizeObserver.disconnect()</code> to stop tracking the element when the component is unmounted.</li></ol><h3 id="the-useresizeobserver-hook">The <code>useResizeObserver</code> hook</h3><p>If you need to use resize observers often, writing the above code can become cumbersome and repetitive. You can move it to a reusable hook that allows you to easily keep track of the element&apos;s dimensions:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">import { useLayoutEffect, useRef } from &apos;react&apos;;

function useResizeObserver&lt;T extends HTMLElement&gt;(
  callback: (target: T, entry: ResizeObserverEntry) =&gt; void
) {
  const ref = useRef&lt;T&gt;(null)

  useLayoutEffect(() =&gt; {
    const element = ref?.current;

    if (!element) {
      return;
    }

    const observer = new ResizeObserver((entries) =&gt; {
      callback(element, entries[0]);
    });

    observer.observe(element);
    return () =&gt; {
      observer.disconnect();
    };
  }, [callback, ref]);

  return ref
}

export default useResizeObserver;</code></pre><figcaption>A reusable React hook for the ResizeObserver.</figcaption></figure><p>You can then use it like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">import { useCallback } from &quot;react&quot;;

function Example() {
  const onResize = useCallback((target: HTMLDivElement) =&gt; {
    // Handle the resize event
  }, []);

  const ref = useResizeObserver(onResize);

  return &lt;div ref={ref}&gt;...&lt;/div&gt;;
}</code></pre><figcaption>Example usage of the <code>useResizeObserver</code> hook.</figcaption></figure><p>The <code>onResize</code> function must preserve its references between component re-renders, that&apos;s why we wrap it with the <code>useCallback</code> hook. Otherwise, the resize observer would be re-created on every re-render, which may lead to performance issues.</p><h2 id="conclusion">Conclusion</h2><p>Using the resize observer in React is the same as using it in JavaScript, and you can use the <code>useEffect</code> hook to hook into the React component lifecycle, for instance, to start observing when the component is mounted.</p><p>There may be a case when you want to start observing when some event happens, e.g. a button is pressed. The important point is that the resize observer must keep the same reference between component re-renders.</p><p>To do that, you can assign the <code>ResizeObserver</code> instance to a ref object created with a call to <code>useRef</code> and access it on the <code>.current</code> property.</p><p>Here are the complete code examples you can copy and paste &#x1F447;</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/vincaslt/twitter-code/tree/main/src/tooltips?ref=code-frontend"><div class="kg-bookmark-content"><div class="kg-bookmark-title">twitter-code/src/tooltips at main &#xB7; vincaslt/twitter-code</div><div class="kg-bookmark-description">Code examples from my Twitter @VincasStonys. Contribute to vincaslt/twitter-code development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="How to Use Resize Observer in React"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">vincaslt</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/a0098915ef4a1ccdf91dee15370a4c4ec6d03a0549b4a9fe9f406b17a9997539/vincaslt/twitter-code" alt="How to Use Resize Observer in React"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[How to Add an Event Handler in React]]></title><description><![CDATA[React and DOM events are different, here's how to use both event systems in React.]]></description><link>https://codefrontend.com/event-handler-in-react/</link><guid isPermaLink="false">631eeb6a69001703f783d8fe</guid><category><![CDATA[Quick Guide]]></category><category><![CDATA[ReactJS]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Tue, 07 Feb 2023 15:21:59 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2022/09/addeventlistener-article-cover.png" medium="image"/><content:encoded><![CDATA[<img src="https://codefrontend.com/content/images/2022/09/addeventlistener-article-cover.png" alt="How to Add an Event Handler in React"><p>React has its own event system that&apos;s similar to the DOM event system, but that works in parallel to it. That means that React event handlers are assigned differently than DOM event listeners.</p><h2 id="react-event-handlers">React Event Handlers</h2><p>You can handle events in React by passing an event handler function as a prop JSX element:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">import { MouseEvent } from &quot;react&quot;;

function MyComponent() {
  const handleButtonClick = (e: MouseEvent&lt;HTMLButtonElement&gt;) =&gt; {
    // Called when the button is clicked
  }
  
  return (
    &lt;button onClick={handleButtonClick}&gt;
      Click me!
    &lt;/button&gt;
  )
}</code></pre><figcaption>Adding an event handler to React elements in TypeScript.</figcaption></figure><p>The event handler props are all camel-cased and accept a callback function. Some commonly used ones are <code>onClick</code>, <code>onChange</code> and <code>onSubmit</code>. The callback function will receive <a href="https://beta.reactjs.org/reference/react-dom/components/common?ref=code-frontend#react-event-object">SyntheticEvent</a> as the parameter.</p><h2 id="dom-event-handlers-in-react">DOM Event Handlers in React</h2><p>It&apos;s sometimes necessary to use the DOM event system, for instance when integrating with 3rd party libraries or listening to <code>window</code> events.</p><p>You should use the <code>useEffect</code> hook to add event listeners in React. Here&apos;s how to listen to events on <code>window</code> in React:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">function MyComponent() {
  useEffect(() =&gt; {
    const handleWindowResize = (e: UIEvent) =&gt; {
      // Called when browser window is resized
    };

    window.addEventListener(&quot;resize&quot;, handleWindowResize);
    return () =&gt; {
      window.removeEventListener(&quot;resize&quot;, handleWindowResize);
    };
  }, []);

  return &lt;&gt;...&lt;/&gt;;
}</code></pre><figcaption>Adding an event handler to the <code>window</code>.</figcaption></figure><ol><li>Add a call to <code>useEffect</code> and provide <code>[]</code> as the dependency list to only register the event listener once when the component is mounted.</li><li>Create a handler function inside of the <code>useEffect</code> hook&apos;s callback function.</li><li>Bind the event handler to an event using <code>addEventListener</code> on the DOM element.</li><li>Return a cleanup function for the <code>useEffect</code> hook that removes the assigned event handler.</li></ol><h3 id="assigning-dom-events-to-react-elements">Assigning DOM events to React elements</h3><p>To assign the event listener to the React element you need to first get the reference to the underlying DOM element. This is commonly done using the <code>useRef</code> hook:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">function MyComponent() {
  const buttonRef = useRef&lt;HTMLButtonElement&gt;(null);

  useEffect(() =&gt; {
  	// &#x1F447; Get the DOM element from the React element
    const element = buttonRef.current;
    
    if (!element) return;

    const handleButtonClick = (e: MouseEvent) =&gt; {
      // Called when button is clicked
    };

    element.addEventListener(&quot;click&quot;, handleButtonClick);
    return () =&gt; {
      element.removeEventListener(&quot;click&quot;, handleButtonClick);
    };
  }, []);

  return &lt;button ref={buttonRef}&gt;Click me!&lt;/button&gt;;
}</code></pre><figcaption>Listening to DOM events on React elements using <code>useRef</code> to get their references.</figcaption></figure><ol><li>Call the <code>useRef</code> and assign its value to the <code>ref</code> prop on the React element.</li><li>In the <code>useEffect</code> hook, get the reference to the DOM element from <code>current</code> property on the ref object. Check that it&apos;s not null.</li><li>Add the DOM event listener like before using <code>addEventListener</code>.</li></ol><h2 id="conclusion">Conclusion</h2><p>Always prefer to use the React event system, because it fixes certain inconsistencies, and working with it is generally easier.</p><p>In the rare cases when you do need DOM event listeners, assign them in the <code>useEffect</code> hook. You should first have the reference to the DOM element which you can get using the <code>useRef</code> hook or simply by querying it directly, e.g. <code>window</code>, <code>document.body</code> or <code>document.querySelector(...)</code>.</p>]]></content:encoded></item><item><title><![CDATA[Using Local Storage in JavaScript and React]]></title><description><![CDATA[Learn the simplest way to persist values in the browser using the local storage API.]]></description><link>https://codefrontend.com/local-storage-javascript-react/</link><guid isPermaLink="false">63d7cb47c8819d9ffebf1b0e</guid><category><![CDATA[Quick Guide]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[ReactJS]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Mon, 30 Jan 2023 13:52:08 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2023/01/local-storage-article-cover-1.png" medium="image"/><content:encoded><![CDATA[<h2 id="local-storage-in-javascript">Local Storage in JavaScript</h2><img src="https://codefrontend.com/content/images/2023/01/local-storage-article-cover-1.png" alt="Using Local Storage in JavaScript and React"><p>In the browser, you can access local storage through the <code>window.localStorage</code> property. The <code>window</code> is assumed though, so you can omit it and simply write <code>localStorage</code>.</p><p>Local storage gives access to 4 important functions:</p><ul><li><code>setItem(key, value)</code> - assign a string value to a key in local storage.</li><li><code>getItem(key)</code> - get the value assigned to a key.</li><li><code>removeItem(key)</code> - removes the value by key.</li><li><code>clear()</code> - removes all values in local storage.</li></ul><p>Here&apos;s the usage example:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">const value = { name: &apos;Vincas&apos;, surname: &apos;Stonys&apos; };

// 1&#xFE0F;&#x20E3; Stores the value. Must convert value to string first
localStorage.setItem(&apos;userInfo&apos;, JSON.stringify(value));

// 2&#xFE0F;&#x20E3; Gets the stored value and convert it to original value type
JSON.parse(localStorage.getItem(&apos;userInfo&apos;));</code></pre><figcaption>Example usage of local storage in JavaScript.</figcaption></figure><h3 id="what-is-local-storage">What is local storage?</h3><p>Local storage is basically a simple key-value map in your browser. It has a simple synchronous API that&apos;s available in all modern browsers.</p><p>The local storage is separate for every origin (hostname and port): <code>https://vistontea.com</code> and <code>https://blog.vistontea.com</code> won&apos;t share local storage.</p><p>You can inspect values stored in local storage via developer tools. For instance, using <code>CTRL+SHIFT+C</code> keyboard shortcut in Chrome (or <code>CMD+SHIFT+C</code> on Mac), then opening the <em>Application</em> tab.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/01/image-9.png" class="kg-image" alt="Using Local Storage in JavaScript and React" loading="lazy" width="1600" height="900" srcset="https://codefrontend.com/content/images/size/w600/2023/01/image-9.png 600w, https://codefrontend.com/content/images/size/w1000/2023/01/image-9.png 1000w, https://codefrontend.com/content/images/2023/01/image-9.png 1600w" sizes="(min-width: 720px) 720px"><figcaption>Local storage in chrome devtools.</figcaption></figure><h2 id="local-storage-in-react">Local Storage in React</h2><p>In React, you can use local storage directly using plain JavaScript.</p><p>A common use case for local storage is to persist local component state. In that case, it&apos;s useful to write a custom hook that synchronizes component state to local storage.</p><p>Here&apos;s a custom <code>useStoredState</code> hook example &#x1F447;</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import { SetStateAction, useState } from &apos;react&apos;;

function useStoredState&lt;T&gt;(key: string, defaultValue?: T | (() =&gt; T)) {
  // &#x1F447; Load stored state into regular react component state
  const [state, setState] = useState&lt;T&gt;(() =&gt; {
    const storedState = localStorage.getItem(key);

    if (storedState) {
      // &#x1F6A9; Data is stored as string so need to parse
      return JSON.parse(storedState) as T;
    }

    // No stored state - load default value.
    // It could be a function initializer or plain value.
    return defaultValue instanceof Function ? defaultValue() : defaultValue;
  });

  // &#x1F447; Keeps the exact same interface as setState - value or setter function.
  const setValue = (value: SetStateAction&lt;T&gt;) =&gt; {
    const valueToStore = value instanceof Function ? value(state) : value;
    localStorage.setItem(key, JSON.stringify(valueToStore));
    setState(valueToStore);
  };

  // as const tells TypeScript you want tuple type, not array.
  return [state, setValue] as const;
}</code></pre><figcaption>Custom hook to sync local storage with the component state.</figcaption></figure><ol><li>The hook keeps the same return type as <code>useState</code> - a tuple with the value and value setter. Its parameters are the local storage key and the default value or the initializer function.</li><li>It initializes the local component state by getting its value by <code>key</code> from local storage and parsing it in the state initializer function.</li><li>A custom state setter function wraps the native <code>setState</code> with additional logic to store the value in local storage using <code>localStorage.setItem</code> function.</li></ol><p>Here&apos;s how you might use it:</p><pre><code class="language-tsx">import * as React from &apos;react&apos;;
import useStoredState from &apos;./useStoredState&apos;;

export default function App() {
  // &#x1F447; Will be stored in local storage, but used as regular state
  const [items, setItems] = useStoredState(&apos;items&apos;, [&apos;item1&apos;, &apos;item2&apos;]);

  const handleAddClick = () =&gt; {
    setItems((items) =&gt; [...items, `item${items.length + 1}`]);
  };

  return (
    &lt;div&gt;
      &lt;ul&gt;
        {items.map((item) =&gt; (
          &lt;li key={item}&gt;{item}&lt;/li&gt;
        ))}
      &lt;/ul&gt;
      &lt;button onClick={handleAddClick}&gt;Add&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre><p>You can see the full code example with demo here: <a href="https://stackblitz.com/edit/react-ts-x4nwoj?file=App.tsx&amp;ref=code-frontend">https://stackblitz.com/edit/react-ts-x4nwoj?file=App.tsx</a></p><p>This is how it looks like in action &#x1F447;</p><figure class="kg-card kg-video-card"><div class="kg-video-container"><video src="https://codefrontend.com/content/media/2023/01/FiKX0NvX0AAjgSD.mp4" poster="https://img.spacergif.org/v1/1280x720/0a/spacer.png" width="1280" height="720" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://codefrontend.com/content/images/2023/01/media-thumbnail-ember3032.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div></figure><h2 id="using-local-storage-in-typescript">Using Local Storage in TypeScript</h2><p>You will notice that the <code>localStorage</code> property doesn&apos;t have great typings. It&apos;s useful to create your own wrapper around local storage that handles automatic value serialization and parsing.</p><p>Here&apos;s an example &#x1F447;</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">const storage = {
  set: (key: string, value: any) =&gt; {
    localStorage.setItem(key, JSON.stringify(value));
  },
  get: &lt;T&gt;(key: string, defaultValue?: T): T =&gt; {
    const value = localStorage.getItem(key);
    return (value ? JSON.parse(value) : defaultValue) as T;
  },
  remove: (key: string) =&gt; {
    localStorage.removeItem(key);
  },
};

export default storage;</code></pre><figcaption>TypeScript wrapper for local storage with typings.</figcaption></figure><p>You could now use the <code>storage</code> utility instead of <code>localStorage</code> and provide the type for the value stored in local storage: </p><pre><code class="language-ts">// No need to parse the value, it&apos;s handled by the wrapper
const value = storage.get&lt;{ name: string, surname: string }&gt;(&apos;userInfo&apos;);</code></pre><h2 id="conclusion">Conclusion</h2><p>Local storage is the easiest way to persist value in the browser, often eliminating the need for the backend server in the case of frontend-only apps.</p><p>In JavaScript, it&apos;s as simple as using functions exposed by <code>localStorage</code> object. In React you may use the same API, however, it&apos;s helpful to be more declarative and create higher-level utilities. I&apos;ve given an example of a custom hook that synchronizes component state to local storage. </p>]]></content:encoded></item><item><title><![CDATA[Succeed as a Junior Developer]]></title><description><![CDATA[Being a junior developer is hard. Here are 10 guidelines I wish I knew at the beginning of my career.]]></description><link>https://codefrontend.com/succeed-as-a-junior-developer/</link><guid isPermaLink="false">632892274389f73e27914aab</guid><category><![CDATA[Career]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Fri, 13 Jan 2023 14:22:58 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2022/10/how-to-be-a-junior-developer.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://codefrontend.com/content/images/2022/10/how-to-be-a-junior-developer.jpeg" alt="Succeed as a Junior Developer"><p>Being a junior developer is hard. At the start of my career, I used to feel overwhelmed, like I didn&apos;t know what I was doing. And that it was only a matter of time until I got exposed as the imposter that I was.</p><p>While I was at school and university, I seemed to have it all figured out - as long as you go to classes and maybe study a little, you&apos;re good. But when you&apos;re on your first day at work - what the hell do you do?</p><p>There were all these people I&apos;d never met before (everyone was so serious), following some processes I didn&apos;t know (scrum-what?) And the codebase - larger and scarier than I&apos;ve ever seen. Not to mention written in programming languages and frameworks I&apos;ve never heard of (does anyone code in Groovy or use Ext.js anyway?)</p><p>I wish there was someone to reassure me or some sort of guidelines I could follow to not only survive but <em>succeed</em>. Well then...</p><p>After having been a professional software developer for over 7 years now, here&apos;s what I wish I knew at the beginning of my career as a junior developer.</p><h2 id="1-first-relax">1) First, Relax</h2><p>When you&apos;re at your first job as a junior developer it&apos;s easy to get the impression you&apos;re expected to deliver results (and quickly). At least that&apos;s how it was for me - people around me were stressed, constantly talking about some deadline they have.</p><p>Don&apos;t get caught up in it. You as a junior developer are there to <em>learn</em>.</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Dear Junior Developers, we didn&#x2019;t hire you for your output. <br><br>We hired you for your future potential.  Slow down and focus on up-skilling.</p>&#x2014; Nikki Siapno (@NikkiSiapno) <a href="https://twitter.com/NikkiSiapno/status/1571400877029605376?ref_src=twsrc%5Etfw&amp;ref=code-frontend">September 18, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><p>Nobody expects you to deliver. For the company - you&apos;re an investment. And to be a good investment - you need to <em>grow</em> (no... I meant your skills &#x1F926;&#x200D;&#x2642;&#xFE0F;).</p><p>Take your time with whatever you&apos;re doing, learn as much as you can, ask questions and try to enjoy the process.</p><h2 id="2-solve-problems-yourself">2) Solve Problems Yourself</h2><p>I remember when I discovered GitHub I wanted to use a 3rd party library for any problem I had. After all, why write the code yourself, if someone else already has?</p><p><em>Wrong</em>. Your early career is a time for investments. You should try to solve every problem yourself even if it isn&apos;t optimal.</p><p>Over time you&apos;ll build up a database of go-to solutions in your head. And for the more complex problems, you&apos;ll know what to look for in a 3rd party solution - because you&apos;re familiar with the problem and know what a good solution should look like.</p><p>Again, your job as a junior developer is to learn first, and you learn nothing if you never try solving problems yourself.</p><h2 id="3-ask-for-help-early">3) Ask For Help Early</h2><p>As a junior developer, you&apos;re bound to get stuck eventually (a lot, actually). This is where many junior developers get into trouble. They think they mustn&apos;t bother anyone with their stupid issues and instead pretend they&apos;re fine.</p><p>That&apos;s a huge red flag &#xA0;&#x1F6A9;.</p><p><em>Always ask for help early</em>. As a rule of thumb, if you&apos;re stuck, feel free to spend 15-30 minutes trying to figure it out yourself, but then <em>please</em> ask for help.</p><p>There&apos;s nothing worse than junior developers who&apos;re struggling but are pretending they&apos;re fine.</p><h2 id="4-ask-for-help-correctly">4) Ask For Help Correctly</h2><p>As a junior developer, I was scared to ask for help because I thought the senior devs will be annoyed with my questions. And frankly, they probably were. But there is a way to make the experience pleasant for both you and the senior devs.</p><p>Here is what worked well for me &#x1F447;</p><h3 id="batch-your-questions">Batch your questions.</h3><p>Don&apos;t bother the senior dev every time you run into a problem. Write your question down and work on something else for a while. Once you have 3 or more questions (or some reasonable time has passed), go and ask them for help.</p><h3 id="do-your-part">Do your part</h3><p>Try to solve the problem first, then look for a solution online (e.g. documentation) and when you eventually ask for help - explain what you&apos;ve tried. It&apos;s very frustrating when junior devs run into a problem, try nothing to solve it, then expect you to solve the problem for them.</p><h3 id="show-willingness-to-learn">Show willingness to learn</h3><p>Never ask for the answer. Show them that you want to solve the problem yourself, and ask for a roadmap instead. You may ask for steps to find the solution, but if you&apos;re just asking for their solution, you&apos;re learning nothing (see tip #2).</p><h2 id="5-practice-humility">5) Practice Humility</h2><p>Senior developers know better (even if they don&apos;t). Don&apos;t be a rebel. Understand that you most likely know less than someone who&apos;s been doing this thing for years, and follow their instructions exactly.</p><p>Yes, there are stupid senior engineers. And yes, there are people who&apos;re just mean. Accept their criticism, learn what you can from them, and ignore the rest. Don&apos;t get discouraged by criticism.</p><p>These realizations have helped me tremendously in dealing with imposter syndrome and negative feedback:</p><ul><li>Your code is not you.</li><li>Nobody <em>really</em> knows what they&apos;re doing.</li></ul><p>Your time to shine will come - there will be opportunities for you to take on responsibility and show your creativity. When that time comes - overdeliver.</p><h2 id="6-take-ownership-and-responsibility">6) Take Ownership and Responsibility</h2><p>This is the best way to shake off the &quot;junior&quot; label. From the start, I was happy to help other junior developers, which in the eyes of others has made me the most &quot;senior&quot; of the lot (even though we started at the same time with no prior experience).</p><p>Over time, I&apos;ve learned some cool new tech by myself, which got popular (TypeScript), so then even senior devs went to me for help (huge confidence boost). In 2 of the companies I&apos;ve worked at I was known as &quot;The TypeScript Guy&quot;. Creating a niche for yourself is an excellent way to level up your career.</p><p>Voice your opinions, share your learnings, and when the opportunity for you to step up and show initiative comes - take it.</p><p>Here&apos;s a tip from me - <em>write end-to-end tests</em>. Nobody likes setting them up, nobody likes writing them, and nobody likes to be responsible for them. That&apos;s the perfect opportunity for a junior dev to show initiative &#x1F609;.</p><h2 id="7-build-side-projects">7) Build Side Projects</h2><p>Most good developers I know build stuff in their own time. And I know many bad ones that don&apos;t. Building side projects is the best way to fast-track your career.</p><p>Let me put it this way - when you go to an interview, the interviewer is looking for proof that you&apos;re good, that you can get shit done. When you can point him to a real-world app you&apos;ve built and deployed by yourself - you have undeniable proof that you can build and release apps.</p><p>I improved the most as a developer when I was building my side project <a href="https://mediabits.io/?ref=code-frontend">mediabits.io</a>. Also, it&apos;s been invaluable for my career - it has quite literally doubled my salary.</p><p>Instead of being a mere coding monkey, you become someone who builds products.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/01/image.png" class="kg-image" alt="Succeed as a Junior Developer" loading="lazy" width="1400" height="837" srcset="https://codefrontend.com/content/images/size/w600/2023/01/image.png 600w, https://codefrontend.com/content/images/size/w1000/2023/01/image.png 1000w, https://codefrontend.com/content/images/2023/01/image.png 1400w" sizes="(min-width: 720px) 720px"><figcaption>If you only learn from videos and books, never actually building anything by yourself, you risk getting into <em>tutorial hell</em>.</figcaption></figure><p>Ideally, the stuff you build should be something you need yourself and something you&apos;ve built on your own (don&apos;t just follow a tutorial). Here are some ideas &#x1F447;</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">The best way to learn ReactJS is by building real-world applications.<br><br>Not sure what to build?<br><br>Here are 5 ideas for real-world frontend applications using free public APIs &#x1F447; <a href="https://t.co/X1yTH4ADjO?ref=code-frontend">pic.twitter.com/X1yTH4ADjO</a></p>&#x2014; Vincas Stonys (@VincasStonys) <a href="https://twitter.com/VincasStonys/status/1558158514618507268?ref_src=twsrc%5Etfw&amp;ref=code-frontend">August 12, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><h2 id="8-focus-on-fundamentals">8) Focus on Fundamentals</h2><p>Once you start building side-projects it&apos;s easy to get caught up trying to learn all the new technologies. This is overwhelming.</p><p>Make it your goal to deliver a working app, instead of learning the latest-greatest shiny new framework. Just pick a solid technology that&apos;s been around for a while and use that. New technologies come and go, but fundamentals stay.</p><p>Guess what, most new tech is just a flavor of the same old thing with some sprinkles on top. It&apos;s a lot better to spend time mastering JavaScript instead of learning about the nitty-gritty details of react-router.</p><p>Don&apos;t make it harder than it needs to be. You&apos;re inevitably going to learn <em>a lot </em>anyway.</p><h2 id="9-learn-in-public">9) Learn in Public</h2><p>Not writing a blog or creating YouTube videos is the biggest regret I have from my early career. Even as I&apos;m writing this blog, I&apos;ve already forgotten a lot of the struggles I&apos;ve had as a beginner.</p><p>Two things are well-known:</p><ul><li>We learn best when we teach what we learn.</li><li>We learn best not from experts, but from someone who&apos;s only one step ahead.</li></ul><p>Somebody else will find your content invaluable, and as a bonus, you&apos;ll be building an audience that you can monetize later to earn some side income.</p><p>At the moment, <a href="https://hashnode.com/?ref=code-frontend">hashnode.dev</a> and <a href="https://dev.to/?ref=code-frontend">dev.to</a> are the easiest way to start blogging for developers.</p><h2 id="10-don%E2%80%99t-stay-in-your-first-job-for-too-long">10) Don&#x2019;t Stay In Your First Job for Too Long</h2><p>This one is a bit controversial and you&apos;re free to ignore it, but it&apos;s something I regret doing early in my career.</p><p>I think it&apos;s best to change your first job after 1-2 years in the company (unless you landed a job at Google in which case, ignore what I tell you). Here&apos;s why:</p><ul><li>Your opportunities for learning will be mostly exhausted.</li><li>You&apos;ll forever be junior in the eyes of the devs you first worked with.</li><li>You were underpaid when you started, and you will stay that way until you get a new job.</li></ul><p>Money should not be the reason to leave your first job, but just keep in mind that salary grows relative to your current salary (usually around 5-10%). A market-level salary for a developer with 2-3 years of exp is at least 2x higher than an entry-level salary (quick math &#x1F914; ~50% increase for every year).</p><p>If you feel like you&apos;re becoming stagnant, don&apos;t let the fear or &quot;loyalty&quot; stop you from getting another job. Your company is not your family, as much as they wish it were true.</p><h2 id="conclusion">Conclusion</h2><p>Early career as a junior developer is a scary period, but it gets very rewarding later on. I&apos;m glad I had a few friends doing the same thing and struggling with the same feelings. I can&apos;t imagine how lonely it would&apos;ve felt otherwise.</p><p>If you don&apos;t, I want you to at least have the guidelines I wish I had when I was just starting out. It all comes down to:</p><ul><li>Learn as much as you can;</li><li>Share what you learn;</li><li>Build a solid foundation both in terms of skills and career.</li></ul><p>Introduce yourself in the comments and let me know what you&apos;re struggling with. Feel free to connect with me on <a href="https://twitter.com/VincasStonys?ref=code-frontend">Twitter</a>, where I share regular tips for junior developers building their first web app with React.</p><p>Read my guide to CSS next &#x1F447;</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://codefrontend.com/css-guide/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Practical CSS Guide for Busy Developers</div><div class="kg-bookmark-description">Why waste time memorizing stuff you will never use? Here&#x2019;s the only CSS guide you will ever need to start styling web pages.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://codefrontend.com/content/images/size/w256h256/2022/07/code-frontend-logo-3.png" alt="Succeed as a Junior Developer"><span class="kg-bookmark-author">Code Frontend</span><span class="kg-bookmark-publisher">Vincas Stonys</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://codefrontend.com/content/images/2022/10/css-guide-cover.jpg" alt="Succeed as a Junior Developer"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Set up a React App With Typescript in 5 Minutes]]></title><description><![CDATA[Learn to use Vite to set up a modern front-end application with React and recommended tools.]]></description><link>https://codefrontend.com/set-up-react-app/</link><guid isPermaLink="false">6395f071c8819d9ffebf12e2</guid><category><![CDATA[ReactJS]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Tue, 10 Jan 2023 19:02:12 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2023/01/react-app-setup-article-cover-image.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://codefrontend.com/content/images/2023/01/react-app-setup-article-cover-image.jpeg" alt="Set up a React App With Typescript in 5 Minutes"><p>There are many ways to set up a new React app:</p><ul><li><a href="https://create-react-app.dev/?ref=code-frontend">create-react-app</a> has been the standard way to quickly start up a new React project, but it&apos;s starting to show its age;</li><li>nearly all React <a href="https://nextjs.org/docs?ref=code-frontend#automatic-setup">frameworks</a> have a starter template, but you might not need or want to use a framework;</li><li><a href="https://github.com/kriasoft/react-starter-kit?ref=code-frontend">starter-kits</a> are a popular option, but they&apos;re opinionated and add a lot of boilerplate and features, which is not ideal if you want to start minimally.</li></ul><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4CC;</div><div class="kg-callout-text">The simplest and fastest way to start a new React application in 2023 is by using <a href="https://vitejs.dev/?ref=code-frontend">Vite</a>.</div></div><p>Vite is a bundle of tools that help you develop your frontend apps, which includes a development server with hot module replacement, and a build toolchain with plugin support. </p><p>Vite uses native ES modules so it&apos;s extremely fast.</p><h2 id="using-vite-to-set-up-a-react-project">Using Vite to set up a React project</h2><p>Starting a new project with Vite is as simple as running <code>npm create vite@latest</code> with your project name and preferred starter template.</p><p>Here&apos;s how to start a new React project with TypeScript named <code>my-first-app</code>:</p><pre><code>npm create vite@latest my-first-app -- --template react-ts</code></pre><p>This command will create a new folder with your React app, so navigate into it and install dependencies:</p><pre><code>cd my-first-app
npm install</code></pre><p>However, there are a couple of extra steps you should take to make your environment ready for development.</p><h3 id="1-set-up-eslint">1) Set up ESLint</h3><p><a href="https://eslint.org/?ref=code-frontend">ESLint</a> will help you catch small errors such as undeclared variables. It also lets you enforce good practices, such as providing a complete dependency list for certain React hooks like <code>useEffect</code> or <code>useMemo</code>.</p><p>To install ESLint in your Vite project run:</p><pre><code>npm install -D eslint eslint-config-react-app</code></pre><p>This command also installs the <a href="https://www.npmjs.com/package/eslint-config-react-app?ref=code-frontend">eslint-config-react-app</a> which is the same exact configuration create-react-app uses. Extend it by creating a <code>.eslintrc</code> file in the root of the project folder with this code:</p><pre><code class="language-json">{
  &quot;extends&quot;: &quot;react-app&quot;
}</code></pre><p>Finally, make sure ESLint is picked up by your IDE, if you use VSCode, you will need to install an <a href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint&amp;ref=code-frontend">extension</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/01/image-1.png" class="kg-image" alt="Set up a React App With Typescript in 5 Minutes" loading="lazy" width="335" height="146"><figcaption>ESLint extension for VSCode.</figcaption></figure><h3 id="2-set-up-prettier">2) Set up Prettier</h3><p>Prettier automatically formats your code so you don&apos;t need to waste your time debating spaces over tabs or aligning indentations. It&apos;s especially critical in making code formatting consistent when you work with others.</p><p>First, install the VSCode extension (or whatever your IDE is).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/01/image-2.png" class="kg-image" alt="Set up a React App With Typescript in 5 Minutes" loading="lazy" width="339" height="141"><figcaption>Prettier extension for VSCode.</figcaption></figure><p>It will use the default configuration which is good for most people, but if you want to override with your own rules, create a <code>.prettierrc</code> file and provide your rules there.</p><p>Then, configure VSCode to auto-format on save and use Prettier as the default formatter:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/01/image-3.png" class="kg-image" alt="Set up a React App With Typescript in 5 Minutes" loading="lazy" width="1758" height="184" srcset="https://codefrontend.com/content/images/size/w600/2023/01/image-3.png 600w, https://codefrontend.com/content/images/size/w1000/2023/01/image-3.png 1000w, https://codefrontend.com/content/images/size/w1600/2023/01/image-3.png 1600w, https://codefrontend.com/content/images/2023/01/image-3.png 1758w" sizes="(min-width: 720px) 720px"><figcaption>Toggle format on save.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/01/image-4.png" class="kg-image" alt="Set up a React App With Typescript in 5 Minutes" loading="lazy" width="1856" height="228" srcset="https://codefrontend.com/content/images/size/w600/2023/01/image-4.png 600w, https://codefrontend.com/content/images/size/w1000/2023/01/image-4.png 1000w, https://codefrontend.com/content/images/size/w1600/2023/01/image-4.png 1600w, https://codefrontend.com/content/images/2023/01/image-4.png 1856w" sizes="(min-width: 720px) 720px"><figcaption>Set Prettier as the default formatter.</figcaption></figure><h3 id="3-remove-unnecessary-files">3) Remove unnecessary files</h3><p>The Vite template comes with a simple demo app, but you might want to start fresh:</p><ul><li>Remove the assets folder and <code>App.css</code></li><li>Remove everything inside <code>index.css</code></li><li>Remove everything inside <code>App.tsx</code></li></ul><p>Here&apos;s what you will have in the end:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2023/01/image-5.png" class="kg-image" alt="Set up a React App With Typescript in 5 Minutes" loading="lazy" width="736" height="782" srcset="https://codefrontend.com/content/images/size/w600/2023/01/image-5.png 600w, https://codefrontend.com/content/images/2023/01/image-5.png 736w" sizes="(min-width: 720px) 720px"><figcaption>The final project file structure.</figcaption></figure><h2 id="conclusion">Conclusion</h2><p>Vite is the easiest way to start your React frontend application at the moment.</p><p>Its default configuration is great for most cases, but its capabilities can be extended with plugins, for instance, if you&apos;re working with SVG images, you may want to import them as React components - <a href="https://www.npmjs.com/package/vite-plugin-svgr?ref=code-frontend">vite-plugin-svgr</a> can help you with that.</p><p>Vite, unlike create-react-app, doesn&apos;t come with ESLint configuration, so you will have to manually set it up, along with Prettier. Luckily, it&apos;s very simple, and I&apos;ve provided a guide for that in this post.</p>]]></content:encoded></item><item><title><![CDATA[Implement Scroll-Snapping Using Only CSS]]></title><description><![CDATA[Learn to implement scroll-snapping without any JavaScript using only CSS.]]></description><link>https://codefrontend.com/scroll-snap-using-css/</link><guid isPermaLink="false">635bb09672135e010b2af405</guid><category><![CDATA[CSS]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Sun, 11 Dec 2022 16:21:09 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2022/12/css-scroll-snapping-article-cover.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://codefrontend.com/content/images/2022/12/css-scroll-snapping-article-cover.jpeg" alt="Implement Scroll-Snapping Using Only CSS"><p>Scroll snapping can be achieved using CSS:</p><ol><li>Enable scrolling on the parent element with <code>overflow: auto</code>.</li><li>Set the <code>scroll-snap-type</code> property on the parent element.</li><li>Set the <code>scroll-snap-align</code> property on the child element to snap to it.</li></ol><h2 id="scroll-snapping-example">Scroll Snapping Example</h2><p>Here&apos;s an example of how to enable scroll-snapping on the x-axis using CSS:</p><pre><code class="language-css">.parent {
  /* 1&#xFE0F;&#x20E3; enable scrolling */
  overflow: auto;
    
  /* 2&#xFE0F;&#x20E3; enable scroll-snapping on horizontal axis */
  scroll-snap-type: x mandatory; 
}

.child {
  /* 3&#xFE0F;&#x20E3; snap to the start of the element*/
  scroll-snap-align: start;
}</code></pre><p>Here&apos;s what it looks like:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2022/12/Scroll-snapping-in-CSS.gif" class="kg-image" alt="Implement Scroll-Snapping Using Only CSS" loading="lazy" width="720" height="720" srcset="https://codefrontend.com/content/images/size/w600/2022/12/Scroll-snapping-in-CSS.gif 600w, https://codefrontend.com/content/images/2022/12/Scroll-snapping-in-CSS.gif 720w" sizes="(min-width: 720px) 720px"><figcaption>CSS-only scroll snap example.</figcaption></figure><h2 id="scroll-snap-type">Scroll Snap Type</h2><p>The <code>scroll-snap-type</code> property accepts two values - axis and strictness. The axis can be:</p><ol><li><code>x</code> - horizontal;</li><li><code>y</code> - vertical;</li><li><code>both</code> - both, but it may snap to different elements.</li></ol><p>The strictness (second value) can be:</p><ol><li><code>mandatory</code> - always snaps to the element or resets the scroll attempt if not close enough to the alignment point.</li><li><code>proximity</code> - snaps only when close to the alignment point, in other cases the scroll position is left unchanged.</li></ol><h2 id="scroll-snap-align">Scroll Snap Align</h2><p>The <code>scroll-snap-align</code> property accepts a single value - alignment point:</p><ol><li><code>center</code> - the center of the element;</li><li><code>start</code> - the start of the element based on the scroll-snap axis;</li><li><code>end</code> - the end of the element.</li></ol><h2 id="conclusion">Conclusion</h2><p>Basic scroll snapping could be implemented without any JavaScript using the <code>scroll-snap-type</code> and <code>scroll-snap-align</code> CSS properties.</p><p>If you&apos;d like to see the complete example, you can find it on my GitHub:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/vincaslt/twitter-code/tree/main/src/scroll-snap?ref=code-frontend"><div class="kg-bookmark-content"><div class="kg-bookmark-title">twitter-code/src/scroll-snap at main &#xB7; vincaslt/twitter-code</div><div class="kg-bookmark-description">Code examples from my Twitter @VincasStonys. Contribute to vincaslt/twitter-code development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Implement Scroll-Snapping Using Only CSS"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">vincaslt</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/f4d466b75a512f7a8afba5c9f18a20efcca735919d02c606881fbb7d16b95d80/vincaslt/twitter-code" alt="Implement Scroll-Snapping Using Only CSS"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Easiest Way to Truncate Text With Ellipsis in CSS]]></title><description><![CDATA[Learn to truncate text and add ellipsis at the end using CSS. For both, single-line and multi-line text.]]></description><link>https://codefrontend.com/css-ellipsis/</link><guid isPermaLink="false">635bb12272135e010b2af40f</guid><category><![CDATA[CSS]]></category><category><![CDATA[Quick Guide]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Sat, 29 Oct 2022 09:06:49 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2022/10/css-ellipsis-post-cover.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://codefrontend.com/content/images/2022/10/css-ellipsis-post-cover.jpeg" alt="Easiest Way to Truncate Text With Ellipsis in CSS"><p>So your designer has asked you to add the three dots at the end when the text doesn&apos;t fit, and you have no clue how to do that?</p><p>Don&apos;t worry, it&apos;s super simple to do with CSS.</p><p>By the way, those three dots at the end are called <em>ellipsis</em>.</p><hr><p><em>Real quick - would you like to master CSS and learn Web Development? If so, be sure to check out the platform I recommend to learn coding online:</em></p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://codefrontend.com/learn-to-code-online/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Learn Coding Online</div><div class="kg-bookmark-description">Here&#x2019;s the platform I recommend if you want to learn Web Development online.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://codefrontend.com/content/images/size/w256h256/2022/07/code-frontend-logo-3.png" alt="Easiest Way to Truncate Text With Ellipsis in CSS"><span class="kg-bookmark-author">Code Frontend</span><span class="kg-bookmark-publisher">Vincas Stonys</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://codefrontend.com/content/images/2023/03/educative-page-cover.jpeg" alt="Easiest Way to Truncate Text With Ellipsis in CSS"></div></a></figure><h2 id="single-line-ellipsis">Single-line ellipsis</h2><p>Use <code>text-overflow: ellipsis;</code> to automatically truncate the text when it overflows the container and add the three dots at the end.</p><p>The element needs to get resized and the text has to stay in one line for the ellipsis to show up, so here are all 3 CSS lines you need:</p><figure class="kg-card kg-code-card"><pre><code class="language-css">.ellipsis {
  text-overflow: ellipsis; /* enables ellipsis */
  white-space: nowrap; /* keeps the text in a single line */
  overflow: hidden; /* keeps the element from overflowing its parent */
}</code></pre><figcaption>Single-line ellipsis using CSS.</figcaption></figure><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4E3;</div><div class="kg-callout-text">The parent container often allows its contents to overflow, making the ellipsis not show up. Add <code>overflow: hidden;</code> to the parent container to fix that.</div></div><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2022/10/single-line-ellipsis-result.gif" class="kg-image" alt="Easiest Way to Truncate Text With Ellipsis in CSS" loading="lazy" width="720" height="720" srcset="https://codefrontend.com/content/images/size/w600/2022/10/single-line-ellipsis-result.gif 600w, https://codefrontend.com/content/images/2022/10/single-line-ellipsis-result.gif 720w" sizes="(min-width: 720px) 720px"><figcaption>Example of single-line ellipsis in practice.</figcaption></figure><h2 id="multi-line-ellipsis">Multi-line ellipsis</h2><p>You will soon find, that <code>text-overflow: ellipsis;</code> doesn&apos;t work when the text wraps to an extra line.</p><p>To truncate multi-line text and add an ellipsis at the end of the last line use the experimental <code>-webkit-box</code> box model (don&apos;t worry it&apos;s supported in all major browsers):</p><figure class="kg-card kg-code-card"><pre><code class="language-css">.multiline-ellipsis {
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3; /* start showing ellipsis when 3rd line is reached */
  white-space: pre-wrap; /* let the text wrap preserving spaces */
}</code></pre><figcaption>Multi-line ellipsis using CSS.</figcaption></figure><p>Control how many lines to show with <code>-webkit-line-clamp</code>. It is important to let the text wrap by setting the <code>white-space</code> property to <code>pre-wrap</code> or <code>normal</code>.</p><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4E3;</div><div class="kg-callout-text">Same as before, the parent container needs to clip its children. Add <code>overflow: hidden;</code> to the parent element.</div></div><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codefrontend.com/content/images/2022/10/multiline-ellipsis-result.gif" class="kg-image" alt="Easiest Way to Truncate Text With Ellipsis in CSS" loading="lazy" width="720" height="720" srcset="https://codefrontend.com/content/images/size/w600/2022/10/multiline-ellipsis-result.gif 600w, https://codefrontend.com/content/images/2022/10/multiline-ellipsis-result.gif 720w" sizes="(min-width: 720px) 720px"><figcaption>Example of multi-line ellipsis in practice.</figcaption></figure><h2 id="conclusion">Conclusion</h2><p>It&apos;s trivial to truncate the overflowing text and add an ellipsis at the end of a single line. Adding an ellipsis for multiline text is a bit more tricky, so this post should help you.</p><p>You can find the code examples for this article on my GitHub: <a href="https://github.com/vincaslt/twitter-code/tree/main/src/ellipsis?ref=code-frontend">https://github.com/vincaslt/twitter-code/tree/main/src/ellipsis</a> </p><p>Since you&apos;re learning CSS tricks, why not go through my practical CSS guide? It will take only 5 minutes, but it will teach you all of the CSS you need in practice:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://codefrontend.com/css-guide/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Practical CSS Guide for Busy Developers</div><div class="kg-bookmark-description">Why waste time memorizing stuff you will never use? Here&#x2019;s the only CSS guide you will ever need to start styling web pages.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://codefrontend.com/content/images/size/w256h256/2022/07/code-frontend-logo-3.png" alt="Easiest Way to Truncate Text With Ellipsis in CSS"><span class="kg-bookmark-author">Code Frontend</span><span class="kg-bookmark-publisher">Vincas Stonys</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://codefrontend.com/content/images/2022/10/css-guide-cover.jpg" alt="Easiest Way to Truncate Text With Ellipsis in CSS"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Practical CSS Guide for Busy Developers]]></title><description><![CDATA[Why waste time memorizing stuff you will never use? Here's the only CSS tutorial you will ever need to start styling web pages.]]></description><link>https://codefrontend.com/css-guide/</link><guid isPermaLink="false">635bb04772135e010b2af3f7</guid><category><![CDATA[CSS]]></category><category><![CDATA[Deep Dive]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Fri, 28 Oct 2022 20:03:02 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2022/10/css-guide-cover.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://codefrontend.com/content/images/2022/10/css-guide-cover.jpg" alt="Practical CSS Guide for Busy Developers"><p>If you&apos;re like me and prefer to learn by starting to build first and googling when you get stuck, this CSS guide is for you.</p><p>It will give you a crash course in only the CSS you absolutely need in practice, wasting no time on stuff you can google once you need it.</p><p>Give me 5 minutes of your time and you will be on your way to building your app.</p><h2 id="what-is-css">What is CSS?</h2><p>CSS stands for Cascading Style Sheets - it&apos;s a language used to add styles to web pages by describing how elements should be displayed.</p><p>Elements are styled using CSS by selecting them with selectors and setting values for their properties:</p><figure class="kg-card kg-code-card"><pre><code class="language-css">p {
  background-color: green;
}</code></pre><figcaption>CSS code example</figcaption></figure><p>In the above example, all <code>&lt;p&gt;</code> elements will be selected and given a green background color.</p><h3 id="css-selectors">CSS Selectors</h3><p>There are three primary types of selectors:</p><ul><li><strong>element </strong>- any HTML tag <code>p</code>, <code>div</code>, <code>h1</code>, etc.</li><li><strong>class </strong>- string in <code>class</code> attribute prefixed with a dot, e.g. <code>&lt;div class=&quot;section&quot;&gt;</code> can be selected with <code>.section</code></li><li><strong>id </strong>- string in <code>id</code> attribute prefixed with a hashtag, e.g. <code>&lt;div id=&quot;nav-bar&quot;&gt;</code> can be selected with <code>#nav-bar</code></li></ul><p>I recommend primarily using classes to style your elements because elements can have more than one class, making it easy to extend or override styles.</p><p>There are three ways to combine selectors:</p><ul><li><strong>comma</strong> - to select all elements matching <em>any</em> of the comma-separated selectors and apply the same style.</li><li><strong>space</strong> - to select a nested element, by describing its path. For instance <code>.section h1</code> will select a <code>h1</code> element that&apos;s a child of an element with class <code>section</code>.</li><li><strong>nothing</strong> - appending more class selectors to an existing selector will increase its specificity, e.g. <code>h1.title</code> will select <code>&lt;h1&gt;</code> elements with <code>class=&quot;title&quot;</code>.</li></ul><h3 id="cascading">Cascading</h3><p>You can combine and override styles by using selectors with higher <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity?ref=code-frontend">specificity</a>. A simple way to make the selector more specific is to give it more classes.</p><figure class="kg-card kg-code-card"><pre><code class="language-css">.text {
  color: #cccccc;
  font-size: 14px;
}

p.text.small-text {
  font-size: 12px;
}</code></pre><figcaption>Overriding element style with more specific selector</figcaption></figure><p>In this example, all elements with <code>class=&quot;text&quot;</code> will have a gray color and 14px text size. The <code>&lt;p&gt;</code> elements with &#xA0;<code>class=&quot;text small-text&quot;</code> will have the same gray color, but their text size will be 12px instead because of the more specific selector that overrides the <code>font-size</code> property.</p><p>In practice, I recommend keeping selectors as simple as possible, such as using only a single class and not cascading the styles too much.</p><h3 id="adding-css-styles">Adding CSS styles</h3><p>There are two ways to add styles to your elements:</p><ul><li><strong>style attribute </strong>- HTML elements can have a <code>style</code> attribute, where you can directly write CSS expressions that will be applied to the element, for example: <code>&lt;div style=&quot;color: white; background-color: black;&quot;&gt;</code></li><li><strong>stylesheets </strong>- declared either between the <code>&lt;style&gt;</code> tags as a child of the <code>&lt;head&gt;</code> element in your HTML code, or as a separate <code>.css</code> file.</li></ul><h3 id="stylesheets">Stylesheets</h3><p>If you create a <code>.css</code> file containing your CSS code, you can import it in HTML using the <code>&lt;link&gt;</code> tag with <code>rel=&quot;stylesheet&quot;</code> attribute:</p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;head&gt;
  &lt;link rel=&quot;stylesheet&quot; href=&quot;styles.css&quot;&gt;
&lt;/head&gt;</code></pre><figcaption>Importing the CSS file in HTML</figcaption></figure><p>If you&apos;re using something like React with a bundler like Webpack, Vite or similar, you can import the CSS file using the ES6 syntax in your JavaScript files:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">import &apos;./styles.css&apos;</code></pre><figcaption>Import CSS using ES6 import syntax</figcaption></figure><h2 id="the-only-css-rules-to-start-with">The only CSS rules to start with</h2><p>CSS has over 500 different properties. It would be stupid to try and memorize them all. There are only around 20 rules that I most often use in practice.</p><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4CC;</div><div class="kg-callout-text">These rules will get you started building without overwhelming you. Then, once you&apos;ve built something, start learning more advanced CSS properties and concepts like <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries?ref=code-frontend">media queries</a> and <a href="https://css-tricks.com/snippets/css/complete-guide-grid/?ref=code-frontend">CSS grid</a> to make your websites responsive and amazing-looking.</div></div><h3 id="color">Color</h3><p>The <code>color</code> CSS property changes text color. You can prefix it with <code>border-</code> or <code>background-</code> to style the respective parts of the element:</p><figure class="kg-card kg-code-card"><pre><code class="language-css">p {
  color: white;
  background-color: black;
  border-color: green;
  
  /* Additional styles needed for border */
  border-style: solid;
  border-width: 3px;
}</code></pre><figcaption>Changing color using CSS</figcaption></figure><p>Here&apos;s what it looks like: </p><figure class="kg-card kg-image-card"><img src="https://codefrontend.com/content/images/2022/10/image.png" class="kg-image" alt="Practical CSS Guide for Busy Developers" loading="lazy" width="161" height="59"></figure><h3 id="width-and-height">Width and Height</h3><p>You can specify the element size by changing the <code>width</code> and <code>height</code> CSS properties. It&apos;s common to use absolute pixel values or percentages, but <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units?ref=code-frontend">other units</a> are also available:</p><figure class="kg-card kg-code-card"><pre><code class="language-css">.full {
  width: 100%; /* Fills the space horizontally */
}

.fixed-height {
  height: 200px; /* Fixed 200px vertical size */
}</code></pre><figcaption>Changing size using CSS</figcaption></figure><h3 id="padding-and-margin">Padding and Margin</h3><p>Blank space inside of the element is called padding and outside of the element is the margin. Here&apos;s how to add spacing to HTML elements:</p><figure class="kg-card kg-code-card"><pre><code class="language-css">.section {
  padding: 20px; /* Empty space inside of the element on all sides */
  margin: 10px /* Empty space outside of the element on all sides */;
}</code></pre><figcaption>Spacing in CSS</figcaption></figure><p>You can control where to add white space by appending <code>-top</code>, <code>-left</code>, <code>-right</code> or <code>-bottom</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-css">.box {
  padding-left: 10%;
  margin-bottom: 20px;
}</code></pre><figcaption>Specifying where to add spacing</figcaption></figure><h3 id="border">Border</h3><p>Border requires three properties <code>width</code>, <code>style</code> and <code>color</code>, but you can use the shorthand which I recommend:</p><figure class="kg-card kg-code-card"><pre><code class="language-css">.box {
  border: 1px solid black; /* width style color */
}</code></pre><figcaption>Adding borders in CSS</figcaption></figure><p>Like with margin and padding, you can append <code>-top</code>, <code>-left</code>, <code>-right</code> or <code>-bottom</code> to specify where to add the border.</p><h3 id="display">Display</h3><p>If you want to remove the element from the screen, a common way to do it is by setting <code>display: none;</code> </p><p>You will also need to set <code>display: flex;</code> when you want to use the flexbox model.</p><p>Other common options for display include <code>inline</code> (default for elements like <code>&lt;span&gt;</code>, <code>&lt;b&gt;</code>, <code>&lt;i&gt;</code>) and <code>block</code> (default for &#xA0;<code>&lt;div&gt;</code> , <code>&lt;p&gt;</code> and most others).</p><h3 id="flexbox">Flexbox</h3><p>The element enters the flexbox mode when you set <code>display: flex;</code></p><p>The most commonly used flexbox properties are:</p><ul><li><code>flex</code> - to control what the element should do with available free space.</li><li><code>flex-direction</code> - to control the auto-layout direction.</li><li><code>justify-content</code> - to control how elements are aligned on the axis parallel to the flex-direction.</li><li><code>align-items</code> - to control how elements are aligned on the axis perpendicular to the flex-direction.</li></ul><p>If you&apos;re using Figma, flexbox is similar to auto-layout. It&apos;s an incredibly useful tool in modern web development that makes creating layouts quick and simple.</p><p>Here&apos;s a great visual guide by <a href="https://twitter.com/NikkiSiapno?ref=code-frontend">@NikkiSiapno</a> and <a href="https://twitter.com/ChrisStaud?ref=code-frontend">@ChrisStaud</a> (they make awesome visual explanations of programming concepts):</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://nikkiandchris.gumroad.com/l/css-flexbox-made-simple?ref=code-frontend"><div class="kg-bookmark-content"><div class="kg-bookmark-title">CSS Flexbox Made Simple</div><div class="kg-bookmark-description">Everything you need to know about Flexbox in 15 pagesSimple to understand and beautifully explained with illustrations and diagrams.What&#x2019;s included?Flexbox terminologyAll 15 flexbox properties explained simplyDescriptionsDefault valuesInvalid valuesIllustrations that demonstrate property valuesAcces&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://public-files.gumroad.com/variants/wdck5hnwihkjk5je992bhv27q99g/532b90e67d0f2b8d3f37ee46f365ac5444b519f35527f4d9d3c523dd33d2fd30" alt="Practical CSS Guide for Busy Developers"><span class="kg-bookmark-author">Gumroad</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://public-files.gumroad.com/variants/yzh37cm4ep1x15xsqe4uc6nqom1a/baaca0eb0e33dc4f9d45910b8c86623f0144cea0fe0c2093c546d17d535752eb" alt="Practical CSS Guide for Busy Developers"></div></a></figure><h3 id="font">Font</h3><p>The most common font properties you&apos;ll use are:</p><ul><li> <code>font-size</code> - to change the size of the font.</li><li><code>font-family</code> - to change the font itself.</li></ul><p>Additionally, you will sometimes want to use <code>line-height</code> which essentially lets you increase of decrease the space between lines of text.</p><p>Here&apos;s what they might look like:</p><pre><code class="language-css">body {
  font-size: 12px;
  line-height: 20px;
  font-family: &quot;Gill Sans&quot;, Arial, sans-serif; /* Ordered by preferrence */
}</code></pre><h3 id="position">Position</h3><p>There are three values you&apos;ll be most commonly using for <code>position</code> property:</p><ul><li><code>relative</code> - the container element for it&apos;s <code>absolute</code> positioned children.</li><li><code>absolute</code> - overlays the element on top of other content at the coordinates inside the <code>relative</code> parent.</li><li><code>fixed</code> - same as absolute, except coordinates are relative to the browser&apos;s viewport.</li></ul><p>You can specify the coordinates of the element using <code>top</code>, <code>left</code>, <code>right</code> and <code>bottom</code> properties:</p><figure class="kg-card kg-code-card"><pre><code class="language-css">.parent {
  position: relative; /* Container for absolute children */
}

.child {
  position: absolute; /* Relative to .parent */
  
  /* Coordinates inside .parent */
  top: 50%;
  bottom: 0px;
  left: 20px;
  right: 20px;
}

.overlay {
  position: fixed; /* Relative to the browser window */
  top: 100%;
}</code></pre><figcaption>CSS <code>position</code> usage</figcaption></figure><h3 id="z-index">Z Index</h3><p>CSS <code>z-index</code> is used to control which elements will be on top when they overlap other elements. A higher number means higher priority. Usually, it&apos;s used on <code>absolute</code> or <code>fixed</code> positioned elements:</p><figure class="kg-card kg-code-card"><pre><code class="language-css">.absolute-element {
  z-index: 10;
}</code></pre><figcaption>CSS <code>z-index</code> usage</figcaption></figure><h3 id="overflow">Overflow</h3><p>The <code>overflow</code> property controls how an element&apos;s contents should behave when they don&apos;t fit into its box anymore.</p><p>Most commonly, you will want to set <code>overflow: auto;</code> to show the scrollbars. However, you also have the option to clip the content using <code>overflow: hidden;</code> which can be useful if you want to show an ellipsis for instance.</p><figure class="kg-card kg-code-card"><pre><code class="language-css">.clipped-container {
  overflow: hidden; /* Will hide content that doesn&apos;t fit */
}

/* You can control the vertical and horizontal overflows individually */
.scrollbar-container {
  overflow-y: auto; /* Will only show vertical scrollbar */
  overflow-x: hidden; /* Prevents horizontal scrolling */
}</code></pre><figcaption>CSS <code>overflow</code> usage</figcaption></figure><h3 id="box-sizing">Box Sizing</h3><p>The <code>box-sizing</code> property controls what <code>width</code> and <code>height</code> mean - the content size or the box size.</p><p>To save yourself frustration, I recommend just setting <code>border-box</code> on all elements (instead of the default <code>content-box</code>), because it&apos;s more intuitive to work with:</p><figure class="kg-card kg-code-card"><pre><code class="language-css">* {
  box-sizing: border-box;
}</code></pre><figcaption>Resetting the <code>box-sizing</code> on all elements</figcaption></figure><p>In the long term, it&apos;s important to understand the <a href="https://web.dev/learn/css/box-model/?ref=code-frontend">CSS box model</a>, so here&apos;s a great brief explanation by <a href="https://twitter.com/ishratUmar18/status/1556652722182430723?s=20&amp;t=5qhCUtnHZrwLX35vRooiwg&amp;ref=code-frontend">@ishratUmar18</a> on Twitter:</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">The Box Model in CSS &#x1F3A8;<br><br>&#x2727; In HTML, all elements are considered as a box. A box comprises padding, border, and margin in addition to its actual content.<br><br>A thread &#x1F447;&#x1F3FB; <a href="https://t.co/O8h8RZiDnP?ref=code-frontend">pic.twitter.com/O8h8RZiDnP</a></p>&#x2014; Ishrat (@ishratUmar18) <a href="https://twitter.com/ishratUmar18/status/1556652722182430723?ref_src=twsrc%5Etfw&amp;ref=code-frontend">August 8, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><h2 id="conclusion">Conclusion</h2><p>Even though there are hundreds of available CSS properties, you will rarely use most of them. This guide covered the ones necessary to start building something.</p><p>That being said, there is still <em>a lot</em> to learn if you want to create amazing-looking websites. I haven&apos;t touched upon <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries?ref=code-frontend">media queries</a> or <a href="https://css-tricks.com/snippets/css/complete-guide-grid/?ref=code-frontend">CSS grid</a>. You should learn them next. However, this guide should be enough to get you started building your first app. </p><p>Now, go start building something!</p><hr><p>PS, if you still want to read something, why not learn about React hooks and patterns they&apos;ve replaced?</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://codefrontend.com/react-hooks-patterns/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">React Patterns Replaced by Hooks</div><div class="kg-bookmark-description">Learn how React hooks have replaced the old coding patterns so that you write clean modern code.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://codefrontend.com/content/images/size/w256h256/2022/07/code-frontend-logo-3.png" alt="Practical CSS Guide for Busy Developers"><span class="kg-bookmark-author">Code Frontend</span><span class="kg-bookmark-publisher">Vincas Stonys</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://codefrontend.com/content/images/2022/09/react-patterns-replaced-by-hooks.jpeg" alt="Practical CSS Guide for Busy Developers"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Modernizing Old React Patterns With React Hooks]]></title><description><![CDATA[Learn how to replace HOCs, render props and container components with custom React hooks.]]></description><link>https://codefrontend.com/react-hooks-patterns/</link><guid isPermaLink="false">632892b64389f73e27914abd</guid><category><![CDATA[Deep Dive]]></category><category><![CDATA[ReactJS]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Sat, 24 Sep 2022 18:21:00 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2022/09/react-patterns-replaced-by-hooks.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://codefrontend.com/content/images/2022/09/react-patterns-replaced-by-hooks.jpeg" alt="Modernizing Old React Patterns With React Hooks"><p>React hooks have changed the way we build frontends ever since their introduction in React 16.8.</p><p>In combination with functional components, they&apos;ve greatly reduced the amount of boilerplate code we write and gave us a place to store our reusable logic.</p><p>Prior to hooks, where to store the shared logic used to be a difficult question without a clear answer. For that, the React community came up with various patterns, which nowadays have largely been obsoleted by React hooks.</p><p>Nonetheless, those patterns are still prevalent in many older articles, which causes confusion, especially among beginners.</p><p>This article will explain those patterns and show how React hooks have replaced them so that you&apos;re able to better navigate the ever-changing React landscape. You will be writing better front-end code afterwards.</p><h2 id="1-container-presentational-components">1) Container-presentational components</h2><p>This pattern was introduced to separate simple stateless UI components from those that use lifecycle methods or trigger side effects. It was largely popularized by the Redux framework and was sometimes called smart-dumb components.</p><p>At the time, if you wanted to hook into the lifecycle methods or even simply have some local state, you needed to write a class component, which generally involved writing more boilerplate and more complex code. The container-presentational component pattern helped with that.</p><p>The presentational component would receive anything it needed via props, so it could be a simple stateless functional component, that mostly contained JSX:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">import React from &quot;react&quot;;

const CommentList = comments =&gt; (
  &lt;ul&gt;
    {comments.map(({ body, author }) =&gt;
      &lt;li&gt;{body}-{author}&lt;/li&gt;
    )}
  &lt;/ul&gt;
)

export default CommentList</code></pre><figcaption>The presentational component <code>CommentList.js</code></figcaption></figure><p>Then, the container component would mostly contain the side-effect code (e.g. bind to global redux state) or lifecycle-related code (e.g. calling the APIs). You could then use it to wrap any compatible presentational component to reuse the logic:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">import CommentList from &quot;./CommentList&quot;;

class CommentListContainer extends React.Component {
  constructor() {
    super();
    this.state = { comments: [] }
  }
  
  componentDidMount() {
    fetch(&quot;/my-comments.json&quot;)
      .then(res =&gt; res.json())
      .then(comments =&gt; this.setState({ comments }))
  }
  
  render() {
    return &lt;CommentList comments={this.state.comments} /&gt;;
  }
}</code></pre><figcaption>The container component <code>CommentListContainer.js</code></figcaption></figure><p>That was an example of a container-presentational component pattern from 2015 by <a href="https://gist.github.com/chantastic/fc9e3853464dffdb1e3c?ref=code-frontend">Michael Chan</a>. It&apos;s now considered ancient in the internet years.</p><h3 id="example-with-functional-components">Example with functional components</h3><p>Nowadays, you&apos;re unlikely to see class components in action. However, the pattern could be used with modern functional components too:</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="1000" height="500" src="https://codesandbox.io/embed/still-thunder-iq8r4y?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FFilesContainer.tsx&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe><figcaption>Example of the container-presentational components.</figcaption></figure><h3 id="replace-the-container-with-a-custom-hook">Replace the container with a custom hook</h3><p>However, since our presentational component doesn&apos;t do much else than manage some state and register event handlers and effects, we can move all of it into a reusable custom hook:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">function useFiles() {
  const [files, setFiles] = useState&lt;File[] | undefined&gt;();

  useEffect(() =&gt; {
    loadFiles().then(setFiles);
  }, []);

  const upload = async () =&gt; {
    const file = await uploadFile();
    setFiles((files) =&gt; (files ? [...files, file] : [file]));
  };

  return { files, upload };
}</code></pre><figcaption>A custom hook containing the container component logic.</figcaption></figure><p>Then we can use it together with the presentational component, wherever we need the logic:</p><pre><code class="language-tsx">function FileUploadApp() {
  const { files, upload } = useFiles();

  return (
    &lt;div&gt;
      &lt;h1&gt;Cloud Drive&lt;/h1&gt;
      {files ? &lt;Files files={files} onUploadClick={upload} /&gt; : &lt;div&gt;Loading...&lt;/div&gt;}
    &lt;/div&gt;
  );
}</code></pre><p>Using custom hooks instead of container components is a much simpler and more flexible way to share reusable logic between components. Here&apos;s the full code:</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="1000" height="500" src="https://codesandbox.io/embed/container-presentational-custom-hook-gw8b23?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.tsx&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe><figcaption>The example from before, only using the custom hook.</figcaption></figure><h2 id="2-higher-order-components-hocs">2) Higher-Order components (HOCs)</h2><p>Higher-order component, also known as HOC, is a function that takes a component function as an argument and returns a new component function with some extra props or functionality.</p><p>They initially replaced mixins as a way to reuse logic between components, but nowadays it&apos;s easier to use hooks instead.</p><p>If you&apos;ve used redux before react-toolkit and hooks, you will remember having to wrap your container components with a <code>connect()</code> function. That&apos;s an example of a higher-order component:</p><figure class="kg-card kg-code-card"><pre><code class="language-jsx">function Container(props) {
  // ...
}

const mapStateToProps = state =&gt; ({
  // ...
});

export default connect(mapStateToProps)(Container);</code></pre><figcaption>The old style of connecting components to redux.</figcaption></figure><p>Even though it&apos;s easier to use custom hooks for code reuse, the HOC pattern is still useful sometimes - unlike hooks, the HOC has its own scope and can wrap a component not only with logic but also with additional JSX.</p><h3 id="creating-hocs">Creating HOCs</h3><p>To create a higher-order component you need to declare a function that accepts a component function as part of its arguments, and then return a new component function:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">interface HocProps {
  // ...
}

interface InjectedProps {
  // ...
}

// &#x1F447; Creates a HOC component that has accepts a component that requires OwnProps+InjectedProps
// The HOC will then inject the InjectedProps, but will additionally require HocProps
function createHOC&lt;OwnProps&gt;(Component: React.ComponentType&lt;OwnProps &amp; InjectedProps&gt;) {
  const injectedProps: InjectedProps = {
    // ...
  };

  return (props: OwnProps &amp; HocProps) =&gt; {
    // &#x1F4AD; Do something with HocProps
    return &lt;Component {...props} {...injectedProps} /&gt;;
  };
}</code></pre><figcaption>TypeScript example of a generic HOC.</figcaption></figure><p>The above is a framework for writing HOCs. The complexity comes from typescript usage. Strip out the types, and it becomes very simple. In fact, it&apos;s just a function closure.</p><h3 id="hoc-example">HOC example</h3><p>Here&apos;s the same &quot;file upload&quot; example from before, only using <code>withFiles</code> higher-order component:</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="1000" height="500" src="https://codesandbox.io/embed/container-presentational-custom-hook-forked-rlfbnf?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FwithFiles.tsx&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe><figcaption>The example from before, using a HOC.</figcaption></figure><p>It can be refactored to use a custom hook in the exact same way you would refactor the container component:</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="1000" height="500" src="https://codesandbox.io/embed/container-presentational-custom-hook-gw8b23?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.tsx&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe><figcaption>The same custom hook example from before. Incidentally, that&apos;s how you refactor a HOC too.</figcaption></figure><h2 id="3-render-props">3) Render props</h2><p>Render prop is a function prop that returns a react element when called. The component with the render prop might not even render any JSX itself, and call the render function instead:</p><pre><code class="language-tsx">&lt;Component render={(props) =&gt; &lt;div&gt;some content&lt;/div&gt;} /&gt;</code></pre><p>The technique is used to separate business logic from rendering, often to delegate the rendering to the parent component. The render function gets access to the internal component state via props that are passed into it.</p><h3 id="render-props-example">Render props example</h3><p>Let&apos;s take the same &quot;file upload&quot; example from before, only this time with render props used to render the list and the upload button:</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="1000" height="500" src="https://codesandbox.io/embed/render-props-example-r2t8uj?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.tsx&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe><figcaption>Render props used to render the list item and the button.</figcaption></figure><h3 id="replacing-render-props-with-custom-hooks">Replacing render props with custom hooks</h3><p>At first glance, render props seem to let us customize how we render certain parts of the UI. They&apos;re not solving an issue of reusing logic. However, is that really so?</p><p>In the previous examples, we used the custom hook to share logic. Now, the reason render props is a function in the first place, and not simply JSX - is to gain access to the component&apos;s internal state.</p><p>We could move that internal state into a custom hook, lift it up to the component&apos;s parent, and then pass it down as props. Then we could use the same state from the hook to render whatever we would render in the render prop function.</p><p>This way we don&apos;t need the render props anymore, and instead can pass the rendered JSX as props:</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="1000" height="500" src="https://codesandbox.io/embed/render-props-custom-hook-7iofgn?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.tsx&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe><figcaption>The custom hook is used in the parent component to replace the render props.</figcaption></figure><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Note that the <code>useFiles</code> hook is exactly the same. We didn&apos;t even need to do anything special to remove the render props from our code.</div></div><h2 id="conclusion">Conclusion</h2><p>I find it fascinating how custom hooks have replaced these three seemingly different patterns. Even more interesting is that the hook code is exactly the same in all three examples.</p><p>This hints to me that if we use custom hooks to share logic between components, we naturally don&apos;t need the container components, HOCs, or even render props.</p><p>Now when you read some older code examples, you can always reference the above three examples and see how that code could be rewritten using custom hooks. Use them, and your code will be cleaner and simpler.</p><p>As usual, find the code examples in my <a href="https://github.com/vincaslt/twitter-code/tree/main/src/patterns-replaced-by-custom-hooks?ref=code-frontend">GitHub repository</a>.</p><hr><p>Continue your streak of learning advanced React concepts and read about how to use React portals in the real world:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://codefrontend.com/react-portals/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Learn to Use React Portals in the Real World</div><div class="kg-bookmark-description">What real-world problems do they solve? Learn by example.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://codefrontend.com/content/images/size/w256h256/2022/07/code-frontend-logo-3.png" alt="Modernizing Old React Patterns With React Hooks"><span class="kg-bookmark-author">Code Frontend</span><span class="kg-bookmark-publisher">Vincas Stonys</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://codefrontend.com/content/images/2022/09/react-portals-blog-cover.jpeg" alt="Modernizing Old React Patterns With React Hooks"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Throttle and Debounce in Javascript and React]]></title><description><![CDATA[Debounce and throttle are one of the most common optimization techniques in JavaScript. Learn to use them in React.]]></description><link>https://codefrontend.com/debounce-throttle-js-react/</link><guid isPermaLink="false">632892734389f73e27914ab3</guid><category><![CDATA[Performance]]></category><category><![CDATA[ReactJS]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Vincas Stonys]]></dc:creator><pubDate>Wed, 21 Sep 2022 07:27:19 GMT</pubDate><media:content url="https://codefrontend.com/content/images/2022/09/throttle-and-debounce.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://codefrontend.com/content/images/2022/09/throttle-and-debounce.jpg" alt="Throttle and Debounce in Javascript and React"><p>Both throttle and debounce are used to optimize expensive, frequently executed actions. They&apos;re two of the most common performance optimization techniques.</p><p>I&apos;ll show you how to implement them yourself and how to use them in JavaScript and React.</p><h2 id="what-is-debouncing">What is debouncing?</h2><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">&#x1F4DA;</div><div class="kg-callout-text">Debouncing is a technique used to improve the performance of frequently executed actions, by delaying them, grouping them, and only executing the last call.</div></div><p>Picture this: you want a search input where results are queried automatically as you&apos;re typing into it.</p><p>You wouldn&apos;t want to ping the backend on every keystroke, rather you only care about the final value. That&apos;s the perfect use case for a debounce function, in fact, it&apos;s a very common one.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4CC;</div><div class="kg-callout-text">Use the debounce function when you only care about the final result of the expensive action.</div></div><hr><p><em>Real quick - would you like to master Web Development? If so, be sure to check out my recommended platform to learn coding online:</em></p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://codefrontend.com/learn-to-code-online/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Learn Coding Online</div><div class="kg-bookmark-description">Here&#x2019;s the platform I recommend if you want to learn Web Development online.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://codefrontend.com/content/images/size/w256h256/2022/07/code-frontend-logo-3.png" alt="Throttle and Debounce in Javascript and React"><span class="kg-bookmark-author">Code Frontend</span><span class="kg-bookmark-publisher">Vincas Stonys</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://codefrontend.com/content/images/2023/03/educative-page-cover.jpeg" alt="Throttle and Debounce in Javascript and React"></div></a></figure><h3 id="implementing-a-basic-debounce-function">Implementing a basic debounce function</h3><p>Debounce is implemented using a timer, where the action executes after the timeout. If the action is repeated before the timer has finished, we restart the timer and queue the latest action.</p><p>Here&apos;s the most basic implementation in TypeScript:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">function debounce&lt;Args extends unknown[]&gt;(fn: (...args: Args) =&gt; void, delay: number) {
  let timeoutID: number | undefined;

  const debounced = (...args: Args) =&gt; {
    clearTimeout(timeoutID);
    timeoutID = window.setTimeout(() =&gt; fn(...args), delay);
  };

  return debounced;
}</code></pre><figcaption>Simple debounce function.&#xA0;</figcaption></figure><ol><li>Create a function that accepts a function to debounce and the timeout delay as arguments.</li><li>The debounce function returns a new function. When it&apos;s executed, it creates a timer to execute the original function after the delay and cancels the previous timer.</li></ol><p>Here&apos;s how to use it:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">const expensiveCalculation = debounce(() =&gt; {
  // &#x1F6A9; Do the expensive calculation
}, 1000)

// &#x1F447; Frequently called function
function onChange() {
  // &#x1F447; Will run at most once per second
  expensiveCalculation()
}</code></pre><figcaption>Example debounce usage.</figcaption></figure><h3 id="flushing-the-debounce-result">Flushing the debounce result</h3><p>What if we sometimes need to run the action before the delay and cancel any pending executions? We call that flushing.</p><p>We can attach an extra method to the original debounce function implementation, that runs the pending action instantly and clears the timer:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">function debounce&lt;Args extends unknown[]&gt;(fn: (...args: Args) =&gt; void, delay: number) {
  let timeoutID: number | undefined;
  let lastArgs: Args | undefined;

  const run = () =&gt; {
    if (lastArgs) {
      fn(...lastArgs);
      lastArgs = undefined;
    }
  };

  const debounced = (...args: Args) =&gt; {
    clearTimeout(timeoutID);
    lastArgs = args;
    timeoutID = window.setTimeout(run, delay);
  };

  debounced.flush = () =&gt; {
    clearTimeout(timeoutID);
    run();
  };

  return debounced;
}</code></pre><figcaption>Debounce function with flush support.</figcaption></figure><ol><li>Store the arguments of the last action into an array when calling the debounced function.</li><li>Create a new <code>debounce.flush</code> function that runs the action with the most recently used arguments and clears the timer and cached arguments.</li></ol><p>Call <code>debounce.flush()</code> to run the action immediately:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">const expensiveCalculation = debounce(() =&gt; {
  // Expensive calculation...
}, 1000)

function onChange() {
  expensiveCalculation()
}

function onClose() {
  // &#x1F447; Instantly runs the calculation and cancels any pending calls
  expensiveCalculation.flush()
}</code></pre><figcaption>Flushing the debounce function.</figcaption></figure><p>This implementation is still quite basic because, in the real world, you may want the debounce function to additionally handle these:</p><ul><li>Always run the first call (greedy/eager debounce);</li><li>To cap the delay, in case the action is long-running and continuous and we want the function to run at some point (a mix between debounce and throttle).</li><li>An ability to clear the queued action, without running it.</li></ul><p>Feel free to extend the implementation to add these considerations yourself. If you&apos;re fine with depending on a 3rd party library, there is a good implementation on <a href="https://github.com/niksy/throttle-debounce?ref=code-frontend">GitHub</a>, or by <a href="https://lodash.com/docs?ref=code-frontend#debounce">lodash</a>.</p><h2 id="what-is-throttling">What is throttling?</h2><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">&#x1F4DA;</div><div class="kg-callout-text">Throttling is a technique used to improve the performance of frequently executed actions, by limiting the rate of execution. It is similar to debounce, except it guarantees the regular execution of an action.</div></div><p>The most common use case from my experience is to optimize the resize and scroll handlers. That&apos;s especially important in React because they often trigger state updates that are responsible for making your components re-render.</p><p>The solution to this problem is to call the handlers intermittently. Most of the time we don&apos;t need to keep 100% in sync with the resize or scroll events, so we can throttle the handler functions. I like to think of them as <em>lossy event handlers</em>.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4CC;</div><div class="kg-callout-text">Use the throttle function when you care about some intermediate values of a frequently executed expensive action, but it&apos;s ok to discard most of them.</div></div><h3 id="implementing-a-throttle-function"><strong>Implementing a throttle function</strong></h3><p>The throttle function is implemented using a timer that puts the throttled function on cooldown:</p><ol><li>Create a throttle function that accepts a callback and the cooldown duration arguments.</li><li>The throttle function returns a new function, which when executed, stores the call arguments and starts the cooldown timer.</li><li>When the timer finishes, execute the action with the cached arguments and clear them.</li></ol><figure class="kg-card kg-code-card"><pre><code class="language-ts">function throttle&lt;Args extends unknown[]&gt;(fn: (...args: Args) =&gt; void, cooldown: number) {
  let lastArgs: Args | undefined;

  const run = () =&gt; {
    if (lastArgs) {
      fn(...lastArgs);
      lastArgs = undefined;
    }
  };

  const throttled = (...args: Args) =&gt; {
    const isOnCooldown = !!lastArgs;

    lastArgs = args;

    if (isOnCooldown) {
      return;
    }

    window.setTimeout(run, cooldown);
  };

  return throttled;
}</code></pre><figcaption>The throttle function code snippet.</figcaption></figure><p>Here&apos;s how you would use it:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">const expensiveCalculation = throttle(() =&gt; {
  // &#x1F6A9; Do the expensive calculation
}, 100)

function onResize() {
  // &#x1F447; Will be called once every 100ms
  expensiveCalculation()
}</code></pre><figcaption>Example throttle usage.</figcaption></figure><p>Similarly to debounce, you may need some extra flexibility, which isn&apos;t hard to implement yourself, but you may consider using a 3rd party library for:</p><ul><li>Execute the action initially (currently, it&apos;s delayed);</li><li>Cancel or flush the throttled function;</li></ul><p>As before, you have a good library on <a href="https://github.com/niksy/throttle-debounce?ref=code-frontend">GitHub</a> and by <a href="https://lodash.com/docs?ref=code-frontend#throttle">lodash</a>.</p><h2 id="using-throttle-and-debounce-in-react">Using throttle and debounce in React</h2><p>In React, new functions are created every time the component re-renders, which is not great for our debounce/throttle implementation which relies on the closure staying the same.</p><p>When you use debounce and throttle in React, make sure to wrap them with <code>useMemo</code> hook:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">const handleChangeText = useMemo(() =&gt;
  debounce((e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    // Handle the onChange event
  }, 1000),
[]);

const handleWindowResize = useMemo(() =&gt;
  throttle(() =&gt; {
    // Handle the onResize event
  }, 100),
[]);</code></pre><figcaption>Wrapping the debounce and throttle with <code>useMemo</code>.</figcaption></figure><h3 id="custom-usedebounce-and-usethrottle-hooks">Custom useDebounce and useThrottle hooks</h3><p>You can also turn this into custom react hooks:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import { DependencyList, useMemo } from &apos;react&apos;;
import debounce from &apos;./debounce&apos;;
import throttle from &apos;./throttle&apos;;

function useDebounce&lt;Args extends unknown[]&gt;(
  cb: (...args: Args) =&gt; void,
  delay: number,
  deps: DependencyList,
) {
  return useMemo(() =&gt; debounce(cb, delay), deps);
}

function useThrottle&lt;Args extends unknown[]&gt;(
  cb: (...args: Args) =&gt; void,
  cooldown: number,
  deps: DependencyList,
) {
  return useMemo(() =&gt; throttle(cb, cooldown), deps);
}</code></pre><figcaption>Code snippet for <code>useDebounce</code> and <code>useThrottle</code> hooks.</figcaption></figure><h3 id="debounce-example-in-react">Debounce example in React</h3><p>Here&apos;s how you could use the custom <code>useDebounce</code> hook in React:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">import { ChangeEvent, useState } from &apos;react&apos;;
import useDebounce from &apos;./useDebounce&apos;;

function DebounceWithFlushExample() {
  const [text, setText] = useState(&apos;&apos;);

  const handleChangeText = useDebounce(
    (e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
      setText(e.target.value);
    },
    5000,
    [],
  );

  return (
    &lt;div&gt;
      &lt;div&gt;Text (5 second): {text}&lt;/div&gt;
      &lt;input type=&quot;text&quot; onChange={handleChangeText} /&gt;
      &lt;button onClick={handleChangeText.flush}&gt;Flush&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre><figcaption>Example of using debounce in React.</figcaption></figure><h3 id="throttle-example-in-react">Throttle example in React</h3><p>Here&apos;s how you could use the custom <code>useThrottle</code> hook in React:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">import { useEffect, useState } from &apos;react&apos;;
import useThrottle from &apos;./useThrottle&apos;;

type Range = &apos;small&apos; | &apos;medium&apos; | &apos;large&apos;;

const sizeToRange = (size: number): Range =&gt; {
  if (size &lt; 600) {
    return &apos;small&apos;;
  } else if (size &gt; 1200) {
    return &apos;large&apos;;
  }
  return &apos;medium&apos;;
};

function ThrottleExample() {
  const [range, setRange] = useState(sizeToRange(window.innerWidth));

  const handleWindowResize = useThrottle(
    () =&gt; {
      // Execute some expensive operation
      setRange(sizeToRange(window.innerWidth));
    },
    100,
    [],
  );

  useEffect(() =&gt; {
    window.addEventListener(&apos;resize&apos;, handleWindowResize);
    return () =&gt; {
      window.removeEventListener(&apos;resize&apos;, handleWindowResize);
    };
  }, [handleWindowResize]);

  return &lt;div&gt;Screen size (resize to see): {range}&lt;/div&gt;;
}

export default ThrottleExample;
</code></pre><figcaption>Example of using throttle in React.</figcaption></figure><p>Here are both examples in CodeSandbox so you can play around with them:</p><figure class="kg-card kg-embed-card"><iframe width="1000" height="500" src="https://codesandbox.io/embed/debounce-and-throttle-in-react-60yiwy?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FExample.tsx&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe></figure><h2 id="when-to-use-throttling-and-when-to-debounce">When to use throttling and when to debounce?</h2><p>Use <strong>debounce</strong> when you don&apos;t care about the intermediate results because the action only makes sense with the last result. An example of that is - search results.</p><p>Use <strong>throttle</strong> when you need intermediate results, but a small delay is acceptable. For example, resizing or scrolling.</p><p>You can find the code examples in my <a href="https://github.com/vincaslt/twitter-code/tree/main/src?ref=code-frontend">GitHub repository</a>.</p><hr><p><em>Hey, if you&apos;re serious about learning React, I highly recommend the courses on <a href="https://www.educative.io/explore?aff=Vp6m&amp;ref=code-frontend">educative.io</a> - it&apos;s <a href="https://codefrontend.com/learn-to-code-online/">my favorite platform</a> for learning to code.</em></p><p><em>Here&apos;s a course on React that will teach you how to build something more than another to-do app (<strong>finally</strong>, am I right?):</em></p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.educative.io/path/react-front-end-developer?aff=Vp6m&amp;ref=code-frontend"><div class="kg-bookmark-content"><div class="kg-bookmark-title">React for Front-End Developers - Learn Interactively</div><div class="kg-bookmark-description">Backed by Facebook and used by tech companies large and small, React has quickly become the most popular front-end framework in the tech world. Its developers are constantly in high demand. If you&#x2019;re already familiar with JavaScript, adding React to your skillset is a wise career investment. This p&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.educative.io/static/favicons/faviconV2.png" alt="Throttle and Debounce in Javascript and React"><span class="kg-bookmark-author">Educative: Interactive Courses for Software Developers</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://educative.io/api/collection/10370001/6462823084326912/image/6677150020403200.png" alt="Throttle and Debounce in Javascript and React"></div></a></figure>]]></content:encoded></item></channel></rss>