<?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[CodeWithGillis]]></title><description><![CDATA[From web dev foundations to the future of AI. Deep dives & practical guides at Codewithgillis.com..]]></description><link>https://codewithgillis.com/</link><image><url>https://codewithgillis.com/favicon.png</url><title>CodeWithGillis</title><link>https://codewithgillis.com/</link></image><generator>Ghost 5.82</generator><lastBuildDate>Wed, 22 Apr 2026 10:19:28 GMT</lastBuildDate><atom:link href="https://codewithgillis.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[My Perspective on Software Development]]></title><description><![CDATA[<p>When I first started out in software development, it felt like solving an impossible puzzle&#x2014;complex, intimidating, and never-ending. But as I gained more experience (and made plenty of mistakes), I began to realize that building software doesn&#x2019;t have to feel overwhelming. With the right perspective, it</p>]]></description><link>https://codewithgillis.com/a-different-perspective-on-software-development/</link><guid isPermaLink="false">68c92f234387b100018343f5</guid><dc:creator><![CDATA[Ngwa Nathan]]></dc:creator><pubDate>Thu, 25 Sep 2025 15:59:13 GMT</pubDate><media:content url="https://codewithgillis.com/content/images/2025/09/ChatGPT-Image-Sep-25--2025--05_04_41-PM.png" medium="image"/><content:encoded><![CDATA[<img src="https://codewithgillis.com/content/images/2025/09/ChatGPT-Image-Sep-25--2025--05_04_41-PM.png" alt="My Perspective on Software Development"><p>When I first started out in software development, it felt like solving an impossible puzzle&#x2014;complex, intimidating, and never-ending. But as I gained more experience (and made plenty of mistakes), I began to realize that building software doesn&#x2019;t have to feel overwhelming. With the right perspective, it can actually be straightforward, even enjoyable.</p><p>Here are two lessons that completely changed how I approach development:</p><hr><h3 id="focus-on-one-feature-at-a-time"> Focus on One Feature at a Time</h3><p>One of the biggest mistakes I made early on was trying to wrap my head around the <em>entire</em> project or dozens of features all at once. Unsurprisingly, this approach only led to frustration and burnout.</p><p>The truth is, no one builds a fully finished product in a single step. The key is to break things down and focus on one feature at a time. Instead of stressing over everything that still needs to be built, I learned to ask:</p><p> <em>What&#x2019;s the one small part I can make work perfectly right now?</em></p><p>This mindset shift makes the process far more manageable. Each small success builds momentum, and before long, you find yourself with a working application.</p><hr><h3 id="think-like-a-user-first">Think Like a User First</h3><p>Another early mistake I often made was diving straight into the code without thinking about the user. The result? Features that technically worked but were confusing or frustrating to use.</p><p>What I&#x2019;ve since learned is that usability comes first. Before writing a single line of code, I now step into the user&#x2019;s shoes:</p><ul><li>How will they interact with this feature?</li><li>What steps will they take?</li><li>Does this flow feel natural and intuitive?</li></ul><p>By mapping out the user experience first, the coding part becomes much clearer. This simple habit&#x2014;working from usability to code&#x2014;saves countless hours and leads to software that people actually <em>enjoy</em> using.</p><hr><h3 id="final-thoughts"> Final Thoughts</h3><p>As a junior developer, it&#x2019;s easy to feel overwhelmed by the complexity of software development. But here&#x2019;s the truth: it&#x2019;s not some impossible puzzle. If you focus on one feature at a time and always think like a user first, the process becomes much simpler, and your results get a whole lot better.</p><p>In the end, software development is just problem-solving&#x2014;step by step, feature by feature. And the more you practice, the more natural it feels.</p>]]></content:encoded></item><item><title><![CDATA[Building Enterprise-Grade Frontend Applications: A Complete Guide to Advanced Project Structure]]></title><description><![CDATA[<p>As senior developers, we know that building scalable frontend applications goes far beyond writing functional code. It requires establishing a robust foundation that enables teams to work efficiently, maintain code quality, and deliver reliable products. This comprehensive guide explores the advanced tooling and architectural decisions that transform a simple frontend</p>]]></description><link>https://codewithgillis.com/building-enterprise-grade-frontend-applications-a-complete-guide-to-advanced-project-structure/</link><guid isPermaLink="false">68d04df24387b100018344ee</guid><dc:creator><![CDATA[Sone GIllis]]></dc:creator><pubDate>Sun, 21 Sep 2025 19:42:53 GMT</pubDate><media:content url="https://codewithgillis.com/content/images/2025/09/building-enterprise-grade-apps.png" medium="image"/><content:encoded><![CDATA[<img src="https://codewithgillis.com/content/images/2025/09/building-enterprise-grade-apps.png" alt="Building Enterprise-Grade Frontend Applications: A Complete Guide to Advanced Project Structure"><p>As senior developers, we know that building scalable frontend applications goes far beyond writing functional code. It requires establishing a robust foundation that enables teams to work efficiently, maintain code quality, and deliver reliable products. This comprehensive guide explores the advanced tooling and architectural decisions that transform a simple frontend project into an enterprise-grade application.</p>
<h2 id="table-of-contents">Table of Contents</h2>
<ol>
<li><a href="#monorepo-architecture">Monorepo Architecture with Modern Package Managers</a></li>
<li><a href="#build-system-optimization">Build System Optimization with Task Runners</a></li>
<li><a href="#typescript-configuration">TypeScript Configuration &amp; Project References</a></li>
<li><a href="#code-quality-strategy">Code Quality &amp; Linting Strategy</a></li>
<li><a href="#git-workflow">Git Workflow &amp; Commit Standards</a></li>
<li><a href="#cicd-pipeline">CI/CD Pipeline Architecture</a></li>
<li><a href="#testing-strategy">Testing Strategy &amp; Coverage</a></li>
<li><a href="#branch-protection">Branch Protection &amp; Validation</a></li>
<li><a href="#deployment-strategies">Deployment Strategies</a></li>
<li><a href="#best-practices">Best Practices &amp; Lessons Learned</a></li>
</ol>
<h2 id="monorepo-architecture-with-modern-package-managers">Monorepo Architecture with Modern Package Managers</h2>
<h3 id="why-monorepos">Why Monorepos?</h3>
<p>Traditional multi-repo setups create several challenges:</p>
<ul>
<li><strong>Dependency Hell</strong>: Managing versions across multiple repositories</li>
<li><strong>Code Duplication</strong>: Shared utilities scattered across projects</li>
<li><strong>Complex Release Coordination</strong>: Coordinating releases across related packages</li>
<li><strong>Developer Experience</strong>: Context switching between repositories</li>
</ul>
<p>A well-structured monorepo solves these problems while maintaining clear boundaries:</p>
<pre><code>frontend-platform/
&#x251C;&#x2500;&#x2500; apps/                     # Applications
&#x2502;   &#x251C;&#x2500;&#x2500; main-app/            # Primary application
&#x2502;   &#x251C;&#x2500;&#x2500; auth-service/        # Authentication service
&#x2502;   &#x251C;&#x2500;&#x2500; admin-panel/         # Administrative interface
&#x2502;   &#x2514;&#x2500;&#x2500; mobile-app/          # Mobile application
&#x251C;&#x2500;&#x2500; packages/                 # Shared libraries
&#x2502;   &#x251C;&#x2500;&#x2500; data-layer/          # API layer &amp; state management
&#x2502;   &#x251C;&#x2500;&#x2500; ui-components/       # Reusable UI components
&#x2502;   &#x251C;&#x2500;&#x2500; utilities/           # Utility functions
&#x2502;   &#x251C;&#x2500;&#x2500; api-client/          # Core API definitions
&#x2502;   &#x2514;&#x2500;&#x2500; feature-modules/     # Domain-specific features
&#x2514;&#x2500;&#x2500; tooling/                 # Build tools &amp; configurations
</code></pre>
<h3 id="pnpm-workspace-configuration">pnpm Workspace Configuration</h3>
<p><strong>pnpm-workspace.yaml</strong>:</p>
<pre><code class="language-yaml">packages:
  - apps/*
  - packages/*
ignoredBuiltDependencies:
  - esbuild
  - fsevents
  - sharp
  - unrs-resolver
</code></pre>
<p><strong>Key Benefits of pnpm</strong>:</p>
<ul>
<li><strong>Efficient Storage</strong>: Symlinked dependencies reduce disk usage</li>
<li><strong>Fast Installs</strong>: Content-addressable storage with deduplication</li>
<li><strong>Strict Dependencies</strong>: Prevents phantom dependencies</li>
<li><strong>Workspace Protocol</strong>: <code>workspace:*</code> ensures local package linking</li>
</ul>
<h3 id="package-management-strategy">Package Management Strategy</h3>
<p>Each package defines its dependencies precisely:</p>
<pre><code class="language-json">{
  &quot;dependencies&quot;: {
    &quot;@company/data-layer&quot;: &quot;workspace:*&quot;,
    &quot;@company/ui-components&quot;: &quot;workspace:*&quot;,
    &quot;@company/utilities&quot;: &quot;workspace:*&quot;
  }
}
</code></pre>
<p>The <code>workspace:*</code> protocol ensures:</p>
<ul>
<li>Local packages are always linked during development</li>
<li>Production builds use published versions</li>
<li>Dependency graph remains consistent across the monorepo</li>
</ul>
<h2 id="build-system-optimization-with-turborepo">Build System Optimization with Turborepo</h2>
<h3 id="why-turborepo">Why Turborepo?</h3>
<p>Turborepo transforms our build system with:</p>
<ul>
<li><strong>Intelligent Caching</strong>: Only rebuilds changed packages</li>
<li><strong>Parallel Execution</strong>: Maximizes CPU utilization</li>
<li><strong>Remote Caching</strong>: Shares build artifacts across team members</li>
<li><strong>Dependency-Aware Builds</strong>: Respects package dependency graph</li>
</ul>
<h3 id="turborepo-configuration">Turborepo Configuration</h3>
<p><strong>turbo.json</strong>:</p>
<pre><code class="language-json">{
  &quot;$schema&quot;: &quot;https://turborepo.com/schema.json&quot;,
  &quot;ui&quot;: &quot;tui&quot;,
  &quot;tasks&quot;: {
    &quot;build&quot;: {
      &quot;dependsOn&quot;: [&quot;^build&quot;],
      &quot;inputs&quot;: [&quot;$TURBO_DEFAULT$&quot;, &quot;.env*&quot;],
      &quot;outputs&quot;: [&quot;dist/**&quot;, &quot;.next/**&quot;, &quot;!.next/cache/**&quot;]
    },
    &quot;lint&quot;: {
      &quot;dependsOn&quot;: [&quot;^lint&quot;]
    },
    &quot;typecheck&quot;: {
      &quot;dependsOn&quot;: [&quot;^build&quot;]
    },
    &quot;test&quot;: {
      &quot;dependsOn&quot;: [&quot;^build&quot;]
    },
    &quot;dev&quot;: {
      &quot;cache&quot;: false,
      &quot;persistent&quot;: true
    }
  }
}
</code></pre>
<h3 id="build-script-orchestration">Build Script Orchestration</h3>
<p><strong>Root package.json scripts</strong>:</p>
<pre><code class="language-json">{
  &quot;scripts&quot;: {
    &quot;prebuild&quot;: &quot;pnpm -r --filter ./packages/* run build&quot;,
    &quot;build&quot;: &quot;pnpm turbo run build&quot;,
    &quot;build:packages&quot;: &quot;pnpm turbo run build --filter &apos;./packages/*&apos;&quot;,
    &quot;dev&quot;: &quot;turbo run dev&quot;,
    &quot;dev:main&quot;: &quot;pnpm --filter enterprise-main-app dev&quot;,
    &quot;lint&quot;: &quot;pnpm turbo run lint&quot;,
    &quot;test&quot;: &quot;pnpm turbo run test --filter !e2e_tests&quot;,
    &quot;typecheck&quot;: &quot;tsc --noEmit&quot;
  }
}
</code></pre>
<p><strong>Performance Benefits</strong>:</p>
<ul>
<li><strong>Build Time Reduction</strong>: 60-80% faster builds with caching</li>
<li><strong>Selective Builds</strong>: Only affected packages rebuild</li>
<li><strong>Parallel Processing</strong>: Multiple packages build simultaneously</li>
<li><strong>Incremental Development</strong>: Faster feedback loops</li>
</ul>
<h2 id="typescript-configuration-project-references">TypeScript Configuration &amp; Project References</h2>
<h3 id="project-references-architecture">Project References Architecture</h3>
<p>TypeScript project references enable:</p>
<ul>
<li><strong>Independent Compilation</strong>: Each package compiles separately</li>
<li><strong>Build Coordination</strong>: Proper dependency order</li>
<li><strong>IDE Performance</strong>: Faster type checking and navigation</li>
<li><strong>Incremental Builds</strong>: Only recompile changed projects</li>
</ul>
<h3 id="base-configuration">Base Configuration</h3>
<p><strong>tsconfig.base.json</strong>:</p>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;baseUrl&quot;: &quot;.&quot;,
    &quot;paths&quot;: {
      &quot;@company/data-layer&quot;: [&quot;packages/data-layer/src&quot;],
      &quot;@ui-components/*&quot;: [&quot;packages/ui-components/src/*&quot;],
      &quot;@utilities/*&quot;: [&quot;packages/utilities/src/*&quot;]
    }
  },
  &quot;references&quot;: [
    { &quot;path&quot;: &quot;packages/data-layer&quot; },
    { &quot;path&quot;: &quot;packages/ui-components&quot; },
    { &quot;path&quot;: &quot;packages/utilities&quot; }
  ]
}
</code></pre>
<h3 id="root-configuration">Root Configuration</h3>
<p><strong>tsconfig.json</strong>:</p>
<pre><code class="language-json">{
  &quot;extends&quot;: &quot;./tsconfig.base.json&quot;,
  &quot;files&quot;: [],
  &quot;references&quot;: [
    { &quot;path&quot;: &quot;apps/main-app&quot; },
    { &quot;path&quot;: &quot;apps/auth-service&quot; },
    { &quot;path&quot;: &quot;apps/mobile-app&quot; },
    { &quot;path&quot;: &quot;apps/admin-panel&quot; },
    { &quot;path&quot;: &quot;packages/utilities&quot; },
    { &quot;path&quot;: &quot;packages/ui-components&quot; },
    { &quot;path&quot;: &quot;packages/data-layer&quot; }
  ]
}
</code></pre>
<h3 id="benefits-of-this-approach">Benefits of This Approach</h3>
<ol>
<li><strong>Faster Type Checking</strong>: Each project maintains its own types</li>
<li><strong>Better IDE Support</strong>: IntelliSense works across package boundaries</li>
<li><strong>Incremental Compilation</strong>: Only changed projects recompile</li>
<li><strong>Clear Dependencies</strong>: Explicit project relationships</li>
</ol>
<h2 id="code-quality-linting-strategy">Code Quality &amp; Linting Strategy</h2>
<h3 id="eslint-configuration">ESLint Configuration</h3>
<p>Our ESLint setup enforces consistent code style across the monorepo:</p>
<p><strong>.eslintrc.cjs</strong>:</p>
<pre><code class="language-javascript">module.exports = {
  root: true,
  parser: &apos;@typescript-eslint/parser&apos;,
  plugins: [&apos;@typescript-eslint&apos;, &apos;react&apos;, &apos;react-hooks&apos;],
  extends: [
    &apos;eslint:recommended&apos;,
    &apos;plugin:@typescript-eslint/recommended&apos;,
    &apos;plugin:react/recommended&apos;,
    &apos;plugin:react-hooks/recommended&apos;,
    &apos;prettier&apos;, // Disables conflicting ESLint rules
  ],
  settings: {
    react: {
      version: &apos;detect&apos;,
    },
  },
};
</code></pre>
<h3 id="prettier-integration">Prettier Integration</h3>
<p><strong>Prettier Configuration</strong> ensures consistent formatting:</p>
<ul>
<li><strong>No Configuration Conflicts</strong>: ESLint extends &apos;prettier&apos; to disable conflicting rules</li>
<li><strong>Automatic Formatting</strong>: Pre-commit hooks format code</li>
<li><strong>Team Consistency</strong>: Everyone uses the same formatting rules</li>
</ul>
<h3 id="lint-staged-configuration">Lint-Staged Configuration</h3>
<p><strong>package.json</strong>:</p>
<pre><code class="language-json">{
  &quot;lint-staged&quot;: {
    &quot;*.ts?(x)&quot;: [&quot;pnpm prettier --write&quot;],
    &quot;*.js?(x)&quot;: [&quot;pnpm prettier --write&quot;],
    &quot;*.css&quot;: [&quot;pnpm prettier --write&quot;]
  }
}
</code></pre>
<h3 id="quality-gates">Quality Gates</h3>
<ol>
<li><strong>Pre-commit</strong>: Automatically format changed files</li>
<li><strong>CI Pipeline</strong>: Lint and typecheck all affected packages</li>
<li><strong>PR Requirements</strong>: All checks must pass before merge</li>
</ol>
<h2 id="git-workflow-commit-standards">Git Workflow &amp; Commit Standards</h2>
<h3 id="conventional-commits-with-commitlint">Conventional Commits with Commitlint</h3>
<p>We enforce conventional commits to enable:</p>
<ul>
<li><strong>Automated Versioning</strong>: Semantic versioning from commit messages</li>
<li><strong>Generated Changelogs</strong>: Automatic release notes</li>
<li><strong>Clear History</strong>: Structured commit messages</li>
</ul>
<p><strong>commitlint.config.ts</strong>:</p>
<pre><code class="language-typescript">export default {
  extends: [&apos;@commitlint/config-conventional&apos;],
  rules: {
    &apos;scope-enum&apos;: [
      2,
      &apos;always&apos;,
      [
        &apos;main-app&apos;,
        &apos;auth-service&apos;,
        &apos;admin-panel&apos;,
        &apos;analytics&apos;,
        &apos;ui-components&apos;,
        &apos;data-layer&apos;,
        &apos;e2e-tests&apos;,
        &apos;mobile-app&apos;,
        &apos;utilities&apos;,
        &apos;deps&apos;,
        &apos;ci&apos;,
        &apos;husky&apos;,
      ],
    ],
  },
};
</code></pre>
<h3 id="branch-naming-strategy">Branch Naming Strategy</h3>
<p><strong>validate-branch.sh</strong>:</p>
<pre><code class="language-bash">#!/bin/sh
branch_name=$(git symbolic-ref --short HEAD)
pattern=&quot;^(feat|fix|chore|docs|refactor|release|test)/(main-app|auth-service|admin-panel|mobile-app|e2e-tests|utilities|ui-components|data-layer|analytics|ci|ui|monitoring|app)/[a-z0-9.\-]+$|^release/(main-app|auth-service|admin-panel|mobile-app|utilities|ui-components|data-layer|analytics|ci|ui|monitoring|app)/[0-9]+\.[0-9]+\.[0-9]+$&quot;

if [[ &quot;$branch_name&quot; == &quot;main&quot; ]]; then
  exit 0
fi

if ! echo &quot;$branch_name&quot; | grep -Eq &quot;$pattern&quot;; then
  echo &quot;ERROR: Branch name &apos;$branch_name&apos; does not follow the pattern:&quot;
  echo &quot;       &lt;type&gt;/&lt;scope&gt;/&lt;description&gt;&quot;
  echo &quot;       Examples: feat/main-app/add-login, fix/mobile-app/fix-button&quot;
  exit 1
fi
</code></pre>
<h3 id="husky-git-hooks">Husky Git Hooks</h3>
<p><strong>.husky/pre-commit</strong>:</p>
<pre><code class="language-bash">pnpm dlx lint-staged --verbose
</code></pre>
<p><strong>.husky/commit-msg</strong>:</p>
<pre><code class="language-bash">pnpm commitlint --edit &quot;$1&quot;
</code></pre>
<p><strong>.husky/pre-push</strong>:</p>
<pre><code class="language-bash">sh .husky/validate-branch.sh
</code></pre>
<h2 id="cicd-pipeline-architecture">CI/CD Pipeline Architecture</h2>
<h3 id="multi-stage-pipeline-strategy">Multi-Stage Pipeline Strategy</h3>
<p>Our CI/CD pipeline follows a sophisticated multi-stage approach:</p>
<ol>
<li><strong>Setup Stage</strong>: Cache dependencies and install</li>
<li><strong>Quality Gates</strong>: Lint, typecheck, and test in parallel</li>
<li><strong>Build Stage</strong>: Create production artifacts</li>
<li><strong>Deployment</strong>: Environment-specific deployments</li>
<li><strong>Testing</strong>: End-to-end validation</li>
</ol>
<h3 id="github-actions-workflow">GitHub Actions Workflow</h3>
<p><strong>ci.yml</strong> (Core CI Pipeline):</p>
<pre><code class="language-yaml">name: CI

on:
  pull_request:
    branches: [main]

jobs:
  setup:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v3
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: &apos;pnpm&apos;
      - name: Install dependencies
        run: pnpm install --frozen-lockfile

  lint:
    runs-on: ubuntu-22.04
    needs: setup
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2
      - uses: pnpm/action-setup@v3
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: &apos;pnpm&apos;
      - name: Install dependencies
        run: pnpm install --frozen-lockfile
      - run: pnpm turbo run lint --filter=...[HEAD^1]

  typecheck:
    runs-on: ubuntu-22.04
    needs: setup
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2
      - run: pnpm turbo run typecheck --filter=...[HEAD^1]

  test:
    runs-on: ubuntu-22.04
    needs: setup
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2
      - run: pnpm turbo run test --filter=...[HEAD^1] --filter !e2e_tests -- --coverage

  build:
    runs-on: ubuntu-22.04
    needs: [lint, typecheck, test]
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2
      - run: pnpm turbo run build --filter=...[HEAD^1]
</code></pre>
<h3 id="intelligent-change-detection">Intelligent Change Detection</h3>
<p>The pipeline uses Turbo&apos;s change detection:</p>
<ul>
<li><code>--filter=...[HEAD^1]</code>: Only runs tasks for changed packages</li>
<li>Dependency-aware: Includes packages that depend on changed packages</li>
<li>Efficient: Skips unnecessary work</li>
</ul>
<h3 id="pr-checklist-automation">PR Checklist Automation</h3>
<pre><code class="language-yaml">checklist:
  runs-on: ubuntu-22.04
  steps:
    - name: Check PR checklist
      env:
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        body=$(gh pr view ${{ github.event.pull_request.number }} --json body -q &quot;.body&quot;)
        if [[ $body != *&quot;- [x] Tests added/updated&quot;* ]]; then
          echo &quot;PR checklist not completed: Tests missing&quot;
          exit 1
        fi
        if [[ $body != *&quot;- [x] Lint &amp; Typecheck pass locally&quot;* ]]; then
          echo &quot;PR checklist not completed: Lint/Typecheck missing&quot;
          exit 1
        fi
</code></pre>
<h2 id="testing-strategy-coverage">Testing Strategy &amp; Coverage</h2>
<h3 id="multi-level-testing-approach">Multi-Level Testing Approach</h3>
<p>Our testing strategy includes:</p>
<ol>
<li><strong>Unit Tests</strong>: Component and utility testing with Vitest</li>
<li><strong>Integration Tests</strong>: API and data layer testing</li>
<li><strong>E2E Tests</strong>: Full user journey validation with Playwright</li>
<li><strong>Visual Regression</strong>: Screenshot comparisons</li>
</ol>
<h3 id="vitest-configuration">Vitest Configuration</h3>
<p><strong>vitest.config.ts</strong>:</p>
<pre><code class="language-typescript">import { defineConfig } from &apos;vitest/config&apos;
import react from &apos;@vitejs/plugin-react&apos;
import tsconfigPaths from &apos;vite-tsconfig-paths&apos;

export default defineConfig({
  plugins: [react(), tsconfigPaths()],
  test: {
    environment: &apos;jsdom&apos;,
    setupFiles: [&apos;__tests__/vitest.setup.ts&apos;],
    coverage: {
      provider: &apos;istanbul&apos;,
      reporter: [&apos;text&apos;, &apos;json&apos;, &apos;html&apos;],
      exclude: [
        &apos;node_modules/&apos;,
        &apos;__tests__/&apos;,
        &apos;**/*.config.*&apos;,
        &apos;**/*.d.ts&apos;,
      ],
    },
  },
})
</code></pre>
<h3 id="test-organization">Test Organization</h3>
<pre><code>apps/main-app/
&#x251C;&#x2500;&#x2500; __tests__/
&#x2502;   &#x251C;&#x2500;&#x2500; components/         # Component tests
&#x2502;   &#x251C;&#x2500;&#x2500; utils/             # Utility tests
&#x2502;   &#x251C;&#x2500;&#x2500; hooks/             # Custom hook tests
&#x2502;   &#x2514;&#x2500;&#x2500; vitest.setup.ts    # Test setup
&#x251C;&#x2500;&#x2500; src/
&#x2502;   &#x251C;&#x2500;&#x2500; components/
&#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; Button.tsx
&#x2502;   &#x2502;   &#x2514;&#x2500;&#x2500; Button.test.tsx
</code></pre>
<h3 id="playwright-e2e-testing">Playwright E2E Testing</h3>
<p><strong>Multi-Browser Testing Matrix</strong>:</p>
<pre><code class="language-yaml">strategy:
  fail-fast: false
  matrix:
    browser: [chrome, firefox, safari, edge]
    shard: [1, 2, 3]
</code></pre>
<p>Benefits:</p>
<ul>
<li><strong>Parallel Execution</strong>: 3 shards per browser</li>
<li><strong>Cross-Browser Coverage</strong>: Ensures compatibility</li>
<li><strong>Fail-Fast Disabled</strong>: All browsers tested even if one fails</li>
</ul>
<h2 id="branch-protection-validation">Branch Protection &amp; Validation</h2>
<h3 id="advanced-branch-protection">Advanced Branch Protection</h3>
<p>Our branch protection strategy includes:</p>
<ol>
<li><strong>Automatic Validation</strong>: Branch names must follow convention</li>
<li><strong>Status Checks</strong>: All CI jobs must pass</li>
<li><strong>Review Requirements</strong>: Code review mandatory</li>
<li><strong>Deployment Validation</strong>: Staging deployment and testing</li>
</ol>
<h3 id="app-specific-validation">App-Specific Validation</h3>
<p><strong>main-app-pr-validation.yml</strong> demonstrates sophisticated validation:</p>
<pre><code class="language-yaml">check-changes:
  runs-on: ubuntu-latest
  outputs:
    main-app-changed: ${{ steps.set-output.outputs.main-app-changed }}
  steps:
    - uses: dorny/paths-filter@v3
      id: changes
      with:
        base: main
        filters: |
          main-app:
            - &apos;apps/main-app/**&apos;
            - &apos;packages/ui-components/**&apos;
            - &apos;packages/data-layer/**&apos;
            - &apos;packages/utilities/**&apos;
</code></pre>
<h3 id="conditional-deployments">Conditional Deployments</h3>
<p>Only deploy and test when relevant files change:</p>
<ul>
<li><strong>Path-based Filtering</strong>: Detect changes in app or dependencies</li>
<li><strong>Conditional Jobs</strong>: Skip unnecessary work</li>
<li><strong>Resource Optimization</strong>: Don&apos;t waste CI/CD resources</li>
</ul>
<h2 id="deployment-strategies">Deployment Strategies</h2>
<h3 id="containerized-deployments">Containerized Deployments</h3>
<p><strong>Docker Strategy</strong>:</p>
<pre><code class="language-dockerfile"># Multi-stage build for optimal image size
FROM node:20-alpine AS base
RUN corepack enable

FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs
EXPOSE 3000
ENV PORT 3000

CMD [&quot;node&quot;, &quot;server.js&quot;]
</code></pre>
<h3 id="container-registry-integration-image-management">Container Registry Integration &amp; Image Management</h3>
<p><strong>Automated Image Building</strong>:</p>
<pre><code class="language-yaml">- name: Build and push Docker image
  env:
    REGISTRY: ${{ steps.login-registry.outputs.registry }}
    IMAGE_TAG: ${{ steps.generate-tag.outputs.tag }}
  run: |
    FULL_IMAGE_URI=&quot;$REGISTRY/$IMAGE_NAME:$IMAGE_TAG&quot;
    docker build -f apps/main-app/Dockerfile -t $FULL_IMAGE_URI --no-cache .
    docker push $FULL_IMAGE_URI
</code></pre>
<h3 id="zero-downtime-deployments">Zero-Downtime Deployments</h3>
<p><strong>Rolling Updates</strong>:</p>
<pre><code class="language-yaml">- name: Deploy Application on Staging
  run: |
    # Replace the image with the exact one we just built
    CURRENT_IMAGE=$(grep &apos;image:&apos; docker-compose.yml | awk &apos;{print $2}&apos;)
    NEW_IMAGE=&quot;$FULL_IMAGE_URI&quot;

    sed -i &quot;s|^ *image:.*|    image: $NEW_IMAGE|&quot; docker-compose.yml

    docker compose pull
    docker compose down
    docker compose up -d
</code></pre>
<h2 id="best-practices-lessons-learned">Best Practices &amp; Lessons Learned</h2>
<h3 id="monorepo-management">Monorepo Management</h3>
<p><strong>Do&apos;s</strong>:</p>
<ul>
<li>&#x2705; Use workspace protocols for internal dependencies</li>
<li>&#x2705; Maintain clear package boundaries</li>
<li>&#x2705; Implement consistent build and test patterns</li>
<li>&#x2705; Use project references for TypeScript</li>
<li>&#x2705; Cache everything (builds, tests, linting)</li>
</ul>
<p><strong>Don&apos;ts</strong>:</p>
<ul>
<li>&#x274C; Create circular dependencies between packages</li>
<li>&#x274C; Mix unrelated concerns in shared packages</li>
<li>&#x274C; Skip dependency declarations</li>
<li>&#x274C; Ignore build order dependencies</li>
</ul>
<h3 id="cicd-optimization">CI/CD Optimization</h3>
<p><strong>Performance Tips</strong>:</p>
<ol>
<li><strong>Parallel Jobs</strong>: Run independent tasks simultaneously</li>
<li><strong>Smart Caching</strong>: Cache node_modules, build artifacts, and Docker layers</li>
<li><strong>Change Detection</strong>: Only run tasks for affected packages</li>
<li><strong>Resource Management</strong>: Use appropriate runner sizes</li>
<li><strong>Fail Fast</strong>: Stop early when possible, continue when valuable</li>
</ol>
<h3 id="code-quality-enforcement">Code Quality Enforcement</h3>
<p><strong>Automation Strategy</strong>:</p>
<ul>
<li><strong>Pre-commit Hooks</strong>: Catch issues before commit</li>
<li><strong>CI Validation</strong>: Comprehensive checks on all changes</li>
<li><strong>PR Requirements</strong>: Enforce standards before merge</li>
<li><strong>Automated Fixes</strong>: Auto-format and auto-fix when possible</li>
</ul>
<h3 id="team-collaboration">Team Collaboration</h3>
<p><strong>Documentation</strong>:</p>
<ul>
<li>Clear README files in each package</li>
<li>Conventional commit messages</li>
<li>PR templates with checklists</li>
<li>Architecture decision records (ADRs)</li>
</ul>
<p><strong>Developer Experience</strong>:</p>
<ul>
<li>Fast feedback loops</li>
<li>Clear error messages</li>
<li>Consistent tooling across packages</li>
<li>Automated setup and configuration</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Building enterprise-grade frontend applications requires more than just writing good code. It demands a comprehensive approach to:</p>
<ul>
<li><strong>Architecture</strong>: Monorepo structure with clear boundaries</li>
<li><strong>Build System</strong>: Fast, reliable, and cached builds</li>
<li><strong>Code Quality</strong>: Automated linting, formatting, and testing</li>
<li><strong>CI/CD</strong>: Sophisticated pipelines with smart optimizations</li>
<li><strong>Team Workflow</strong>: Clear processes and automated enforcement</li>
</ul>
<p>The investment in this infrastructure pays dividends through:</p>
<ul>
<li><strong>Faster Development</strong>: Reduced friction and faster feedback</li>
<li><strong>Higher Quality</strong>: Automated quality gates and consistency</li>
<li><strong>Better Collaboration</strong>: Clear processes and shared tooling</li>
<li><strong>Easier Maintenance</strong>: Consistent patterns and good documentation</li>
</ul>
<p>As your team grows and your application scales, these practices become not just helpful, but essential for maintaining velocity and quality.</p>
<p>Remember: the goal isn&apos;t to adopt every tool and practice, but to thoughtfully select and implement the ones that solve real problems for your team and application. Start with the basics, measure the impact, and evolve your toolchain based on actual needs and constraints.</p>
<hr>
<p><em>This article is based on my real-world experience building and maintaining enterprise frontend applications. The specific tooling choices and configurations represent battle-tested solutions that have proven effective in production environments.</em></p>
]]></content:encoded></item><item><title><![CDATA[Handle Your API Responses Like a Pro in Django]]></title><description><![CDATA[<h3 id="introduction">Introduction</h3><p>When building APIs in Django, consistency in responses is key. A well-structured response format simplifies how the frontend handles API results, leading to a <strong>better developer experience</strong> and a <strong>smoother user experience</strong>.</p><p>A common approach is to structure API responses as:</p><pre><code class="language-json">{
  &quot;data&quot;: { /* Response Data as an object</code></pre>]]></description><link>https://codewithgillis.com/handle-your-api-response-like-a-pro/</link><guid isPermaLink="false">66d25579407c340001e8799e</guid><category><![CDATA[django]]></category><category><![CDATA[djangorestframework]]></category><category><![CDATA[api]]></category><dc:creator><![CDATA[Sone GIllis]]></dc:creator><pubDate>Tue, 25 Mar 2025 03:28:59 GMT</pubDate><media:content url="https://codewithgillis.com/content/images/2025/03/create-api-endpoints-like-a-pro-in-django.webp" medium="image"/><content:encoded><![CDATA[<h3 id="introduction">Introduction</h3><img src="https://codewithgillis.com/content/images/2025/03/create-api-endpoints-like-a-pro-in-django.webp" alt="Handle Your API Responses Like a Pro in Django"><p>When building APIs in Django, consistency in responses is key. A well-structured response format simplifies how the frontend handles API results, leading to a <strong>better developer experience</strong> and a <strong>smoother user experience</strong>.</p><p>A common approach is to structure API responses as:</p><pre><code class="language-json">{
  &quot;data&quot;: { /* Response Data as an object or list of objects and null if error */ },
  &quot;erc&quot;: 1,  // 1 for success, other numbers for errors
  &quot;msg&quot;: &quot;Associated message&quot;
}
</code></pre><h3 id="why-is-this-important">Why Is This Important?</h3><p>&#x2705; <strong>Predictability</strong>: The frontend can rely on a consistent format, reducing conditional checks.<br>&#x2705; <strong>Standardized Error Handling</strong>: The UI can use the <code>erc</code> value to determine the type of error without parsing complex error structures.<br>&#x2705; <strong>Improved Debugging</strong>: Logs and API responses are uniform, making debugging easier.<br>&#x2705; <strong>Scalability</strong>: If multiple teams work on the project, everyone follows a standard API response format.</p><h3 id="implementing-a-middleware-to-standardize-api-responses">Implementing a Middleware to Standardize API Responses</h3><p>Instead of manually structuring responses in every view, we can use a <strong>Django middleware</strong> to <strong>automatically format all responses</strong>.</p><p><strong>Step 1: Create the Middleware</strong></p><pre><code class="language-python">class ApiResponseMiddleware:
    def __init__(self, get_response=None):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        return response

    def process_template_response(self, request, response):
        if hasattr(response, &quot;data&quot;) and not request.path.startswith(&quot;/api/schema&quot;):
            # add conditional checks above to ignore paths that have data in the response object but are not part of your api spec
            if response.data and &apos;non_field_errors&apos; in response.data:
                response.data = {
                    &apos;erc&apos;: response.data[&quot;non_field_errors&quot;][0].code,
                    &apos;msg&apos;: response.data[&quot;non_field_errors&quot;][0]
                }
            elif response.data and response.status_code == 400:
                print(response.data);
                response.data = {
                    &apos;erc&apos;: 0,
                    &apos;msg&apos;: {key: &apos;&apos;.join([str(error_str) for error_str in value]) for key, value in response.data.items()}
                }
            elif response.data and response.status_code &gt; 400:
                print(response.data)
                response.data = {
                    &apos;erc&apos;: 0,
                    &apos;msg&apos;: response.data.get(&apos;detail&apos;)
                }
            elif response.data and &apos;count&apos; in response.data:
                response.data = {
                    &quot;erc&quot;: 1,
                    &quot;msg&quot;: &quot;success&quot;,
                    &quot;total&quot;: response.data.get(&apos;count&apos;, 1),
                    &quot;next&quot;: response.data.get(&apos;next&apos;),
                    &quot;data&quot;: response.data.get(&apos;results&apos;) if &apos;results&apos; in response.data else response.data
                }
            else:
                response.data = {
                    &quot;erc&quot;: 1,
                    &quot;msg&quot;: &quot;success&quot;,
                    &quot;data&quot;: response.data
                }
        return response
</code></pre><p><strong>Step 2: Using an <code>ErrorCode</code> Enum for Named Errors</strong></p><p>To make error handling even more structured, we can use an <strong><code>ErrorCode</code> enum</strong>. This allows us to define named error codes with meaningful messages, making it easy to raise and handle errors concisely.</p><h3 id="defining-the-errorcode-enum">Defining the <code>ErrorCode</code> Enum</h3><h3 id></h3><pre><code class="language-python">from enum import Enum
from django.utils.translation import gettext_lazy as _

class ErrorCode(Enum):
    INVALID_INPUT = (1001, _(&apos;Invalid input&apos;))
    AUTHENTICATION_FAILED = (1002, _(&apos;Authentication failed&apos;))
    PERMISSION_DENIED = (1003, _(&apos;Permission denied&apos;))
    RESOURCE_NOT_FOUND = (1004, _(&apos;Requested resource not found&apos;))
    SERVER_ERROR = (1005, _(&apos;Internal server error&apos;))

    def __init__(self, code: int, message: str):
        self.code = code
        self.message = message</code></pre><p><strong>How This Helps?</strong></p><p>&#x2705; <strong>Clear, Named Errors</strong> &#x2192; Instead of returning raw numbers, we have readable, maintainable error codes.<br>&#x2705; <strong>Standardized Error Responses</strong> &#x2192; No need to manually define error messages in every view.<br>&#x2705; <strong>Translation Support</strong> &#x2192; <code>gettext_lazy</code> enables internationalization for error messages.</p><p><strong>Step 3: Using the <code>ErrorCode</code> Enum in API Responses</strong></p><p>With our middleware in place, Django views no longer need to manually format responses. The middleware will automatically wrap them in the <code>{data, erc, msg}</code> structure.</p><p>The only responsibility of the views is to:<br>&#x2705; <strong>Return data normally</strong> for successful requests.<br>&#x2705; <strong>Raise structured errors</strong> using <code>ErrorCode</code> for failures.</p><p><strong>Example Usage in a View</strong></p><pre><code class="language-python">from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError
from .error_codes import ErrorCode

class SampleView(APIView):
    def get(self, request):
        if &quot;invalid_param&quot; in request.query_params:
            raise ValidationError(ErrorCode.INVALID_INPUT.message, ErrorCode.INVALID_INPUT.code)  # Example of a validation error

        return Response({&quot;message&quot;: &quot;Welcome!&quot;})  # Middleware will wrap this</code></pre><p><strong>How the Middleware Handles Responses</strong></p><p><strong>For successful responses</strong>, the middleware automatically wraps the returned data:</p><p>View Returns:</p><pre><code class="language-json">{&quot;message&quot;: &quot;Welcome!&quot;}</code></pre><p>Middleware Converts to:</p><pre><code class="language-json">{
  &quot;data&quot;: { &quot;message&quot;: &quot;Welcome!&quot; },
  &quot;erc&quot;: 1,
  &quot;msg&quot;: &quot;Success&quot;
}</code></pre><p>For errors, the middleware extracts the <code>ErrorCode</code> and formats the response:</p><p>View Raises:</p><pre><code class="language-python">raise ValidationError(ErrorCode.INVALID_INPUT.message, ErrorCode.INVALID_INPUT.code)</code></pre><p>Middleware Converts to:</p><pre><code class="language-json">{
  &quot;data&quot;: null,
  &quot;erc&quot;: 1001,
  &quot;msg&quot;: &quot;Invalid input&quot;
}</code></pre><p><strong>Step 4: Activating the Middleware</strong></p><p>Once the middleware is ready, add it to Django&#x2019;s <code>MIDDLEWARE</code> list in <code>settings.py</code>:</p><pre><code class="language-python">MIDDLEWARE = [
    ...
    &apos;path.to.your.ApiResponseMiddleware&apos;,  # Add this line
    ...
]</code></pre><h3 id="final-thoughts">Final Thoughts</h3><p>By implementing <strong>structured API responses and an <code>ErrorCode</code> enum</strong>, you ensure:<br>&#x2705; <strong>Consistent and readable responses</strong> for frontend developers<br>&#x2705; <strong>Predictable error handling</strong> with predefined error codes<br>&#x2705; <strong>Scalable and maintainable APIs</strong></p><p>With this approach, you&#x2019;ll handle API responses <strong>like a pro</strong> in Django! &#x1F680;</p>]]></content:encoded></item><item><title><![CDATA[Common terms used in Image Processing (Part 1)]]></title><description><![CDATA[<h3 id="image-formation">Image Formation</h3><p>Involves the fall of light on an object which gets reflected to an image source like your eyes or a camera having a lens where the image is formed.</p><h3 id="bayer-mosaic">Bayer Mosaic</h3><p>A sensor array of filters containing Red, Green and Blue filters. Light coming in from an object</p>]]></description><link>https://codewithgillis.com/common-terms-in-image-processing/</link><guid isPermaLink="false">67dfabfbd3159e0001079c04</guid><category><![CDATA[computervision]]></category><category><![CDATA[imageprocessing]]></category><dc:creator><![CDATA[Sone GIllis]]></dc:creator><pubDate>Sun, 23 Mar 2025 08:22:17 GMT</pubDate><media:content url="https://codewithgillis.com/content/images/2025/03/common-terms-in-image-processing.webp" medium="image"/><content:encoded><![CDATA[<h3 id="image-formation">Image Formation</h3><img src="https://codewithgillis.com/content/images/2025/03/common-terms-in-image-processing.webp" alt="Common terms used in Image Processing (Part 1)"><p>Involves the fall of light on an object which gets reflected to an image source like your eyes or a camera having a lens where the image is formed.</p><h3 id="bayer-mosaic">Bayer Mosaic</h3><p>A sensor array of filters containing Red, Green and Blue filters. Light coming in from an object contains Red, Green and Blue components that get filtered by these individual filters (R filter allows only the Red component to go through, G filter allows only the Green component to go through and B filter allows only the Blue component to go through) with Green containing 50% of the content, Red containing 25% and Blue containing 25%. </p><p>The choice of this design is based on how the human eye responses to light. The human visual system is most sensitive to Green wavelengths. Green also contributes most to luminance (brightness perception), therefore capturing more green data leads to more detailed and fine images with less noise.</p><h3 id="bits-per-pixel-bpp">Bits per pixel (Bpp)</h3><p>Refers to the amount of bits of information stored in each pixel in an image. It also referred to as Color depth because it determines the number of bits used to represent each single pixel in an image. For example, 2bits could only represent 2 colors (Black &amp; White) and 8 bits could represent 256 colors.</p><h3 id="brightness">Brightness</h3><p>Brightness is a relative term, that compares the intensities of each pixel in an image. Given 3 pixels, A, B and C, each with intensities 20, 100, and 250 respectively, we can deduce that the B pixel is brighter than the A pixel, but not as bright as the C pixel.</p><h3 id="contrast">Contrast</h3><p>This is the difference between the maximum and the minimum pixel intensities in an image. Given an image A, with pixel intensities ranging from 180 to 255, and another image B, with pixel intensities ranging from 20 to 200, we&apos;ll conclude that image B has more contrast than A because image A has a 180 difference in intensity compared to the 75 in image A.</p><h3 id="resolution">Resolution</h3><p>It is quantity factor referring to the total number of pixels in an image. For example HD (1280 x 720) resolutions containing 1280 pixels horizontally and 720 pixels vertically giving it a total of 921,600 pixels. It is important to note that this is just a measure and on it&apos;s own doesn&apos;t guarantee quality in the image.</p><p><strong>Pixel Density</strong></p><p>This refers to the number of pixels in an inch of a display. It also indicates how close pixels are to each other in a display represented as Dot Per Inch (DPI). The sharpness of an image will be better with higher pixel densities</p><h3 id="color-models">Color Models</h3><p>A system that makes use of 3 primary colors (RGB) to produce a vast range of colors. Color models include (RGB, HSV, YUV)</p><h3 id="hsv">HSV</h3><p>This is a color model that signifies Hue, Saturation and Value (Brightness). Hue represents the color in the pixel measured in degree ranging from 0 to 360 deg. Saturation is directly related to intensity and it is measured in percentage ranging from 0 to 100. A high saturation means high intensity of the color present. Value also referred to as brightness indicates the brightness of the color ranging from 0 to 100 measured in percentage (o represents black and 100 represents the brightest)</p><h3 id="luminance-and-chrominance">Luminance and Chrominance</h3><p>Luminance usually refers to the brightness whereas chrominance refers to the color. It is usually indicated in the YUV color model with Y interpreted as the luminance (brightness) and U,V interpreted as the chrominance</p><h3 id="chroma-subsampling">Chroma Subsampling</h3><p>This refers to the lessening of the color resolution of video signals thereby saving bandwidth. Chroma is also known as the Color component information. We could lessen this component by sampling at a lower rate than the brightness or luminance</p>]]></content:encoded></item><item><title><![CDATA[Things you should learn before starting React]]></title><description><![CDATA[<h2 id="arrow-functions"><strong>Arrow Functions</strong></h2><p>Arrow functions provide a concise way to write function expressions which are excellent for handling common tasks such as defining event handlers and creating function components</p><pre><code class="language-js">const sum = (a, b) =&gt; a + b; 
console.log(&apos;The sum of 3 and 5 is &apos;, sum(3, 5));</code></pre><h2 id="map-and-filter"><strong>Map</strong></h2>]]></description><link>https://codewithgillis.com/things-you-should-learn-before-starting-react/</link><guid isPermaLink="false">67500fe3407c340001e879ab</guid><dc:creator><![CDATA[Sone GIllis]]></dc:creator><pubDate>Sun, 23 Feb 2025 06:52:33 GMT</pubDate><media:content url="https://codewithgillis.com/content/images/2025/02/DALL-E-2025-02-23-07.51.46---A-visually-engaging-digital-illustration-representing-JavaScript-concepts-essential-for-learning-React.-The-image-includes-symbols-for-map--filter--sl.webp" medium="image"/><content:encoded><![CDATA[<h2 id="arrow-functions"><strong>Arrow Functions</strong></h2><img src="https://codewithgillis.com/content/images/2025/02/DALL-E-2025-02-23-07.51.46---A-visually-engaging-digital-illustration-representing-JavaScript-concepts-essential-for-learning-React.-The-image-includes-symbols-for-map--filter--sl.webp" alt="Things you should learn before starting React"><p>Arrow functions provide a concise way to write function expressions which are excellent for handling common tasks such as defining event handlers and creating function components</p><pre><code class="language-js">const sum = (a, b) =&gt; a + b; 
console.log(&apos;The sum of 3 and 5 is &apos;, sum(3, 5));</code></pre><h2 id="map-and-filter"><strong>Map and Filter</strong></h2><p>Map creates a new array by transforming each element, while Filter creates a new array with elements that pass a test.</p><p>Map provides a concise and declarative to render html content and components in react from a list of data. Filter on the other hands removes unwanted data from a list</p><pre><code class="language-js">const numbers = [1, 2, 3, 4, 5, 6, 7]
const squares = numbers.map(el =&gt; el * el) // use of map to get squares of numbers
const even = numbers.filter(el =&gt; el % 2 === 0) // use of filter to get even numbers</code></pre><h2 id="slice-and-splice"><strong>Slice and Splice</strong></h2><h3 id="slice"><strong>Slice</strong></h3><p>Slice returns a shallow copy of a portion of an array without modifying the original array. It does not modify the contents of the original array. It returns a new array</p><pre><code class="language-javascript">const numbers = [1, 2, 3, 4, 5];
const slicedNumbers = numbers.slice(1, 4); // Extracts elements from index 1 to 3 (4 is not included)

console.log(slicedNumbers); // Output: [2, 3, 4]
console.log(numbers); // Output: [1, 2, 3, 4, 5] (Original array remains unchanged)</code></pre><h3 id="splice">      <strong>Splice </strong></h3><p>Splice modifies the original array by adding, removing or replacing elements.</p><pre><code class="language-javascript">const colors = [&quot;red&quot;, &quot;blue&quot;, &quot;green&quot;, &quot;yellow&quot;];
const removedColors = colors.splice(1, 2); // Removes 2 elements starting from index 1

console.log(removedColors); // Output: [&quot;blue&quot;, &quot;green&quot;]
console.log(colors); // Output: [&quot;red&quot;, &quot;yellow&quot;] (Original array is modified)

const fruits = [&quot;apple&quot;, &quot;banana&quot;, &quot;grape&quot;];
fruits.splice(1, 0, &quot;orange&quot;, &quot;mango&quot;); // Inserts at index 1 without deleting anything

console.log(fruits); // Output: [&quot;apple&quot;, &quot;orange&quot;, &quot;mango&quot;, &quot;banana&quot;, &quot;grape&quot;]</code></pre><p>React&apos;s State Management Requires Immutability. In React, we should not modify state directly; instead, we create a new copy and update it. <code>slice()</code> is useful for creating copies of state data, whereas <code>splice()</code> modifies arrays in place (which is discouraged in React state updates).</p><p><strong>Example: Bad Practice (Mutating State in React)</strong></p><pre><code class="language-javascript">const [items, setItems] = useState([&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;]);

const removeItem = () =&gt; {
    items.splice(1, 1); // Mutates the state directly
    setItems(items); // React may not re-render properly
};</code></pre><p>Example: Good Practice (Using slice)</p><pre><code class="language-javascript">const [items, setItems] = useState([&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;]);

const removeItem = () =&gt; {
    setItems(items.slice(0, 1).concat(items.slice(2))); // Creates a new array
};</code></pre><p>React&apos;s reconciliation process benefits from immutable data updates. Methods like <code>slice()</code> help create new state objects without modifying the previous one, allowing React to detect changes efficiently.</p><h2 id="destructuring">Destructuring</h2><h3 id="array-destructuring">Array Destructuring</h3><p>Allows you extract elements from an array and assign to variables</p><pre><code class="language-javascript">// Basic Array Destructing

const numbers = [10, 20, 30];

// Extract values into separate variables
const [first, second, third] = numbers;

console.log(first); // Output: 10
console.log(second); // Output: 20
console.log(third); // Output: 30


// Skipping Elements

const colors = [&quot;red&quot;, &quot;blue&quot;, &quot;green&quot;, &quot;yellow&quot;];

// Skip the second element
const [firstColor, , thirdColor] = colors;

console.log(firstColor); // Output: &quot;red&quot;
console.log(thirdColor); // Output: &quot;green&quot;


// Rest Operator

const fruits = [&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;, &quot;date&quot;];
const [firstFruit, ...restFruits] = fruits;

console.log(firstFruit); // Output: &quot;apple&quot;
console.log(restFruits); // Output: [&quot;banana&quot;, &quot;cherry&quot;, &quot;date&quot;]</code></pre><h3 id="object-destructuring">Object Destructuring</h3><p>Used to extract values from objects and assign to variables</p><pre><code class="language-javascript">// Basic object destructuring

const person = { name: &quot;Gillis&quot;, age: 29, country: &quot;Cameroon&quot; };

const { name, age, country } = person;

console.log(name); // Output: &quot;Gillis&quot;
console.log(age); // Output: 29
console.log(country); // Output: &quot;Cameroon&quot;

// Renaming variables

const user = { username: &quot;gillis&quot;, email: &quot;me@codewithgillis.com&quot; };

const { username: userName, email: userEmail } = user;

console.log(userName); // Output: &quot;gillis&quot;
console.log(userEmail); // Output: &quot;me@codewithgillis.com&quot;


// Setting default values

const product = { title: &quot;Laptop&quot;, price: 1000 };

const { title, price, stock = &quot;Out of Stock&quot; } = product;

console.log(stock); // Output: &quot;Out of Stock&quot;
</code></pre><h3 id="destructuring-in-function-parameters">Destructuring in Function Parameters</h3><p>It can also be used directly in function parameters</p><pre><code class="language-javascript">function displayUser({ name, age }) {
    console.log(`User: ${name}, Age: ${age}`);
}

const user = { name: &quot;Alice&quot;, age: 30, location: &quot;New York&quot; };
displayUser(user); // Output: User: Alice, Age: 30</code></pre><p>The above concepts are widely used in react when working with props and states in function components</p><pre><code class="language-react">function UserProfile({ name, age, location }) {
    const [visits, setVisits] = useState(0); // Destructuring useState
    return (
        &lt;div&gt;
            &lt;h2&gt;{name}&lt;/h2&gt;
            &lt;p&gt;Age: {age}&lt;/p&gt;
            &lt;p&gt;Location: {location}&lt;/p&gt;
            &lt;p&gt;Visits: {visits}&lt;/p&gt;
        &lt;/div&gt;
    );
}

// Usage
&lt;UserProfile name=&quot;Gillis&quot; age={29} location=&quot;Cameroon&quot; /&gt;;</code></pre><h3 id="template-literals">Template Literals</h3><p>Template literals (also called template strings) are a feature in JavaScript that allow you to create strings more efficiently using backticks <code>`</code>, instead of regular quotes (<code>&apos;</code> or <code>&quot;</code>). They also enable <strong>string interpolation</strong>, multiline strings, and embedded expressions.</p><pre><code class="language-javascript">const name = &quot;John&quot;;
const greeting = `Hello, ${name}!`;

console.log(greeting); // Output: Hello, John!</code></pre><p>In react, we use template literals extensively in several cases</p><ol><li>Easier JSX and String Handling</li></ol><pre><code class="language-react">const name = &quot;React&quot;;
return &lt;h1&gt;{`Welcome to ${name}!`}&lt;/h1&gt;;
</code></pre><ol start="2"><li>Dynamic Class Names in JSX</li></ol><pre><code class="language-react">const isActive = true;
return &lt;div className={`button ${isActive ? &quot;active&quot; : &quot;inactive&quot;}`}&gt;Click me&lt;/div&gt;;
</code></pre><ol start="3"><li>Improved Readability in Styled Components</li></ol><pre><code class="language-react">const StyledButton = styled.button`
    background: ${props =&gt; (props.primary ? &quot;blue&quot; : &quot;gray&quot;)};
    color: white;
`;
</code></pre><ol start="4"><li>Simplifying API Calls and URLs</li></ol><pre><code class="language-javascript">const userId = 123;
fetch(`https://api.example.com/users/${userId}`)
    .then(response =&gt; response.json())
    .then(data =&gt; console.log(data));
</code></pre><h2 id="conclusion">Conclusion</h2><p>Learning these JavaScript features before React ensures:<br>&#x2705; <strong>Better state &amp; prop management</strong> (Destructuring)<br>&#x2705; <strong>Simpler dynamic UI updates</strong> (<code>map</code>, <code>filter</code>, <code>slice</code>, <code>splice</code>)<br>&#x2705; <strong>Efficient styling &amp; string manipulation</strong> (Template Literals)<br>&#x2705; <strong>More concise and readable functions</strong> (Arrow Functions)<br>&#x2705; <strong>Clean &amp; readable React code</strong></p><p>By mastering these, transitioning to React will be <strong>much smoother and more intuitive</strong>! &#x1F680;</p>]]></content:encoded></item><item><title><![CDATA[Build An Interactive NoteBook application Using Django, React and tailwind]]></title><description><![CDATA[<p>In this article we&apos;ll be building a responsive notebook application with Django, React and Tailwind CSS. The design inspiration is gotten from <a href="https://dribbble.com/shots/23443018--Concepts-Notes-Widgets?ref=codewithgillis.com" rel="noopener">dribble</a> </p><p>Let&apos;s have a preview of our notebook application on mobile and desktop</p><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/06/image.png" class="kg-image" alt loading="lazy" width="1852" height="933" srcset="https://codewithgillis.com/content/images/size/w600/2024/06/image.png 600w, https://codewithgillis.com/content/images/size/w1000/2024/06/image.png 1000w, https://codewithgillis.com/content/images/size/w1600/2024/06/image.png 1600w, https://codewithgillis.com/content/images/2024/06/image.png 1852w" sizes="(min-width: 720px) 720px"></figure><h2 id="backend">Backend</h2><p><strong>Create the django project</strong></p><pre><code class="language-shell">django-admin startproject notebook
python manage.py</code></pre>]]></description><link>https://codewithgillis.com/build-a-interactive-notebook-application-using-django-react-and-tailwind/</link><guid isPermaLink="false">6665ccab407c340001e878c4</guid><dc:creator><![CDATA[Sone GIllis]]></dc:creator><pubDate>Tue, 11 Jun 2024 10:08:54 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1517694712202-14dd9538aa97?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fE5vdGVib29rJTIwQ29kZXxlbnwwfHx8fDE3MTgxMDA4NzF8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1517694712202-14dd9538aa97?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fE5vdGVib29rJTIwQ29kZXxlbnwwfHx8fDE3MTgxMDA4NzF8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Build An Interactive NoteBook application Using Django, React and tailwind"><p>In this article we&apos;ll be building a responsive notebook application with Django, React and Tailwind CSS. The design inspiration is gotten from <a href="https://dribbble.com/shots/23443018--Concepts-Notes-Widgets?ref=codewithgillis.com" rel="noopener">dribble</a> </p><p>Let&apos;s have a preview of our notebook application on mobile and desktop</p><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/06/image.png" class="kg-image" alt="Build An Interactive NoteBook application Using Django, React and tailwind" loading="lazy" width="1852" height="933" srcset="https://codewithgillis.com/content/images/size/w600/2024/06/image.png 600w, https://codewithgillis.com/content/images/size/w1000/2024/06/image.png 1000w, https://codewithgillis.com/content/images/size/w1600/2024/06/image.png 1600w, https://codewithgillis.com/content/images/2024/06/image.png 1852w" sizes="(min-width: 720px) 720px"></figure><h2 id="backend">Backend</h2><p><strong>Create the django project</strong></p><pre><code class="language-shell">django-admin startproject notebook
python manage.py startapp core</code></pre><p><strong>Create the Note model</strong></p><pre><code class="language-python"># core/models.py

from django.db import models
from django.conf import settings


class Note(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    note = models.JSONField()
    date_created = models.DateTimeField(auto_now_add=True)
    date_updated = models.DateTimeField(auto_now=True)</code></pre><p><strong>Create the Note Serializer</strong></p><pre><code class="language-python"># core/serializers.py

from rest_framework.serializers import ModelSerializer
from .models import Note


class NoteSerializer(ModelSerializer):
    class Meta:
        model = Note
        exclude = [&apos;user&apos;]

    def create(self, validated_data):
        return Note.objects.create(user=self.context[&apos;request&apos;].user, **validated_data)
</code></pre><p><strong>Create the View Class</strong></p><pre><code class="language-python"># core/views.py

from rest_framework.generics import ListCreateAPIView, GenericAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.mixins import UpdateModelMixin, DestroyModelMixin

from .searializers import NoteSerializer
from .models import Note


class NoteView(ListCreateAPIView, UpdateModelMixin, DestroyModelMixin):
    serializer_class = NoteSerializer
    queryset = Note.objects.all()
    permission_classes = [IsAuthenticated]

    def get_serializer_context(self):
        return {&apos;request&apos;: self.request}

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)</code></pre><p><strong>Create the URL routes</strong></p><pre><code class="language-python"># notebook/urls.py

from django.contrib import admin
from django.urls import path, include
from core.views import NoteView

urlpatterns = [
    path(&apos;admin/&apos;, admin.site.urls),
    path(&apos;api-auth/&apos;, include(&apos;rest_framework.urls&apos;)),
    path(&apos;api/v1/note&apos;, NoteView.as_view(), name=&apos;note&apos;),
    path(&apos;api/v1/note/&lt;int:pk&gt;&apos;, NoteView.as_view(), name=&apos;note&apos;)
]</code></pre><p>For the sake of this tutorial, we&apos;ll be using session authentication. So to be authenticated, we&apos;ll create a super user via the Django CLI and use it to login to the admin. This way we will establish a session with the backend and we can use that to make authenticated API calls to the backend from the frontend</p><p>Next, we&apos;ll run db migrations</p><pre><code class="language-shell">python manage.py migrate</code></pre><p>Then we create a superuser and login from the admin URL <code>http://localhost:8000</code> to have a session established in the browser so our API calls will be authenticated</p><pre><code class="language-shell"># Create a superuser
python manage.py createsupseruser</code></pre><p>Start the django server</p><pre><code class="language-shell">python manage.py runserver</code></pre><h2 id="frontend">Frontend</h2><p>Now that we have the notebook backend API application, we can go ahead to build the react application</p><p><strong>Create the React app</strong><br></p><pre><code class="language-shell">npx create-react-app notebook</code></pre><p>Install the necessary libraries we&apos;ll be using in this project</p><pre><code class="language-shell">npm install @editorjs/editorjs @editorjs/header react-spinners react-toastify</code></pre><p>Install and initialize tailwind CSS</p><pre><code class="language-shell">npm install -D tailwindcss

npx tailwindcss init</code></pre><p>Copy and paste the following code in the <code>tailwind.config.js</code> file</p><pre><code class="language-javascript">/** @type {import(&apos;tailwindcss&apos;).Config} */
module.exports = {
  content: [&apos;./src/**/*.{js,jsx,ts,tsx}&apos;],
  theme: {
    extend: {
      colors: ({ colors }) =&gt; ({
        primary: {
          100: &apos;#24A0ED&apos;,
          200: &apos;#0086D0&apos;,
        },
        secondary: {
          100: &apos;#E2E2D5&apos;,
          200: &apos;#888883&apos;,
        },
        dark: {
          100: &apos;#343434&apos;,
          200: &apos;#28282B&apos;,
          300: &apos;#1B1212&apos;,
        },
      }),
      fontFamily: {
        body: [&apos;Poppins&apos;],
      },
    },
  },
  plugins: [],
};
</code></pre><p><strong>Create API Utilities</strong></p><pre><code class="language-javascript">// src/api/endpoints.js

export const endpoints = {
  ADD_NOTE: &apos;/api/v1/note&apos;,
  EDIT_NOTE: (id) =&gt; `/api/v1/note/${id}`,
  GET_NOTES: &apos;/api/v1/note&apos;,
};

export const getUrl = (path) =&gt; {
  return process.env.REACT_APP_API_URL + path;
};

# src/api/notes.js

import { endpoints, getUrl } from &apos;./endpoints&apos;;

function getCookie(cName) {
  const name = cName + &apos;=&apos;;
  const cDecoded = decodeURIComponent(document.cookie); //to be careful
  const cArr = cDecoded.split(&apos;; &apos;);
  let res;
  cArr.forEach((val) =&gt; {
    if (val.indexOf(name) === 0) res = val.substring(name.length);
  });
  return res;
}

export function addNote(note, successCb, errorCb) {
  return fetch(getUrl(endpoints.ADD_NOTE), {
    method: &apos;POST&apos;,
    credentials: &apos;include&apos;,
    headers: {
      &apos;Content-Type&apos;: &apos;application/json&apos;,
      &apos;X-CSRFTOKEN&apos;: getCookie(&apos;csrftoken&apos;),
    },
    body: JSON.stringify(note),
  })
    .then(successCb)
    .catch(errorCb);
}

export function editNote(id, note, successCb, errorCb) {
  return fetch(getUrl(endpoints.EDIT_NOTE(id)), {
    method: &apos;PUT&apos;,
    credentials: &apos;include&apos;,
    headers: {
      &apos;Content-Type&apos;: &apos;application/json&apos;,
      &apos;X-CSRFTOKEN&apos;: getCookie(&apos;csrftoken&apos;),
    },
    body: JSON.stringify({
      note,
    }),
  })
    .then(successCb)
    .catch(errorCb);
}

export function deleteNote(id, successCb, errorCb) {
  return fetch(getUrl(endpoints.EDIT_NOTE(id)), {
    method: &apos;DELETE&apos;,
    credentials: &apos;include&apos;,
    headers: {
      &apos;Content-Type&apos;: &apos;application/json&apos;,
      &apos;X-CSRFTOKEN&apos;: getCookie(&apos;csrftoken&apos;),
    },
  })
    .then(successCb)
    .catch(errorCb);
}

export function getNotes(successCb, errorCb) {
  fetch(getUrl(endpoints.GET_NOTES), {
    method: &apos;GET&apos;,
    credentials: &apos;include&apos;,
    headers: {
      &apos;Content-Type&apos;: &apos;application/json&apos;,
    },
  })
    .then(successCb)
    .catch(errorCb);
}
</code></pre><p><strong>Create the Notification Component</strong></p><p>We&apos;ll be using this component to notify the user of the app state when we make API calls. For example when we get success response after creating, editing or deleting a note. </p><p>We&apos;ll wrap the <code>ToastContainer</code> from <code>react-toastify</code> and create wrapper functions that we will use to notify success and error messages</p><pre><code class="language-shell">// src/components/Notification/Notification.jsx

import React from &apos;react&apos;;
import { ToastContainer, toast } from &apos;react-toastify&apos;;
import &apos;react-toastify/dist/ReactToastify.css&apos;;

const Notification = () =&gt; {
  return (
    &lt;ToastContainer
      position=&quot;top-right&quot;
      autoClose={5000}
      hideProgressBar={false}
      newestOnTop={false}
      closeOnClick
      rtl={false}
      pauseOnFocusLoss
      draggable
      pauseOnHover
    /&gt;
  );
};

export const notifySuccess = (message) =&gt; {
  toast.success(message);
};

export const notifyError = (message) =&gt; {
  toast.error(message);
};

export default Notification;
</code></pre><p><strong>Create the Loading Button Component</strong></p><p>We&apos;ll use this component as our submit button that will show a loader when clicked and remove the loader when the async click handler passed to it as prop completes</p><pre><code class="language-javascript">// src/components/LoadingButton/LoadingButton.jsx

import React, { useState } from &apos;react&apos;;
import { ClipLoader } from &apos;react-spinners&apos;;

const LoadingButton = ({ onClick, className, children }) =&gt; {
  const [loading, setLoading] = useState(false);
  const handleClick = async () =&gt; {
    setLoading(true);
    try {
      await onClick();
    } finally {
      setLoading(false);
    }
  };

  return (
    &lt;button onClick={handleClick} className={className} disabled={loading}&gt;
      {loading ? &lt;ClipLoader color=&quot;white&quot; size={20} /&gt; : children}
    &lt;/button&gt;
  );
};

export default LoadingButton;</code></pre><p></p><p><strong>Create the Editor Component</strong></p><p>Here we&apos;ll be using <a href="https://editorjs.io/?ref=codewithgillis.com" rel="noreferrer">editor.js</a> as the editor library for creating, editing and viewing our notes</p><pre><code class="language-javascript">// src/components/Editor/Editor.jsx

import EditorJS from &apos;@editorjs/editorjs&apos;;
import Header from &apos;@editorjs/header&apos;;

import { useEffect, useRef } from &apos;react&apos;;
import LoadingButton from &apos;../LoadingButton/LoadingButton&apos;;
import { notifyError } from &apos;../Notification/Notification&apos;;

// Let the start of new not be a header with &apos;Your title&apos;
const DEFAULT_INITIAL_DATA = {
  time: new Date().getTime(),
  blocks: [
    {
      type: &apos;header&apos;,
      data: {
        text: &apos;Your title&apos;,
        level: 1,
      },
    },
  ],
};

const Editor = ({ handleClose, handleSave, note }) =&gt; {
  const ejInstance = useRef();
  useEffect(() =&gt; {
    if (!ejInstance.current) {
      initEditor();
    }
    return () =&gt; {
      ejInstance?.current?.destroy();
      ejInstance.current = null;
    };
  });

  const saveNote = async () =&gt; {
    const outputData = await ejInstance.current.save();
    if (outputData.blocks.length) {
      if (outputData.blocks[0].type !== &apos;header&apos;) {
        outputData.blocks.unshift({
          type: &apos;header&apos;,
          data: {
            text: &apos;No title&apos;,
            level: 1,
          },
        });
      }
    } else {
      notifyError(&apos;Error!!! Cannot Save, Notes is empty&apos;);
    }
    await handleSave(outputData);
  };

  const initEditor = () =&gt; {
    const editor = new EditorJS({
      holder: &apos;editorjs&apos;,
      onReady: () =&gt; {
        ejInstance.current = editor;
      },
      autofocus: true,
      data: note ? note : DEFAULT_INITIAL_DATA,
      placeholder: &apos;Tell a Story&apos;,
      tools: {
        header: {
          class: Header,
          config: {
            levels: [1, 2, 3, 4],
            defaultLevel: 3,
          },
        },
      },
    });
  };

  return (
    &lt;&gt;
      &lt;div className=&quot;fixed top-0 left-0 w-full h-full bg-dark-300 bg-opacity-70 flex items-center justify-center&quot;&gt;
        &lt;div className=&quot;bg-white w-2/3 h-2/3 relative rounded-sm flex flex-col&quot;&gt;
          &lt;svg
            onClick={handleClose}
            dataslot=&quot;icon&quot;
            fill=&quot;currentColor&quot;
            viewBox=&quot;0 0 16 16&quot;
            xmlns=&quot;http://www.w3.org/2000/svg&quot;
            aria-hidden=&quot;true&quot;
            className=&quot;w-8 cursor-pointer self-end mr-4&quot;
          &gt;
            &lt;path d=&quot;M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z&quot; /&gt;
          &lt;/svg&gt;
          &lt;div className=&quot;overflow-y-scroll w-full h-4/5 mt-2&quot;&gt;
            &lt;div id=&quot;editorjs&quot; className=&quot;mt-10&quot;&gt;&lt;/div&gt;
          &lt;/div&gt;
          &lt;div className=&quot;flex w-full justify-end&quot;&gt;
            &lt;LoadingButton
              onClick={saveNote}
              className={&apos;btn btn-primary text-white mr-12 mt-8&apos;}
            &gt;
              Submit
            &lt;/LoadingButton&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/&gt;
  );
};

export default Editor;
</code></pre><p>From the above, we have a <code>saveNote</code> function that checks to ensure your note has a header. If no header is present, It adds a default header with text <code>No Title</code></p><p><strong>Create the NoteCard Component</strong></p><p>This component will be a card that will show the note header and text. It&apos;ll also present action buttons for editing and deleting the note</p><p>This component expects the note as an object with a header and body. So we create a utility that will parse the note object and return an object with a header and a body that can be used by this component</p><pre><code class="language-javascript">// src/helpers/utils.js

export function getNotesPlainTextFromBlocks(blocks) {
  let header = &apos;&apos;;
  let body = &apos;&apos;;
  if (blocks?.length &gt; 0) {
    if (blocks[0].type === &apos;header&apos;) {
      header = blocks[0].data.text;
    } else {
      body = blocks[0].data.text;
    }
  }
  if (!body &amp;&amp; blocks?.length &gt; 1) {
    body = blocks[1].data.text;
  }
  return {
    header,
    body,
  };
}
</code></pre><p>Then we create the <code>NoteCard</code> component.</p><pre><code class="language-javascript">//src/components/NoteCard/NoteCard.jsx

import { useEffect, useState } from &apos;react&apos;;
import { getNotesPlainTextFromBlocks } from &apos;../../helpers/utils&apos;;

const NoteCard = ({ handleDelete, handleEdit, note, onClick }) =&gt; {
  const [showAction, setShowAction] = useState(false);
  let [formattedNote, setFormattedNote] = useState({
    header: &apos;&apos;,
    body: &apos;&apos;,
  });
  useEffect(() =&gt; {
    setFormattedNote(getNotesPlainTextFromBlocks(note.blocks));
  }, [note]);
  const handleAction = (event, handler) =&gt; {
    event.stopPropagation();
    handler();
  };

  const actionPopOverClickHandler = (event) =&gt; {
    event.stopPropagation();
    setShowAction(!showAction);
  };

  return (
    &lt;div className=&quot;card overflow-visible cursor-pointer&quot; onClick={onClick}&gt;
      &lt;div className=&quot;flex justify-between&quot;&gt;
        &lt;h3
          className={
            &apos;text-white text-lg &apos; + (!formattedNote.header ? &apos;opacity-40&apos; : &apos;&apos;)
          }
        &gt;
          {formattedNote?.header ? formattedNote.header : &apos;No title&apos;}
        &lt;/h3&gt;
        &lt;div
          onClick={actionPopOverClickHandler}
          className=&quot;text-gray-300 cursor-pointer hover:bg-dark-100 hover:rounded-md h-fit relative&quot;
        &gt;
          &lt;svg
            dataslot=&quot;icon&quot;
            fill=&quot;currentColor&quot;
            viewBox=&quot;0 0 16 16&quot;
            xmlns=&quot;http://www.w3.org/2000/svg&quot;
            aria-hidden=&quot;true&quot;
            className=&quot;w-5&quot;
          &gt;
            &lt;path d=&quot;M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z&quot; /&gt;
          &lt;/svg&gt;
          {showAction &amp;&amp; (
            &lt;div className=&quot;absolute top-5 right-0 border border-gray-700 rounded-lg bg-dark-200 p-2 min-w-32&quot;&gt;
              &lt;div
                className=&quot;text-gray-500 flex item-center z-50&quot;
                onClick={(event) =&gt; handleAction(event, handleEdit)}
              &gt;
                &lt;svg
                  dataslot=&quot;icon&quot;
                  fill=&quot;currentColor&quot;
                  viewBox=&quot;0 0 16 16&quot;
                  xmlns=&quot;http://www.w3.org/2000/svg&quot;
                  aria-hidden=&quot;true&quot;
                  className=&quot;w-5&quot;
                &gt;
                  &lt;path
                    clipRule=&quot;evenodd&quot;
                    fillRule=&quot;evenodd&quot;
                    d=&quot;M11.013 2.513a1.75 1.75 0 0 1 2.475 2.474L6.226 12.25a2.751 2.751 0 0 1-.892.596l-2.047.848a.75.75 0 0 1-.98-.98l.848-2.047a2.75 2.75 0 0 1 .596-.892l7.262-7.261Z&quot;
                  /&gt;
                &lt;/svg&gt;
                &lt;span className=&quot;pl-2 text-sm&quot;&gt;Edit&lt;/span&gt;
              &lt;/div&gt;
              &lt;div
                onClick={(event) =&gt; handleAction(event, handleDelete)}
                className=&quot;text-red-500 flex items-center mt-3&quot;
              &gt;
                &lt;svg
                  dataslot=&quot;icon&quot;
                  fill=&quot;currentColor&quot;
                  viewBox=&quot;0 0 16 16&quot;
                  xmlns=&quot;http://www.w3.org/2000/svg&quot;
                  aria-hidden=&quot;true&quot;
                  className=&quot;w-5&quot;
                &gt;
                  &lt;path
                    clipRule=&quot;evenodd&quot;
                    fillRule=&quot;evenodd&quot;
                    d=&quot;M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z&quot;
                  /&gt;
                &lt;/svg&gt;
                &lt;span className=&quot;pl-2 text-sm&quot;&gt;Delete&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          )}
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;
        &lt;p className=&quot;text-gray-300 text-xs mt-3&quot;&gt;{formattedNote.body}&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default NoteCard;
</code></pre><p><strong>Create the Home Page</strong></p><p>Here we get the notes from the backend on page load. We also handle the deleting, editing and deleting actions of the note</p><pre><code class="language-jsx">// src/pages/Home/Home.jsx

import &apos;./Home.css&apos;;
import NoteCard from &apos;../../components/NoteCard/NoteCard&apos;;
import Editor from &apos;../../components/Editor/Editor&apos;;
import { useEffect, useState } from &apos;react&apos;;
import { addNote, editNote, deleteNote, getNotes } from &apos;../../api/notes&apos;;
import Notification, {
  notifyError,
  notifySuccess,
} from &apos;../../components/Notification/Notification&apos;;

const Home = () =&gt; {
  const [showEditor, setShowEditor] = useState(false);
  const [selectedNote, setSelectedNote] = useState();
  const [notes, setNotes] = useState([]);

  useEffect(() =&gt; {
    getNotes(async (response) =&gt; {
      const data = await response.json();
      setNotes(data.results);
    });
  }, []);
  const handleEdit = (note) =&gt; {
    setSelectedNote(note);
    setShowEditor(true);
  };

  const handleDelete = (note) =&gt; {
    deleteNote(
      note.id,
      (_) =&gt; {
        setNotes([...notes.filter((_note) =&gt; _note.id !== note.id)]);
        notifySuccess(`Success!!! Deleted Note ${note.id}`);
      },
      (error) =&gt; notifyError(&apos;Something Went Wrong!!!&apos;)
    );
  };

  const handleNewNote = () =&gt; {
    setSelectedNote(null);
    setShowEditor(true);
  };

  const handleSave = async (data) =&gt; {
    let response;

    if (selectedNote) {
      editNote(
        selectedNote.id,
        data,
        async (response) =&gt; {
          const edittedNote = await response.json();
          const updatedNotes = notes.map((note) =&gt;
            note.id === selectedNote.id ? edittedNote : note
          );
          setNotes([...updatedNotes]);
          notifySuccess(&apos;Success!!! Edited Note successfully&apos;);
        },
        (error) =&gt; notifyError(&apos;Something Went Wrong&apos;)
      ).finally(() =&gt; setShowEditor(false));
    } else {
      addNote({ note: data }, async (response) =&gt; {
        const newNote = await response.json();
        setNotes([...notes, newNote]);
        notifySuccess(&apos;Success!!! Saved Note successfully&apos;);
      }).finally(() =&gt; setShowEditor(false));
    }
    return response;
  };

  return (
    &lt;&gt;
      &lt;Notification /&gt;
      &lt;div className=&quot;font-body bg-dark-100 h-full mx-10 mt-10 md:mx-40 md:mt-40 lg:mx-96&quot;&gt;
        &lt;div className=&quot;flex flex-col md:flex-row md:justify-between text-white&quot;&gt;
          &lt;h2 className=&quot;text-4xl mb-5 md:mb-0&quot;&gt;Notes&lt;/h2&gt;
          &lt;div className=&quot;flex&quot;&gt;
            &lt;button className=&quot;btn btn-primary ml-4&quot;&gt;
              &lt;svg
                dataslot=&quot;icon&quot;
                fill=&quot;currentColor&quot;
                viewBox=&quot;0 0 16 16&quot;
                xmlns=&quot;http://www.w3.org/2000/svg&quot;
                aria-hidden=&quot;true&quot;
                className=&quot;w-5&quot;
              &gt;
                &lt;path d=&quot;M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z&quot; /&gt;
              &lt;/svg&gt;
              &lt;span onClick={handleNewNote} className=&quot;pl-1&quot;&gt;
                Add a new note
              &lt;/span&gt;
            &lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;main className=&quot;mt-8&quot;&gt;
          {notes.map((note) =&gt; (
            &lt;div className=&quot;mb-6&quot; key={note.id}&gt;
              &lt;NoteCard
                note={note.note}
                onClick={() =&gt; handleEdit(note)}
                handleEdit={() =&gt; handleEdit(note)}
                handleDelete={() =&gt; handleDelete(note)}
              &gt;&lt;/NoteCard&gt;
            &lt;/div&gt;
          ))}
          {showEditor &amp;&amp; (
            &lt;Editor
              note={selectedNote?.note}
              handleClose={() =&gt; setShowEditor(false)}
              handleSave={async (note) =&gt; await handleSave(note)}
            &gt;&lt;/Editor&gt;
          )}
        &lt;/main&gt;
      &lt;/div&gt;
    &lt;/&gt;
  );
};

export default Home;
</code></pre><p>Next, run the application</p><pre><code class="language-shell">npm run start</code></pre><p><strong>Improvements</strong></p><ul><li>Add note update date to the NoteCard</li><li>Paginate Notes display in the Home Page</li><li>Include a Sort button to sort the notes by date in the Home Page</li><li>Include Notes Drafts</li></ul><p>You can find the complete source codes on github:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/sonegillis/notebook-backend?ref=codewithgillis.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - sonegillis/notebook-backend: A notebook application API</div><div class="kg-bookmark-description">A notebook application API. Contribute to sonegillis/notebook-backend development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" alt="Build An Interactive NoteBook application Using Django, React and tailwind"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">sonegillis</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/b4d543e88bce7e45fcd5765360c32dd8afccca56d536fdd574d3f88d20686e6d/sonegillis/notebook-backend" alt="Build An Interactive NoteBook application Using Django, React and tailwind"></div></a></figure><p></p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/sonegillis/notebook-frontend?ref=codewithgillis.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - sonegillis/notebook-frontend: Interactive NoteBook application with React and Tailwind CSS</div><div class="kg-bookmark-description">Interactive NoteBook application with React and Tailwind CSS - sonegillis/notebook-frontend</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" alt="Build An Interactive NoteBook application Using Django, React and tailwind"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">sonegillis</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/2cfbf82d83d951e03eed81d2c4e35d5b0c14b55bc5a2ec970d31e81e170f22ca/sonegillis/notebook-frontend" alt="Build An Interactive NoteBook application Using Django, React and tailwind"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Building a Tooltip Component with React Hooks]]></title><description><![CDATA[<p>A tooltip is a message that is positioned relative to an element on a Graphical User Interface. In this project we will be building a controlled tooltip component with React hooks.</p><p>Our tooltip component will by default display the tooltip on hover on the element. We will also be able</p>]]></description><link>https://codewithgillis.com/building-a-tooltip-component-with-react-hooks/</link><guid isPermaLink="false">665baded4eb78e0001892f36</guid><category><![CDATA[react]]></category><category><![CDATA[react hooks]]></category><category><![CDATA[tooltip]]></category><dc:creator><![CDATA[Sone GIllis]]></dc:creator><pubDate>Sat, 01 Jun 2024 23:55:45 GMT</pubDate><media:content url="https://codewithgillis.com/content/images/2024/06/tooltip-project-outline--1-.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://codewithgillis.com/content/images/2024/06/tooltip-project-outline--1-.jpeg" alt="Building a Tooltip Component with React Hooks"><p>A tooltip is a message that is positioned relative to an element on a Graphical User Interface. In this project we will be building a controlled tooltip component with React hooks.</p><p>Our tooltip component will by default display the tooltip on hover on the element. We will also be able to control when we open and close the tooltip, and the direction (<strong>TOP, BOTTOM, LEFT, RIGHT, TOP RIGHT, TOP LEFT, BOTTOM LEFT, BOTTOM RIGHT</strong>) to which we would display the tooltip relative to the element.</p><p>Let&apos;s have a preview of our demo react app showcasing our tooltip component</p>
<!--kg-card-begin: html-->
<div style="position: relative; padding-bottom: 56.25%; height: 0;"><iframe src="https://jumpshare.com/embed/ZgHPYVvqtXaAgYbvq9I9" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></iframe></div>

<!--kg-card-end: html-->
<h3 id="prerequisites"> <strong>Prerequisites:</strong></h3><ul><li><a href="https://www.geeksforgeeks.org/react-tutorial/?ref=codewithgillis.com" rel="noopener">ReactJS</a></li><li><a href="https://www.geeksforgeeks.org/reactjs-hooks/?ref=codewithgillis.com" rel="noopener">React Hooks</a></li></ul><h3 id="approach-to-create-a-tooltip-component"><strong>Approach to Create a Tooltip Component</strong></h3><ul><li>Create a tooltip component (<em>Tooltip</em>) that will wrap the element and the content of the tooltip message</li><li>Create a tooltip content component (<em>TooltipContent</em>) that will wrap the actual tooltip message</li><li>Create a tooltip service class (<em>TooltipService</em>) that will control positioning of the tooltip message</li><li>Create a tooltip hook (<em>useTooltip</em>) that will expose methods to open and close the tooltip message with the help of <em>TooltipService</em> </li></ul><h3 id="creating-the-react-project-for-demo">Creating the React Project for Demo</h3><p><strong>Step 1:  </strong>Create the react project to demo the tooltip component we will build<br></p><pre><code class="language-shell">npx create-react-app tooltip-demo

cd tooltip-demo
</code></pre><p><strong>Step 2: </strong>Create the Tooltip components, hooks and services in a components directory</p><p>Here is how the project structure should look like</p><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/06/tooltip-project-outline.jpeg" class="kg-image" alt="Building a Tooltip Component with React Hooks" loading="lazy" width="253" height="441"></figure><p><strong>Step 3: </strong>Let&apos;s Create the tooltip service (<em>TooltipService) </em>with the following code</p><pre><code class="language-javascript">// TooltipService.js

export const Position  = {
  TOP: 0,
  TOP_RIGHT: 1,
  RIGHT: 2,
  BOTTOM_RIGHT: 3,
  BOTTOM: 4,
  BOTTOM_LEFT: 5,
  LEFT: 6,
  TOP_LEFT: 7,
}

class TooltipService {
    static POSTION_GAP = 5;

    static getTooltipPosition (tooltipEl, position) {
        const elRect = tooltipEl.getBoundingClientRect();
        const containerElRect = tooltipEl.parentElement.getBoundingClientRect();

        const defaultPosition = {
            bottom: -1 * (containerElRect.height + this.POSTION_GAP),
            left: &apos;0&apos;
        }

        if (position === Position.TOP) {
          return {
            top: -1 * (elRect.height + this.POSTION_GAP),
            left: &apos;0&apos;
          }
        } else if (position === Position.TOP_RIGHT) {
          return {
            top: -1 * (elRect.height + this.POSTION_GAP),
            right: -1 * (elRect.width + this.POSTION_GAP)
          }
        } else if (position === Position.BOTTOM_RIGHT) {
          return {
            bottom: -1 * (containerElRect.height + this.POSTION_GAP),
            right: -1 * (elRect.width + this.POSTION_GAP)
          }
        } else if (position === Position.BOTTOM_LEFT) {
          return {
            bottom: -1 * (containerElRect.height + this.POSTION_GAP),
            left:  -1 * (elRect.width + this.POSTION_GAP)
          }
        } else if (position === Position.TOP_LEFT) {
          return {
            top: -1 * (elRect.height + this.POSTION_GAP),
            left: -1 * (elRect.width + this.POSTION_GAP)
          }
        } else if (position === Position.LEFT) {
          return {
            top: &apos;0&apos;,
            left: -1 * (elRect.width + this.POSTION_GAP)
          }
        } else if (position === Position.RIGHT) {
          return {
            top: &apos;0&apos;,
            right: -1 * (elRect.width + this.POSTION_GAP)
          }
        } else {
          return defaultPosition;
        }
    }
}

export default TooltipService</code></pre><p>The above class exposes a static field (<em>POSITION_GAP</em>) to determine by how much distance in px the tooltip message should be away from the element and a static method (<em>getTooltipPosition</em>) that receive a reference to the tooltip message element and the expected display position to determine the position where we will display tooltip message<br>For readability and maintainability, we have exported a <em>Position </em>object that self explains the direction to which we want the tooltip message displayed</p><p><strong>Step 4: </strong>Let&apos;s create the tooltip hook (<em>useTooltip</em>) that will help expose methods for opening and closing the tooltip message</p><pre><code class="language-javascript">// useTooltip.js

import TooltipService, { Position } from &apos;./TooltipService&apos;;

const hideTooltip = (tooltipEl) =&gt; {
    tooltipEl.classList.add(&apos;hide&apos;);
    tooltipEl.classList.remove(&apos;show&apos;);
}

const showTooltip = (tooltipEl) =&gt; {
    tooltipEl.classList.add(&apos;show&apos;);
    tooltipEl.classList.remove(&apos;hide&apos;);
}

const useTooltip = () =&gt; {
    const renderTooltip = (tooltipRef, isOpen, position) =&gt; {        
        if (isOpen) {
            const tooltipPosition = TooltipService.getTooltipPosition(tooltipRef.current, position ? position : Position.TOP);
            if (tooltipPosition.top) {
                tooltipRef.current.style.top = `${tooltipPosition.top}px`;
                tooltipRef.current.style.bottom = null;
            } 
            if (tooltipPosition.left) {
                tooltipRef.current.style.left = `${tooltipPosition.left}px`;
                tooltipRef.current.style.right = null;
            }
            if (tooltipPosition.right) {
                tooltipRef.current.style.right = `${tooltipPosition.right}px`;
                tooltipRef.current.style.left = null;
            }
            if (tooltipPosition.bottom) {
                tooltipRef.current.style.bottom = `${tooltipPosition.bottom}px`;
                tooltipRef.current.style.top = null;
            }
            showTooltip(tooltipRef.current)
        } else {
            hideTooltip(tooltipRef.current)
        }
    }
    return (elementRef) =&gt; {
        return {
            open: (position) =&gt; renderTooltip(elementRef, true, position),
            close: () =&gt; renderTooltip(elementRef, false)
        }
    }

}

export default useTooltip</code></pre><p><strong>Step 5: </strong>Let&apos;s create the tooltip component (<em>Tooltip</em>) and the tooltip content component (<em>TooltipContent</em>)</p><p>The tooltip component will wrap the element that requires a tooltip display while the TooltipContent will display the tooltip message passed to the Tooltip component </p><pre><code class="language-javascript">// TooltipContent.jsx

import React, { forwardRef } from &quot;react&quot;;
import &quot;./Tooltip.css&quot;;

const TooltipContent = forwardRef((props, ref) =&gt; {
    return (
        &lt;&gt;
            &lt;div ref={ref} className=&quot;tooltip-wrapper bottom hide&quot;&gt;
                {React.isValidElement(props.content)
                    ? props.content 
                    : &lt;div className=&quot;content-wrapper&quot;&gt;{props.content}&lt;/div&gt;}
            &lt;/div&gt;
        &lt;/&gt;)
})

export default TooltipContent;

// Tooltip.jsx
import React, { forwardRef, useRef } from &quot;react&quot;;
import &quot;./Tooltip.css&quot;
import TooltipContent from &quot;./TooltipContent&quot;;
import { Position } from &quot;./TooltipService&quot;;
import useTooltip from &quot;./useTooltip&quot;;

const Tooltip = forwardRef(({content, position, children}, ref) =&gt; {
    const elRef = useRef();
    const tooltip = useTooltip();
    const handleMouseEnter = () =&gt; {
        tooltip(ref).open(position)
    }
    const handleMouseLeave = () =&gt; {
        tooltip(ref).close();
    }
    return (
        &lt;div className=&quot;container&quot;&gt;
            &lt;div onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} ref={elRef} className=&quot;tooltip-element&quot;&gt;
                {children}
            &lt;/div&gt;
            &lt;TooltipContent ref={ref} elRef={elRef} position={Position.TOP} content={content}&gt;&lt;/TooltipContent&gt;
        &lt;/div&gt;
    )
})

export default Tooltip;</code></pre><pre><code class="language-css">/* Tooltip.css */

.container {
    position: relative;
    width: fit-content;
}

.tooltip-wrapper {
    position: absolute;
    width: max-content;
}

.tooltip-wrapper.show {
    visibility: &apos;&apos;;
}

.tooltip-wrapper.hide {
    visibility: hidden;
}

.content-wrapper {
    background-color: rgba(0, 0, 0, 0.6);
    font-size: 0.8rem;
    color: #fff;
    width: fit-content;
    padding: 8px;
    border-radius: 4px; 
}</code></pre><p><strong>Final Step: </strong>Let&apos;s update the <em>App.js </em>file to use our tooltip component and utilities to demonstrate<br>We will use a select field to change the direction to which we want to display the tooltip message relative to the element</p><pre><code class="language-javascript">// App.js

import Tooltip from &apos;./components/Tooltip&apos;;
import &apos;./App.css&apos;;
import { useRef, useState } from &apos;react&apos;;
import { Position } from &apos;./components/Tooltip&apos;;
import useTooltip from &apos;./components/Tooltip/useTooltip&apos;;

function App() {

  const tooltip = useTooltip();
  const tooltipEl = useRef();
  const [position, setPosition] = useState(Position.TOP);

  const handleChange = (event) =&gt; {
    setPosition(+event.target.value);
  }
  return (
    &lt;div className=&quot;App&quot; style={{display: &apos;flex&apos;, justifyContent: &apos;center&apos;, alignItems: &apos;center&apos;, flexDirection: &apos;column&apos;, height: &apos;100vh&apos;}}&gt;
      &lt;Tooltip ref={tooltipEl} position={position} content={&lt;h2&gt;Hello!! I am a tooltip message.&lt;/h2&gt;}&gt;
         &lt;h2&gt;TOOLTIP DEMO&lt;/h2&gt;
      &lt;/Tooltip&gt;
      &lt;div style={{marginTop: &apos;200px&apos;}}&gt;
        &lt;select name=&quot;position&quot; onChange={handleChange} value={position}&gt;
          &lt;option value={Position.TOP}&gt;TOP&lt;/option&gt;
          &lt;option value={Position.BOTTOM}&gt;BOTTOM&lt;/option&gt;
          &lt;option value={Position.LEFT}&gt;LEFT&lt;/option&gt;
          &lt;option value={Position.RIGHT}&gt;RIGHT&lt;/option&gt;
          &lt;option value={Position.TOP_RIGHT}&gt;TOP RIGHT&lt;/option&gt;
          &lt;option value={Position.TOP_LEFT}&gt;TOP LEFT&lt;/option&gt;
          &lt;option value={Position.BOTTOM_LEFT}&gt;BOTTOM LEFT&lt;/option&gt;
          &lt;option value={Position.BOTTOM_RIGHT}&gt;BOTTOM RIGHT&lt;/option&gt;
        &lt;/select&gt;
        &lt;button onClick={() =&gt; tooltip(tooltipEl).open(position)}&gt;SHOW TOOLTIP&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre><p><strong>Start the react project</strong></p><pre><code class="language-shell">npm run start</code></pre><p>You can find the complete project on <a href="https://github.com/sonegillis/react-tooltip?ref=codewithgillis.com" rel="noreferrer">github</a></p>]]></content:encoded></item><item><title><![CDATA[Reactive forms with React hooks and typescript]]></title><description><![CDATA[<p>You&apos;ve probably been writing react apps that require you to create forms and collect user inputs, validate them and submit. You may have used libraries like <a href="https://formik.org/docs/overview?ref=codewithgillis.com" rel="noreferrer">formik</a> to build even more complex forms easily. In this article, we&apos;ll be learning how to use react hooks and</p>]]></description><link>https://codewithgillis.com/reactive-forms-with-react-hooks/</link><guid isPermaLink="false">66535d2a4eb78e0001892e7c</guid><category><![CDATA[react]]></category><category><![CDATA[react hooks]]></category><category><![CDATA[typescript]]></category><dc:creator><![CDATA[Sone GIllis]]></dc:creator><pubDate>Tue, 28 May 2024 23:15:41 GMT</pubDate><media:content url="https://codewithgillis.com/content/images/2024/05/WhatsApp-Image-2024-05-29-at-12.11.17-AM.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://codewithgillis.com/content/images/2024/05/WhatsApp-Image-2024-05-29-at-12.11.17-AM.jpeg" alt="Reactive forms with React hooks and typescript"><p>You&apos;ve probably been writing react apps that require you to create forms and collect user inputs, validate them and submit. You may have used libraries like <a href="https://formik.org/docs/overview?ref=codewithgillis.com" rel="noreferrer">formik</a> to build even more complex forms easily. In this article, we&apos;ll be learning how to use react hooks and typescript to build our own reactive forms. This will teach you the power of react hooks and creating your own custom hooks. </p><p>For easy follow up to this article, you&apos;ll be expected to be able to</p><ul><li>Build basic apps with react and typescript</li><li>Use basic react inbuilt hooks like <em>useEffect</em> and <em>useState</em></li></ul><p>Enough of the talking, let&apos;s get coding. We&apos;ll be building a reactive form in react with custom react hooks and and apply this to a registration form. Our custom hook will return validation results that we can use in our form to be able display error messages or take actions. The following below will show you how our reactive form behaves to user interaction</p>
<!--kg-card-begin: html-->
<div style="position: relative; padding-bottom: 56.25%; height: 0;"><iframe src="https://jumpshare.com/embed/ZIpBVtDMtkPk6tkzoaBW" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></iframe></div>
<!--kg-card-end: html-->
<p>Before we proceed to the implementation, let&apos;s see how we&apos;ll be using our form validator library in a form component for validation</p><pre><code class="language-typescript">const {
    inputFields,
    inputFieldsErrorMsgs,
    handleChange,
    formRef,
    isValid,
  }: useFormResult = useForm({
    fullName: [&apos;&apos;, [Validators.validateRequired()]],
    company: [&apos;&apos;],
    username: [
      &apos;&apos;,
      [Validators.validateRequired()],
      [asyncValidateUsername(&apos;User with username already exists&apos;)],
    ],
    email: [&apos;&apos;, [Validators.validateRequired(), Validators.validateEmail()]],
    password: [&apos;&apos;, [Validators.validateRequired()]],
  });</code></pre><p>Our custom hook returns the following that can be used within the form reactively:</p><ul><li><strong>inputFields</strong>: An object of the input fields and their status</li><li><strong>inputFieldsErrorMsgs</strong>: An object of the input fields and the error messages if any</li><li><strong>handleChange</strong>: A function that&apos;ll be added to the change handler of each input field</li><li><strong>formRef</strong>: Reference to the form element</li><li><strong>isValid</strong>: boolean that indicates if the form is valid or not</li></ul><p>It also takes in as input the different form fields, the default values and the synchronous and asynchronous validator functions</p><p>We&apos;ll create a <em>Validator.ts</em> file containing a validator class with default validators including <code>validateRequired</code>, <code>validateEmail</code>, <code>validateUsername</code> Our validator functions will be higher order functions taking an error message as argument and returning a function of type <code>ValidatorFn</code>  that takes in the form input value as argument and returns a <code>ValidationResult</code> type.</p><p>Our <em>Validator.ts</em> file will look like this with our default validators</p><pre><code class="language-typescript">export interface ValidationResult {
    error: boolean;
    msg: string;
}

export interface ValidationFn {
    (msg: string): ValidationResult;
}

export interface AsyncValidatorFn {
    (msg: string): Promise&lt;ValidationResult&gt;
}

export class Validators {
    static validateRequired(msg = &quot;This field is required&quot;): ValidationFn {
        return (value: string): ValidationResult  =&gt; {
            return {
                error: !value,
                msg
            }
        };
    }

    static validateEmail(msg = &quot;Email is not valid&quot;): ValidationFn {
        return (value: string): ValidationResult =&gt; {
            const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
            return {
                error: !regex.test(value),
                msg,
            };
        };
    }

    static validateUsername(msg = &quot;Username is not valid&quot;): ValidationFn {
        return (value: string): ValidationResult =&gt; {
            const regex = /^[-a-zA-Z0-9_]{2,30}$/;
            return {
                error: !regex.test(value),
                msg,
            };
        };
    }
}</code></pre><p>Also notice we have a <code>AsyncValidatorFn</code> type in case we need to implement an asynchronous validator. Users of validator functionality can implement custom validators of type <code>ValidatorFn</code> and <code>AsyncValidatorFn</code></p><p>We&apos;ll then create a custom hook called <code>useForm</code> in a file <em>useForm.ts</em>. Let&apos;s declare a type for our hook as shown</p><pre><code class="language-typescript">export interface useFormResult {
  inputFields: Record&lt;string, Interaction&gt;;
  setInputFields: (newState: Record&lt;string, Interaction&gt;) =&gt; void;
  inputFieldsErrorMsgs: Record&lt;string, string[]&gt;;
  setInputFieldsErrorMsgs: (newState: Record&lt;string, string[]&gt;) =&gt; void;
  handleChange: (e: FormEvent&lt;HTMLInputElement&gt;) =&gt; void;
  formRef: RefObject&lt;HTMLFormElement&gt;;
  isValid: boolean;
}

export type useFormType = (
  validations: Record&lt;string, [string, ValidationFn[]?, AsyncValidatorFn[]?]&gt;
) =&gt; useFormResult;</code></pre><p>Next we&apos;ll initialize the form input elements, error messages and reference to the form</p><pre><code class="language-typescript">
const initializeInputFields = (
  validations: Record&lt;string, [string, ValidationFn[]?, AsyncValidatorFn[]?]&gt;
) =&gt; {
  const fields: Record&lt;string, Interaction&gt; = {};
  const fieldsErrorMsgs: Record&lt;string, string[]&gt; = {};
  Object.keys(validations).forEach((key) =&gt; {
    fieldsErrorMsgs[key] = [];
    fields[key] = {
      dirty: false,
      touched: false,
      value: validations[key][0],
      valid: !(!!validations[key][1]?.length || !!validations[key][2]?.length),
    };
  });
  return { fields, fieldsErrorMsgs };
};

const useForm: useFormType = (
  validations: Record&lt;string, [string, ValidationFn[]?, AsyncValidatorFn[]?]&gt;
) =&gt; {
  const { fields, fieldsErrorMsgs } = initializeInputFields(validations);
  const [inputFields, setInputFields] =
    useState&lt;Record&lt;string, Interaction&gt;&gt;(fields);
  const [inputFieldsErrorMsgs, setInputFieldsErrorMsgs] =
    useState&lt;Record&lt;string, string[]&gt;&gt;(fieldsErrorMsgs);
  const [isValid, setIsValid] = useState&lt;boolean&gt;(false);
  const formRef: RefObject&lt;HTMLFormElement&gt; = useRef&lt;HTMLFormElement&gt;(null);
  }</code></pre><p>We&apos;ll use the <code>useEffect</code> hook to add focus event listeners to the input elements. This is to ensure we know when the input element has been touched. </p><pre><code class="language-typescript">useEffect(() =&gt; {
    const formEl = formRef.current;
    Object.keys(validations).forEach((key) =&gt; {
      const inputEl = formEl?.querySelector(
        `[name=&quot;${key}&quot;]`
      ) as HTMLInputElement;
      inputEl?.addEventListener(&apos;focus&apos;, async () =&gt; {
        const validationResult = validateField(key, inputEl.value);
        updateFieldsAfterValidation(key, validationResult, &apos;focus&apos;);
        const asyncValidationResult = await asyncValidateField(
          key,
          inputEl.value
        );
        if (asyncValidationResult.length) {
          updateFieldsAfterValidation(
            key,
            [...asyncValidationResult, ...validationResult],
            &apos;focus&apos;
          );
        }

        return;
      });
    });
  const updateFieldsAfterValidation = (
    key: string,
    validationResult: string[],
    event: &apos;focus&apos; | &apos;change&apos;
  ) =&gt; {
    validationResult = Array.from(new Set(validationResult));
    setInputFieldsErrorMsgs((_inputFieldsErrorMsgs) =&gt; ({
      ..._inputFieldsErrorMsgs,
      [key]: validationResult,
    }));
    setInputFields((_inputFields) =&gt; {
      const validArr = Object.entries(_inputFields).map(([_key, value]) =&gt; {
        return key === _key ? validationResult.length === 0 : value.valid;
      });
      setIsValid(!validArr.some((valid) =&gt; valid === false));
      return {
        ..._inputFields,
        [key]: {
          ..._inputFields[key],
          touched: event === &apos;focus&apos; ? true : _inputFields[key].touched,
          dirty: event === &apos;change&apos; ? true : _inputFields[key].dirty,
          valid: validationResult.length === 0,
        },
      };
    });
  };

  const validateField = (name: string, value: string): string[] =&gt; {
    const fieldErrors: string[] = [];
    const fieldValidations = validations[name];
    if (fieldValidations.length &gt; 1) {
      fieldValidations[1]?.forEach((validate) =&gt; {
        const validateErr = validate(value) as ValidationResult;
        if (validateErr.error) {
          fieldErrors.push(validateErr.msg);
        }
      });
    }
    return fieldErrors;
  };

  const asyncValidateField = async (
    name: string,
    value: string
  ): Promise&lt;string[]&gt; =&gt; {
    const fieldErrors: string[] = [];
    const fieldValidations = validations[name];

    if (fieldValidations.length &gt; 2) {
      const aysncFieldValidations = fieldValidations[2]
        ? fieldValidations[2].map(async (validate) =&gt; validate(value))
        : [];
      const asyncValidationResults = await Promise.all(aysncFieldValidations);
      asyncValidationResults.forEach((validateErr: ValidationResult) =&gt; {
        if (validateErr.error) {
          fieldErrors.push((validateErr as ValidationResult).msg);
        }
      });
    }
    return fieldErrors;
  };</code></pre><p>We use the <code>validateField</code> function to call the synchronous validator functions registered for that input field and <code>asyncValidateField</code> function to call asynchronous validator functions registered for that input field. An example case where we will want to apply asynchronous validation is in a case where we want to make an API call to verify if a username already exists.</p><p>Next let&apos;s create the onChange handler function</p><pre><code class="language-typescript">const handleChange = async (e: FormEvent&lt;HTMLInputElement&gt;) =&gt; {
    const { name, value } = e.currentTarget;
    setInputFields((_inputFields) =&gt; ({
      ..._inputFields,
      [name]: {
        ..._inputFields[name],
        value,
        dirty: true,
      },
    }));
    const validationResult = validateField(name, value);
    updateFieldsAfterValidation(name, validationResult, &apos;change&apos;);
    const asyncValidationResult = await asyncValidateField(name, value);
    if (asyncValidationResult.length) {
      updateFieldsAfterValidation(
        name,
        [...asyncValidationResult, ...validationResult],
        &apos;change&apos;
      );
    }
  };</code></pre><pre><code class="language-typescript">import { FormEvent, RefObject, useEffect, useRef, useState } from &apos;react&apos;;
import { AsyncValidatorFn, ValidationFn, ValidationResult } from &apos;./Validators&apos;;

export interface Interaction {
  touched: boolean;
  dirty: boolean;
  valid: boolean;
  value: string;
}

export interface useFormResult {
  inputFields: Record&lt;string, Interaction&gt;;
  setInputFields: (newState: Record&lt;string, Interaction&gt;) =&gt; void;
  inputFieldsErrorMsgs: Record&lt;string, string[]&gt;;
  setInputFieldsErrorMsgs: (newState: Record&lt;string, string[]&gt;) =&gt; void;
  handleChange: (e: FormEvent&lt;HTMLInputElement&gt;) =&gt; void;
  formRef: RefObject&lt;HTMLFormElement&gt;;
  isValid: boolean;
}

const initializeInputFields = (
  validations: Record&lt;string, [string, ValidationFn[]?, AsyncValidatorFn[]?]&gt;
) =&gt; {
  const fields: Record&lt;string, Interaction&gt; = {};
  const fieldsErrorMsgs: Record&lt;string, string[]&gt; = {};
  Object.keys(validations).forEach((key) =&gt; {
    fieldsErrorMsgs[key] = [];
    fields[key] = {
      dirty: false,
      touched: false,
      value: validations[key][0],
      valid: !(!!validations[key][1]?.length || !!validations[key][2]?.length),
    };
  });
  return { fields, fieldsErrorMsgs };
};

export type useFormType = (
  validations: Record&lt;string, [string, ValidationFn[]?, AsyncValidatorFn[]?]&gt;
) =&gt; useFormResult;

const useForm: useFormType = (
  validations: Record&lt;string, [string, ValidationFn[]?, AsyncValidatorFn[]?]&gt;
) =&gt; {
  const { fields, fieldsErrorMsgs } = initializeInputFields(validations);
  const [inputFields, setInputFields] =
    useState&lt;Record&lt;string, Interaction&gt;&gt;(fields);
  const [inputFieldsErrorMsgs, setInputFieldsErrorMsgs] =
    useState&lt;Record&lt;string, string[]&gt;&gt;(fieldsErrorMsgs);
  const [isValid, setIsValid] = useState&lt;boolean&gt;(false);
  const formRef: RefObject&lt;HTMLFormElement&gt; = useRef&lt;HTMLFormElement&gt;(null);

  useEffect(() =&gt; {
    const formEl = formRef.current;
    Object.keys(validations).forEach((key) =&gt; {
      const inputEl = formEl?.querySelector(
        `[name=&quot;${key}&quot;]`
      ) as HTMLInputElement;
      inputEl?.addEventListener(&apos;focus&apos;, async () =&gt; {
        const validationResult = validateField(key, inputEl.value);
        updateFieldsAfterValidation(key, validationResult, &apos;focus&apos;);
        const asyncValidationResult = await asyncValidateField(
          key,
          inputEl.value
        );
        if (asyncValidationResult.length) {
          updateFieldsAfterValidation(
            key,
            [...asyncValidationResult, ...validationResult],
            &apos;focus&apos;
          );
        }

        return;
      });
    });

    return () =&gt; {
      Object.keys(validations).forEach((key) =&gt; {
        formEl
          ?.querySelector(`[name=&quot;${key}&quot;]`)
          ?.removeEventListener(&apos;focus&apos;, () =&gt; {});
      });
    };
  }, []);

  const updateFieldsAfterValidation = (
    key: string,
    validationResult: string[],
    event: &apos;focus&apos; | &apos;change&apos;
  ) =&gt; {
    validationResult = Array.from(new Set(validationResult));
    setInputFieldsErrorMsgs((_inputFieldsErrorMsgs) =&gt; ({
      ..._inputFieldsErrorMsgs,
      [key]: validationResult,
    }));
    setInputFields((_inputFields) =&gt; {
      const validArr = Object.entries(_inputFields).map(([_key, value]) =&gt; {
        return key === _key ? validationResult.length === 0 : value.valid;
      });
      setIsValid(!validArr.some((valid) =&gt; valid === false));
      return {
        ..._inputFields,
        [key]: {
          ..._inputFields[key],
          touched: event === &apos;focus&apos; ? true : _inputFields[key].touched,
          dirty: event === &apos;change&apos; ? true : _inputFields[key].dirty,
          valid: validationResult.length === 0,
        },
      };
    });
  };

  const validateField = (name: string, value: string): string[] =&gt; {
    const fieldErrors: string[] = [];
    const fieldValidations = validations[name];
    if (fieldValidations.length &gt; 1) {
      fieldValidations[1]?.forEach((validate) =&gt; {
        const validateErr = validate(value) as ValidationResult;
        if (validateErr.error) {
          fieldErrors.push(validateErr.msg);
        }
      });
    }
    return fieldErrors;
  };

  const asyncValidateField = async (
    name: string,
    value: string
  ): Promise&lt;string[]&gt; =&gt; {
    const fieldErrors: string[] = [];
    const fieldValidations = validations[name];

    if (fieldValidations.length &gt; 2) {
      const aysncFieldValidations = fieldValidations[2]
        ? fieldValidations[2].map(async (validate) =&gt; validate(value))
        : [];
      const asyncValidationResults = await Promise.all(aysncFieldValidations);
      asyncValidationResults.forEach((validateErr: ValidationResult) =&gt; {
        if (validateErr.error) {
          fieldErrors.push((validateErr as ValidationResult).msg);
        }
      });
    }
    return fieldErrors;
  };

  const handleChange = async (e: FormEvent&lt;HTMLInputElement&gt;) =&gt; {
    const { name, value } = e.currentTarget;
    setInputFields((_inputFields) =&gt; ({
      ..._inputFields,
      [name]: {
        ..._inputFields[name],
        value,
        dirty: true,
      },
    }));
    const validationResult = validateField(name, value);
    updateFieldsAfterValidation(name, validationResult, &apos;change&apos;);
    const asyncValidationResult = await asyncValidateField(name, value);
    if (asyncValidationResult.length) {
      updateFieldsAfterValidation(
        name,
        [...asyncValidationResult, ...validationResult],
        &apos;change&apos;
      );
    }
  };

  return {
    inputFields,
    setInputFields,
    inputFieldsErrorMsgs,
    setInputFieldsErrorMsgs,
    handleChange,
    formRef,
    isValid,
  };
};

export default useForm;
</code></pre><p>Now let&apos;s see how we can use this to create a reactive registration form. Our registration form component should look like</p><pre><code class="language-typescript">import React, { FormEvent, useEffect } from &apos;react&apos;;
import &apos;./RegistrationForm.css&apos;; // Import your CSS file for styling
import useForm, { useFormResult } from &apos;../ReactiveForm/useForm&apos;;
import {
  AsyncValidatorFn,
  ValidationResult,
  Validators,
} from &apos;../ReactiveForm/Validators&apos;;

const existingUsers = [
  &apos;pixelpioneer&apos;,
  &apos;techtraveler&apos;,
  &apos;codecrafter&apos;,
  &apos;digitaldreamer&apos;,
  &apos;bytebuilder&apos;,
  &apos;nerdynavigator&apos;,
  &apos;geekyguru&apos;,
  &apos;circuitsage&apos;,
  &apos;binaryboss&apos;,
  &apos;techietitan&apos;,
];

const asyncValidateUsername = (msg: string): AsyncValidatorFn =&gt; {
  return (value: string): Promise&lt;ValidationResult&gt; =&gt; {
    return new Promise((resolve, reject) =&gt; {
      setTimeout(
        () =&gt; resolve({ error: existingUsers.includes(value), msg }),
        2000
      );
    });
  };
};

const RegistrationForm = () =&gt; {
  const {
    inputFields,
    inputFieldsErrorMsgs,
    handleChange,
    formRef,
    isValid,
  }: useFormResult = useForm({
    fullName: [&apos;&apos;, [Validators.validateRequired()]],
    company: [&apos;&apos;],
    username: [
      &apos;&apos;,
      [Validators.validateRequired()],
      [asyncValidateUsername(&apos;User with username already exists&apos;)],
    ],
    email: [&apos;&apos;, [Validators.validateRequired(), Validators.validateEmail()]],
    password: [&apos;&apos;, [Validators.validateRequired()]],
  });

  useEffect(() =&gt; {}, []);

  const handleSubmit = (e: FormEvent&lt;HTMLFormElement&gt;) =&gt; {
    e.preventDefault();
  };

  return (
    &lt;div className=&quot;registration-form&quot;&gt;
      {/* {JSON.stringify(inputFields)}
      {JSON.stringify(inputFieldsErrorMsgs)}
      {JSON.stringify(isValid)} */}
      &lt;h2&gt;Register&lt;/h2&gt;
      &lt;form onSubmit={handleSubmit} ref={formRef}&gt;
        &lt;div className=&quot;form-group&quot;&gt;
          &lt;input
            type=&quot;text&quot;
            name=&quot;fullName&quot;
            placeholder=&quot;Full Name&quot;
            value={inputFields.fullName.value}
            onChange={handleChange}
          /&gt;
          {inputFields?.fullName?.touched &amp;&amp; !inputFields?.fullName?.valid &amp;&amp; (
            &lt;p className=&quot;error&quot;&gt;
              {inputFieldsErrorMsgs?.fullName.map((error) =&gt; (
                &lt;span key={error}&gt;{error}&lt;/span&gt;
              ))}
            &lt;/p&gt;
          )}
        &lt;/div&gt;
        &lt;div className=&quot;form-group&quot;&gt;
          &lt;input
            type=&quot;text&quot;
            name=&quot;company&quot;
            placeholder=&quot;Company&quot;
            value={inputFields.company.value}
            onChange={handleChange}
          /&gt;
          {inputFields?.company?.touched &amp;&amp; !inputFields?.company?.valid &amp;&amp; (
            &lt;p className=&quot;error&quot;&gt;
              {inputFieldsErrorMsgs?.company.map((error) =&gt; (
                &lt;span key={error}&gt;{error}&lt;/span&gt;
              ))}
            &lt;/p&gt;
          )}
        &lt;/div&gt;
        &lt;div className=&quot;form-group&quot;&gt;
          &lt;input
            type=&quot;email&quot;
            name=&quot;email&quot;
            placeholder=&quot;Email&quot;
            value={inputFields.email.value}
            onChange={handleChange}
          /&gt;
          {inputFields?.email?.touched &amp;&amp; !inputFields?.email?.valid &amp;&amp; (
            &lt;p className=&quot;error&quot;&gt;
              {inputFieldsErrorMsgs?.email.map((error) =&gt; (
                &lt;span key={error}&gt;{error}&lt;/span&gt;
              ))}
            &lt;/p&gt;
          )}
        &lt;/div&gt;
        &lt;div className=&quot;form-group&quot;&gt;
          &lt;input
            type=&quot;text&quot;
            name=&quot;username&quot;
            placeholder=&quot;Username&quot;
            value={inputFields.username.value}
            onChange={handleChange}
          /&gt;
          {inputFields?.username?.touched &amp;&amp; !inputFields?.username?.valid &amp;&amp; (
            &lt;p className=&quot;error&quot;&gt;
              {inputFieldsErrorMsgs?.username.map((error) =&gt; (
                &lt;span key={error}&gt;{error}&lt;/span&gt;
              ))}
            &lt;/p&gt;
          )}
        &lt;/div&gt;
        &lt;div className=&quot;form-group&quot;&gt;
          &lt;input
            type=&quot;password&quot;
            name=&quot;password&quot;
            placeholder=&quot;Password&quot;
            value={inputFields.password.value}
            onChange={handleChange}
          /&gt;
          {inputFields?.password?.touched &amp;&amp; !inputFields?.password?.valid &amp;&amp; (
            &lt;p className=&quot;error&quot;&gt;
              {inputFieldsErrorMsgs?.password.map((error) =&gt; (
                &lt;span key={error}&gt;{error}&lt;/span&gt;
              ))}
            &lt;/p&gt;
          )}
        &lt;/div&gt;
        &lt;button type=&quot;submit&quot;&gt;Register&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
};

export default RegistrationForm;
</code></pre><p>Notice how we have added an asynchronous validator function <code>asyncValidateUsername</code> to simulate an api call that checks from an array of usernames to ensure the inputted username doesn&apos;t belong to the list and displays the error accordingly.</p><p>You can find the complete project on <a href="https://github.com/sonegillis/react-reactive-form?ref=codewithgillis.com" rel="noreferrer">github</a></p>]]></content:encoded></item><item><title><![CDATA[Detect when your android application is idle]]></title><description><![CDATA[<p>Sometime ago, while working on an android project, and I reached a point where I had to detect when my application is&#xA0;<strong>idle</strong>&#xA0;and perform a certain action. By idle here I mean the user has opened the app but is not interacting with.</p><p><strong>What are we going</strong></p>]]></description><link>https://codewithgillis.com/detect-when-your-android-application-is-idle/</link><guid isPermaLink="false">664b90ba4eb78e0001892e31</guid><category><![CDATA[android]]></category><category><![CDATA[android dev]]></category><category><![CDATA[android apps]]></category><dc:creator><![CDATA[Sone GIllis]]></dc:creator><pubDate>Mon, 20 May 2024 18:11:22 GMT</pubDate><media:content url="https://codewithgillis.com/content/images/2024/05/idle-app-2-.png" medium="image"/><content:encoded><![CDATA[<img src="https://codewithgillis.com/content/images/2024/05/idle-app-2-.png" alt="Detect when your android application is idle"><p>Sometime ago, while working on an android project, and I reached a point where I had to detect when my application is&#xA0;<strong>idle</strong>&#xA0;and perform a certain action. By idle here I mean the user has opened the app but is not interacting with.</p><p><strong>What are we going to build?</strong><br>Let&#x2019;s assume a bank wanted to give it&#x2019;s customers the ability to increase their account balances just by clicking a button &#x1F603; (that might never happen, yes I know) and we need to build such an app for them. The user&#x2019;s balance will reset to 0 if he delays for a period of time without interacting with the app. Simple app right? But remember our goal is to detect when the user has not been interacting with our app for sometime and not to build some fancy application. After this, you will be able to detect idle state of your application. Now enough of the talking. Let&#x2019;s dive into some concepts and code.</p><p>We&#x2019;ll start by creating a new android project on android studio. Feel free to call it whatever you want. I&#x2019;ll provide a link to the repo at the end. Next we&#x2019;ll create an empty activity and call it&#xA0;<strong>MainActivity.&#xA0;</strong>We&#x2019;ll then create java class and call it&#xA0;<strong>BaseActivity</strong>. BaseActivity will be the parent class of all activities. The reason why I did this is because we&#x2019;ll not want to write code in all our activities where we need to detect idle state of the application.</p><p>Let&#x2019;s add some code to the BaseActivity class.</p><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/android-code-1-1.png" class="kg-image" alt="Detect when your android application is idle" loading="lazy" width="564" height="310"></figure><p>As you can see I created a method&#xA0;<strong>myCustomOnCreate</strong>&#xA0;that will be overidden by all child activities inorder to render the layout.</p><p>I already created the layout that allows the user add up what he has in account by simply tapping on a button. I have also implemented the java functionality. Remember that&#x2019;s not purpose of this article so we&#x2019;ll focus more performing an action when our app is idle. This is a look of our simple app.</p><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/android-code-2-1.jpg" class="kg-image" alt="Detect when your android application is idle" loading="lazy" width="360" height="780"></figure><p>We will be using the android&#xA0;<strong>Handler&#xA0;</strong>class and&#xA0;<strong>Runnable</strong>&#xA0;interface. Inside the BaseActivity class, let&#x2019;s create a&#xA0;<em><strong>handler object</strong>&#xA0;</em>and pass a&#xA0;<em><strong>runnable object</strong>&#xA0;</em>with a&#xA0;<strong><em>timeout</em>&#xA0;</strong>to the handlers&#xA0;<strong><em>postDelayed.&#xA0;</em></strong>This will call the&#xA0;<strong><em>run</em></strong>&#xA0;method implementation inside our runnable implementation after timeout has occured. Let&#x2019;s create a method and call it&#xA0;<strong><em>reactToIdleState.&#xA0;</em></strong>This method will be called inside the run method of the runnable implementation. We will also need to override this method inside all our Activities that extend BaseActivity. That is the method that will be called when the app becomes idle</p><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/android-code-3-1.png" class="kg-image" alt="Detect when your android application is idle" loading="lazy" width="318" height="135"></figure><p>In the BaseActivity onCreate method, we will initialize our handler and call it&#x2019;s postDelayed method passing into it the runnable object and the timeout as shown below. This simply means &#x201C;Call the runnable&#x2019;s run method after a timeout has reached&#x201D;.</p><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/android-code-4-1.png" class="kg-image" alt="Detect when your android application is idle" loading="lazy" width="427" height="120"></figure><p>There are only two ways the user can interact with our app. Either by simple touching the screen or clicking the button to add their balance. So will make our BaseActivity class implement the&#xA0;<strong>View.OnClickListener</strong>&#xA0;interface and implement it&#x2019;s&#xA0;<em><strong>onClick</strong>&#xA0;</em>method. We will also the override the Activity&#xA0;<strong><em>onTouchEvent</em>.&#xA0;</strong>In these two methods we will remove the callback from the handler by calling the<strong>&#xA0;<em>removeCallbacks</em></strong>&#xA0;method and passing to it our runnable object, then call the postDelayed method again on the handler object. I have created a method called&#xA0;<strong><em>resetHandler</em></strong>&#xA0;to the job. We will call this method in our onClick and OnTouchEvent methods so every time user touches his screen or clicks the button we re-initialize the post delay.</p><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/android-code-5-1.png" class="kg-image" alt="Detect when your android application is idle" loading="lazy" width="369" height="87"></figure><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/android-code-6-2.png" class="kg-image" alt="Detect when your android application is idle" loading="lazy" width="376" height="270"></figure><p>If you notice, in the onClick method I called a&#xA0;<strong><em>myCustomOnClick</em></strong>&#xA0;method that will be overridden inside all activities that extend BaseActivity and needs to respond to click events. That&#x2019;s it for the BaseActivity. Now any of our activities can extend our BaseActivity and override the appropriate methods to perform actions on app idleness.</p><p><strong>Our MainActivty will do the following</strong></p><ul><li>Declare member variables and initialise them in the&#xA0;<strong><em>myCustomOnCreate</em></strong>&#xA0;method.</li></ul><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/android-code-7-1.png" class="kg-image" alt="Detect when your android application is idle" loading="lazy" width="462" height="333"></figure><ul><li>React to the click events by overriding the BaseActivity&#xA0;<strong><em>myCustomOnClick&#xA0;</em></strong>method.</li></ul><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/android-code-8-2.png" class="kg-image" alt="Detect when your android application is idle" loading="lazy" width="413" height="217"></figure><ul><li>Override the BaseActivity&#xA0;<strong><em>reactToIdleState</em></strong>&#xA0;method and perform desired action when the app is idle.</li></ul><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/android-code-9-1.png" class="kg-image" alt="Detect when your android application is idle" loading="lazy" width="305" height="109"></figure><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/android-code-10-1.png" class="kg-image" alt="Detect when your android application is idle" loading="lazy" width="419" height="198"></figure><p>That&#x2019;s it for our app. You can improve on the app functionality. But I would expect you to apply the concept to your own application where you need to know when the user hasn&#x2019;t been interacting with the app for a while. Any idea about some other approach or questions will be highly welcome in the comment section. I&#x2019;ll also be pleased if there could be corrections or something to add in the comment section. Here is the link to the complete project&#xA0;<a href="https://github.com/sonegillis/idle-state-detect-app?ref=codewithgillis.com" rel="noopener ugc nofollow">https://github.com/sonegillis/idle-state-detect-app</a>. You can clone the project and run on your android device. To know more about Handlers and Runnable you can check out the following links to the google android developer site.<br>-&#xA0;<a href="https://developer.android.com/reference/android/os/Handler?ref=codewithgillis.com" rel="noopener ugc nofollow">https://developer.android.com/reference/android/os/Handler</a><br>-&#xA0;<a href="https://developer.android.com/reference/java/lang/Runnable?ref=codewithgillis.com" rel="noopener ugc nofollow">https://developer.android.com/reference/java/lang/Runnable</a></p>]]></content:encoded></item><item><title><![CDATA[Generic Multi-Field Django Model Custom Validation]]></title><description><![CDATA[<p>In this article, you will learn how you can create a generic multi-field model custom validation mechanism for your django projects. By default, django provides a way for you to validate your model fields by passing a list of validator functions to the validators argument. What if we have to</p>]]></description><link>https://codewithgillis.com/generic-multi-field-django-model-custom-validation/</link><guid isPermaLink="false">664b8a3f4eb78e0001892dfe</guid><category><![CDATA[django]]></category><category><![CDATA[django models]]></category><dc:creator><![CDATA[Sone GIllis]]></dc:creator><pubDate>Mon, 20 May 2024 17:40:26 GMT</pubDate><media:content url="https://codewithgillis.com/content/images/2024/05/django-admin-validation-error-2.png" medium="image"/><content:encoded><![CDATA[<img src="https://codewithgillis.com/content/images/2024/05/django-admin-validation-error-2.png" alt="Generic Multi-Field Django Model Custom Validation"><p>In this article, you will learn how you can create a generic multi-field model custom validation mechanism for your django projects. By default, django provides a way for you to validate your model fields by passing a list of validator functions to the validators argument. What if we have to apply validations to multiple model fields? For example, in an e-commerce application, say we have to ensure that when a user selects a discount of type percentage, then the discount value should be less than or equal to 100. We will in the following sections, with detailed code and explanations, see how we can do this in Django.</p><h3 id="creating-the-model">Creating the model</h3><p>Let&#x2019;s assume we are implementing a&#xA0;<strong>Coupon&#xA0;</strong>model that keeps track of coupons we offer to customers. Our coupon model looks like this</p><pre><code class="language-python">class Coupon(models.Model):
    class DiscountTypes(models.TextChoices):
        PERCENTAGE_DISCOUNT = &quot;percentage_discount&quot;
        FIXED_CART_DISCOUNT = &quot;fixed_cart_discount&quot;
        FIXED_PRODUCT_DISCOUNT = &quot;fixed_product_discount&quot;
        BOGO = &quot;bogo&quot;, _(&quot;BOGO (Buy X GET X/Y) Offer&quot;)

    class DisplayIn(models.TextChoices):
        MY_ACCOUNT = &quot;my_account&quot;
        CHECKOUT = &quot;checkout&quot;
        CART = &quot;cart&quot;

    code = models.CharField(max_length=20, unique=True, null=True)
    amount = models.FloatField(default=0)
    start_date = models.DateTimeField(auto_now=True)
    expiry_date = models.DateTimeField()
    free_shipping = models.BooleanField(default=False)
    description = models.TextField(max_length=200)
    discount_type = models.CharField(max_length=40, choices=DiscountTypes.choices)
    apply_automatically = models.BooleanField(default=False)
    display_in = MultiSelectField(max_length=20, max_choices=3, choices=DisplayIn.choices, null=True)
    minimum_spend = models.IntegerField(default=0, null=True)
    maximum_spend = models.IntegerField(default=0, null=True)
    individual_use_only = models.BooleanField(default=False)
    exclude_sale_items = models.BooleanField(default=False)
    email_restrictions = models.TextField(help_text=
                                          &quot;Comma separated list of emails to restrict for access to this coupon&quot;,
                                          null=True, blank=True)
    def __str__(self):
        return self.code</code></pre><p>The coupon has different discount types including:</p><ul><li>Percentage discount</li><li>Fixed cart discount</li><li>fixed product discount</li><li>Bogo discount</li></ul><h3 id="validation">Validation</h3><p>Let&#x2019;s say we want to ensure that, if an admin creates a percentage discount coupon, the&#xA0;<strong>amount</strong>&#xA0;field should not be above&#xA0;<strong>100.&#xA0;</strong>To make this generic and reusable, let&#x2019;s create a&#xA0;<strong>Validator</strong>&#xA0;abstract base class. This is will provide two methods:</p><p><strong>register &#x2014;&#xA0;</strong>A class method that will be used to register the fields that we want to validate and the error message that should be shown.</p><p><strong>run &#x2014;&#xA0;</strong>An abstract method that will be implemented by child classes to provide the actual validation functionality.</p><p>Our&#xA0;<strong>Validator&#xA0;</strong>abstract base class should look like this:</p><pre><code class="language-python">from abc import ABC, abstractmethod
from django.db import models
import typing as t


class Validator(ABC):
    @classmethod
    def register(cls, fields: t.List[str] = [], message=&quot;Model is not valid&quot;):
        cls.fields = fields
        cls.message = message
        return cls()

    @abstractmethod
    def run(self, model: models.Model):
        pass</code></pre><p>Now let&#x2019;s create a mixin called&#xA0;<strong>ModelValidationMixin.&#xA0;</strong>This will override the&#xA0;<strong>clean</strong>&#xA0;and the&#xA0;<strong>save</strong>&#xA0;methods of the django model. It will loop through a&#xA0;<strong>validators&#xA0;</strong>(a list of Validator implementations) property and check for validation errors. Our&#xA0;<strong>ModelValidationMixin</strong>&#xA0;should look like this:</p><pre><code class="language-python">class ModelValidationMixin:
    validators: t.List[Validator] = []

    def clean(self):
        for validator in self.validators:
            if not validator.run(self):
                raise ValidationError(validator.message)

    def save(self, *args, **kwargs):
        self.full_clean()
        super(ModelValidationMixin, self).save(*args, **kwargs)
</code></pre><p>Next, let&#x2019;s implement a validation class (<strong>LessThan100IfPercentageDiscount</strong>) which will answer our initial question above &#x2014; Ensure that if the discount type is&#xA0;<strong>percentage_discount,&#xA0;</strong>the discount amount inputted should not go above&#xA0;<strong>100</strong>.</p><pre><code class="language-python">class LessThan100IfPercentageDiscount(Validator):
    def run(self, model):
        return not (model.discount_type == Coupon.DiscountTypes.PERCENTAGE_DISCOUNT and model.amount &gt; 100.0)</code></pre><p>As a bonus let&#x2019;s also implement another validation class that will ensure that the&#xA0;<strong>start_date</strong>&#xA0;is never ahead of the&#xA0;<strong>expiry_date</strong>&#xA0;(<strong>LessThanValidator</strong>) &#x2014; Usable for comparing other fields that need a less-than comparism.</p><pre><code class="language-python">class LessThanValidator(Validator):
    def run(self, model: models.Model):
        return getattr(model, self.fields[0]) &lt; getattr(model, self.fields[1])</code></pre><h3 id="let%E2%80%99s-wrap-it-up">Let&#x2019;s wrap it up</h3><p>To use the above validators, our Coupon model class will then look like:</p><pre><code class="language-python">class Coupon(ModelValidationMixin, models.Model):
    class DiscountTypes(models.TextChoices):
        PERCENTAGE_DISCOUNT = &quot;percentage_discount&quot;
        FIXED_CART_DISCOUNT = &quot;fixed_cart_discount&quot;
        FIXED_PRODUCT_DISCOUNT = &quot;fixed_product_discount&quot;
        BOGO = &quot;bogo&quot;, _(&quot;BOGO (Buy X GET X/Y) Offer&quot;)

    class DisplayIn(models.TextChoices):
        MY_ACCOUNT = &quot;my_account&quot;
        CHECKOUT = &quot;checkout&quot;
        CART = &quot;cart&quot;

    code = models.CharField(max_length=20, unique=True, null=True)
    amount = models.FloatField(default=0)
    start_date = models.DateTimeField(auto_now=True)
    expiry_date = models.DateTimeField()
    free_shipping = models.BooleanField(default=False)
    description = models.TextField(max_length=200)
    discount_type = models.CharField(max_length=40, choices=DiscountTypes.choices)
    apply_automatically = models.BooleanField(default=False)
    display_in = MultiSelectField(max_length=20, max_choices=3, choices=DisplayIn.choices, null=True)
    minimum_spend = models.IntegerField(default=0, null=True)
    maximum_spend = models.IntegerField(default=0, null=True)
    individual_use_only = models.BooleanField(default=False)
    exclude_sale_items = models.BooleanField(default=False)
    email_restrictions = models.TextField(help_text=
                                          &quot;Comma separated list of emails to restrict for access to this coupon&quot;,
                                          null=True, blank=True)
    validators = [
        LessThanValidator.register([&quot;start_date&quot;, &quot;expiry_date&quot;],
                                   message={&quot;expiry_date&quot;: &quot;Should be greater than start date&quot;}),
        LessThan100IfPercentageDiscount.register(message={&quot;amount&quot;: &quot;Amount should be less that 100 if coupon type is&quot;
                                                                    &quot;percentage_discount&quot;})
    ]

    def __str__(self):
        return self.code</code></pre><p>After running this, in the django admin interface, you&#x2019;ll notice an error that appears while trying to input a discount amount of 120 while the discount type is percentage_discount</p><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/django-admin-validation-error-1.png" class="kg-image" alt="Generic Multi-Field Django Model Custom Validation" loading="lazy" width="1562" height="670" srcset="https://codewithgillis.com/content/images/size/w600/2024/05/django-admin-validation-error-1.png 600w, https://codewithgillis.com/content/images/size/w1000/2024/05/django-admin-validation-error-1.png 1000w, https://codewithgillis.com/content/images/2024/05/django-admin-validation-error-1.png 1562w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[Ensure code from a Pull Request is unit tested before enabling merge with github actions]]></title><description><![CDATA[<p>So as a team lead you try to enforce unit testing within your team by making sure every piece of code in a PR is properly unit tested when you review. This can be extra time consuming. Can we make the process of checking for unit tests for a new</p>]]></description><link>https://codewithgillis.com/ensure-code-from-pull-request-is-unittested/</link><guid isPermaLink="false">664b86bd4eb78e0001892dcc</guid><category><![CDATA[github actions]]></category><category><![CDATA[CI / CD]]></category><dc:creator><![CDATA[Sone GIllis]]></dc:creator><pubDate>Mon, 20 May 2024 17:25:16 GMT</pubDate><media:content url="https://codewithgillis.com/content/images/2024/05/unit-test-with-gh-actions-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://codewithgillis.com/content/images/2024/05/unit-test-with-gh-actions-1.png" alt="Ensure code from a Pull Request is unit tested before enabling merge with github actions"><p>So as a team lead you try to enforce unit testing within your team by making sure every piece of code in a PR is properly unit tested when you review. This can be extra time consuming. Can we make the process of checking for unit tests for a new functionality automatically handled via a script run from github actions on PR events and concentrate on reviewing the actual code for quality? That&apos;s what we&apos;ll be looking at in this article.</p><p><strong>Note:</strong><br>The following have been tested with the jest testing utility for JavaScript related projects but can be slightly edited to work for other languages / frameworks. This also assumes that you have setup unit test in your JavaScript project</p><p>We&apos;ll start by ensuring branch protection rules for the branches that require protection. In most cases the main branch and the develop branches. You can check out this <a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/managing-a-branch-protection-rule?ref=codewithgillis.com" rel="noreferrer">documentation from github</a> on how to setup branch protection rules. We&apos;ll create these rules on our branch (in this case <strong><em>main</em></strong>) which includes requiring a PR to merge and enabling status checks to pass. From the image below, you&apos;ll realize github can&apos;t find any status check. We&apos;ll get to that in a bit<br></p><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/image.png" class="kg-image" alt="Ensure code from a Pull Request is unit tested before enabling merge with github actions" loading="lazy" width="773" height="901" srcset="https://codewithgillis.com/content/images/size/w600/2024/05/image.png 600w, https://codewithgillis.com/content/images/2024/05/image.png 773w" sizes="(min-width: 720px) 720px"></figure><p>Next we&apos;ll create a <strong><em>workflow</em></strong> directory in a <strong><em>.github</em></strong> directory at the root of our project repository.</p><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/image-1.png" class="kg-image" alt="Ensure code from a Pull Request is unit tested before enabling merge with github actions" loading="lazy" width="466" height="70"></figure><p>There we&apos;ll create a file and call it <code>must-unit-test.yml</code> .</p><pre><code class="language-yaml">name: Must Unit Test

on:
  pull_request:
    branches:
      - main
      - develop

jobs:
  check_for_unittest:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
  
      - name: Check for unit test
        run: |
          bash must-unit-test.sh
        env:
          MERGING_BRANCH: ${{ github.head_ref }}
          BASE_BRANCH: ${{ github.base_ref }}

      - name: Set conclusion based on script results
        if: ${{ success() &amp;&amp; steps.run_script.outputs.success == &apos;true&apos; }}
        run: |
          echo &quot;Checks passed.&quot;
          exit 0

      - name: Set failed conclusion
        if: ${{ failure() || steps.run_script.outputs.success == &apos;false&apos; }}
        run: |
          echo &quot;Checks failed. See script output for details.&quot;
          exit 1

    env:
      CI: true
</code></pre><p>In the above workflow yaml file, we are</p><ul><li>Listening for a pull request event on the main and develop branches of our repository</li><li>Running a <code>must-unit-test.sh</code> bash script. This script which we will look at in a bit will be expecting a two env variables <code>MERGING_BRANCH</code> and <code>BASE_BRANCH</code> that will the branch we are about to merge from and the branch we are about to merge to respectively</li><li>The rest of instructions simply checks the outcome of the our script and will exit successfully if the PR has unit test or in a failed state if not</li></ul><p>Let&apos;s create our bash script <code>must-unit-test.sh</code> at the root directory of our repository with the following content</p><pre><code class="language-shell">#!/bin/bash

get_coverage_summary() {

    local coverage_summary=&quot;$1&quot;
    
    # Extract values from the coverage summary
    local statements_covered=$(echo &quot;$coverage_summary&quot; | grep &apos;Statements&apos; | awk &apos;{print $5}&apos; | cut -d&apos;/&apos; -f1)
    local statements_total=$(echo &quot;$coverage_summary&quot; | grep &apos;Statements&apos; | awk &apos;{print $5}&apos; | cut -d&apos;/&apos; -f2)
    local branches_covered=$(echo &quot;$coverage_summary&quot; | grep &apos;Branches&apos; | awk &apos;{print $5}&apos; | cut -d&apos;/&apos; -f1)
    local branches_total=$(echo &quot;$coverage_summary&quot; | grep &apos;Branches&apos; | awk &apos;{print $5}&apos; | cut -d&apos;/&apos; -f2)
    local functions_covered=$(echo &quot;$coverage_summary&quot; | grep &apos;Functions&apos; | awk &apos;{print $5}&apos; | cut -d&apos;/&apos; -f1)
    local functions_total=$(echo &quot;$coverage_summary&quot; | grep &apos;Functions&apos; | awk &apos;{print $5}&apos; | cut -d&apos;/&apos; -f2)
    local lines_covered=$(echo &quot;$coverage_summary&quot; | grep &apos;Lines&apos; | awk &apos;{print $5}&apos; | cut -d&apos;/&apos; -f1)
    local lines_total=$(echo &quot;$coverage_summary&quot; | grep &apos;Lines&apos; | awk &apos;{print $5}&apos; | cut -d&apos;/&apos; -f2)

    # Calculate percentages to six decimal places using bc
    local statements=$(echo &quot;scale=6; ($statements_covered/$statements_total)*100&quot; | bc)
    local branches=$(echo &quot;scale=6; ($branches_covered/$branches_total)*100&quot; | bc)
    local functions=$(echo &quot;scale=6; ($functions_covered/$functions_total)*100&quot; | bc)
    local lines=$(echo &quot;scale=6; ($lines_covered/$lines_total)*100&quot; | bc)

    # Assign values to a global array
    COVERAGE_VALUES=(&quot;$statements&quot; &quot;$branches&quot; &quot;$functions&quot; &quot;$lines&quot;)
}

# Define the color code for red
RED=&apos;\033[0;31m&apos;
NC=&apos;\033[0m&apos; # No Color

# Function to echo text in red
echo_red() {
    echo -e &quot;${RED}$1${NC}&quot;
}

merging_branch=$MERGING_BRANCH
base_branch=$BASE_BRANCH

if [ -n &quot;$mergin_branch&quot; ] &amp;&amp; [ -n &quot;$base_branch&quot; ]; then
    # Fetch the base branch, checkout to it and npm i
    git fetch origin $base_branch 2&gt;&amp;1
    git checkout $base_branch 2&gt;&amp;1
    npm i
    # Get the coverage test summary for the base branch
    coverage_summary=$(npx jest --coverage --coverageReporters=&quot;text-summary&quot; | awk &apos;/Coverage summary/ {flag=1; next} /=========================/ {flag=0} flag&apos;)
    if [ $? -eq 0 ]; then
        get_coverage_summary &quot;$coverage_summary&quot;
        remote_statements=${COVERAGE_VALUES[0]}
        remote_branches=${COVERAGE_VALUES[1]}
        remote_functions=${COVERAGE_VALUES[2]}
        remote_lines=${COVERAGE_VALUES[3]}

        # Fetch the base branch, checkout to it and npm i
        git fetch origin $merging_branch 2&gt;&amp;1
        git checkout $merging_branch
        npm i
        
        # Get the coverage test summary for the merging branch
        coverage_summary=$(npx jest --coverage --coverageReporters=&quot;text-summary&quot; 2&gt;&amp;1 | awk &apos;/Coverage summary/ {flag=1; next} /=========================/ {flag=0} flag&apos;)
        if [ $? -eq 0 ]; then
            get_coverage_summary &quot;$coverage_summary&quot;
            local_statements=${COVERAGE_VALUES[0]}
            local_branches=${COVERAGE_VALUES[1]}
            local_functions=${COVERAGE_VALUES[2]}
            local_lines=${COVERAGE_VALUES[3]}
            
            if [ 1 -eq &quot;$(echo &quot;$local_statements &lt; $remote_statements&quot; | bc)&quot; ] || [ 1 -eq &quot;$(echo &quot;$local_branches &lt; $remote_branches&quot; | bc)&quot; ] || [ 1 -eq &quot;$(echo &quot;$local_functions &lt; $remote_functions&quot; | bc)&quot; ] || [ 1 -eq &quot;$(echo &quot;$local_lines &lt; $remote_lines&quot; | bc)&quot; ]; then
                echo_red &quot;You are attempting to push an update without a unit test&quot;
                exit 1
            fi
        fi
    fi
fi

exit 0</code></pre><p><strong>Code Breakdown:</strong><br>Assuming a member of our team created a new feature in a branch <strong>feature/less-work-more-output </strong>and initiate a PR into <strong>develop</strong> branch for merge. The above script will</p><ul><li>Checkout to the develop branch, and install package dependencies. Then run a jest unit test coverage on the project. We will repeat same for the feature/less-work-more-output branch. The output of the unit test command after running through the awk utility will look like this<br>Statements : 0.5% ( 21/4175 )<br>Branches : 0.42% ( 10/2359 )<br>Functions : 0.47% ( 6/1261 )<br>Lines : 0.67% ( 21/3100 )</li><li>We pass the output of the unit test command through a <code>get_coverage_summary</code> function. This will extract the digits in brackets for each of <strong>statements</strong>, <strong>branches</strong>, <strong>functions</strong> and <strong>lines </strong>and perform the division to six decimal places for higher precision. We could easily use the percentage values directly but this will be low precision and will not catch smaller unit of code changes without test</li><li>For each of the outputs of the unit test summary for both branches, we compare with the help of the <code>bc</code> shell utility. The assumption here is: If the merging branch has lesser test coverage for each of statements, branches, functions and lines, compared to the base branch, then it means there is code in the merging branch without unit test and the PR fails the check</li></ul><p>Next we&apos;ll commit and push our changes. We now have our github workflow action setup that will ensure that every piece of added code is unit tested before merging to the main or develop branch. But we are not done yet. We have to add that to our branch protection rules. Remember when we setup status checks, github didn&apos;t find any status checks right? That&apos;s not the case now. You can go back to your branch protection rules and in the <code>Require status checks</code> section, you should see a search input field where you can search for the job you created <strong>check_for_unittest</strong></p><figure class="kg-card kg-image-card"><img src="https://codewithgillis.com/content/images/2024/05/image-2.png" class="kg-image" alt="Ensure code from a Pull Request is unit tested before enabling merge with github actions" loading="lazy" width="768" height="273" srcset="https://codewithgillis.com/content/images/size/w600/2024/05/image-2.png 600w, https://codewithgillis.com/content/images/2024/05/image-2.png 768w" sizes="(min-width: 720px) 720px"></figure><p>After the search, click on it and it should be added to the list of checks before merging. Now you&apos;ll realise that when you make a PR into the main or develop branches, the worfklow will automatically run, and must pass before you can proceed to merge.</p><p><strong>Please note that our script doesn&apos;t handle the case where a unit test fails. You can definitely add that which will make it even better. Please do well to subscribe and leave a comment if you have updated the script to check for failed test or if you want me to update this article with updates on that</strong></p>]]></content:encoded></item><item><title><![CDATA[Coming soon]]></title><description><![CDATA[<p>This is CodeWithGillis, a brand new site by Sone GIllis that&apos;s just getting started. Things will be up and running here shortly, but you can <a href="#/portal/">subscribe</a> in the meantime if you&apos;d like to stay up to date and receive emails when new content is published!</p>]]></description><link>https://codewithgillis.com/coming-soon/</link><guid isPermaLink="false">664b844f4eb78e0001892be8</guid><category><![CDATA[News]]></category><dc:creator><![CDATA[Sone GIllis]]></dc:creator><pubDate>Mon, 20 May 2024 17:11:43 GMT</pubDate><media:content url="https://static.ghost.org/v4.0.0/images/feature-image.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://static.ghost.org/v4.0.0/images/feature-image.jpg" alt="Coming soon"><p>This is CodeWithGillis, a brand new site by Sone GIllis that&apos;s just getting started. Things will be up and running here shortly, but you can <a href="#/portal/">subscribe</a> in the meantime if you&apos;d like to stay up to date and receive emails when new content is published!</p>]]></content:encoded></item></channel></rss>