<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mathis Garberg</title>
    <description>The latest articles on DEV Community by Mathis Garberg (@mathgarb).</description>
    <link>https://dev.to/mathgarb</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F205987%2F7779d2ed-fb9f-4fca-a5d2-84df4345abb5.jpg</url>
      <title>DEV Community: Mathis Garberg</title>
      <link>https://dev.to/mathgarb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mathgarb"/>
    <language>en</language>
    <item>
      <title>Build a Completely Dynamic UI with Sanity, a Node Server and React</title>
      <dc:creator>Mathis Garberg</dc:creator>
      <pubDate>Mon, 05 Aug 2024 19:35:29 +0000</pubDate>
      <link>https://dev.to/mathgarb/build-a-completely-dynamic-ui-with-sanity-a-node-server-and-react-1nj9</link>
      <guid>https://dev.to/mathgarb/build-a-completely-dynamic-ui-with-sanity-a-node-server-and-react-1nj9</guid>
      <description>&lt;p&gt;Building with LEGO has always been an interest of mine. Piecing together a 3-dimensional puzzle, either from the restrictions of a manual or within the creative sphere presents a unique blend of mental stimulation and a test of patience.&lt;/p&gt;

&lt;p&gt;This creativity in building different structures based on a core is a useful trait in many aspects of life, let alone web development. During this article, we’ll build on this concept to create a completely dynamic UI. Much like assembling LEGO blocks, we’ll piece together sections of content, where you’re in complete control of the final outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Concept
&lt;/h2&gt;

&lt;p&gt;So the idea is to perform an initial request to retrieve the application routes from Sanity, and dynamically configure a &lt;a href="https://reactrouter.com/en/main" rel="noopener noreferrer"&gt;React Router&lt;/a&gt; with them. These routes will along with the route name contain a reference to the dynamic page in question, and is used to retrieve the page along with it’s associated sections. Static content like the header and footer exists beyond the outlet, to remain consistent between route transitions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqrlvonkw4bs1oivz6yi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqrlvonkw4bs1oivz6yi.png" alt="Image description" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building our Sanity instance
&lt;/h2&gt;

&lt;p&gt;Since all the content for our application will be served from Sanity, it’s the logical place to start out. Sanity is a popular solution for serving content to consumer applications in a flexible and dynamic manner. You can follow the instructions on &lt;a href="https://www.sanity.io/learn/course/day-one-with-sanity-studio/prerequisites" rel="noopener noreferrer"&gt;this&lt;/a&gt; page on how to set it up.&lt;/p&gt;

&lt;p&gt;Once the initial setup is complete, add the following folder structure inside the Sanity application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;|-- ...
|-- schemas
     |-- documents
          |- application.ts
     |-- dynamicPage
          |-- sections
               |- SectionOne.ts
               |- SectionTwo.ts
               |-- ...
               |- index.ts
          |- dynamicPage.ts
|-- objects
      |- dynamicRoute.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Sections&lt;/em&gt; does, as the image above describes, define an isolated section of content on the page. This could be everything from a &lt;em&gt;HeroBanner&lt;/em&gt; on the top of the page to a simple heading, and will look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// schemas/dynamicPage/sections/sectionOne.ts
export const sectionOne = defineField({
  title: "Section One",
  name: "sectionOne",
  type: "object",
  fields: [
    defineField({
      title: "Properties",
      name: "properties",
      type: "object",
      fields: [
         defineField({
            title: "Heading",
            name: "heading",
            type: "string",
            description: "The heading for the banner.",
          }),
      ]
    })
  ]
});

// schemas/dynamicPage/sections/sectionTwo.ts
export const sectionTwo = defineField({
  title: "Section Two",
  name: "sectionTwo",
  type: "object",
  fields: [
    defineField({
      title: "Properties",
      name: "properties",
      type: "object",
      fields: [
         defineField({
            title: "Heading",
            name: "heading",
            type: "string",
            description: "The heading for the banner.",
          }),
      ]
    })
  ]
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;properties&lt;/em&gt; naming will be familiar for those with a React background, as the idea is to pass these values as props to the component in question.&lt;/p&gt;

&lt;p&gt;Then we need to create an array field to export these sections. We’ll also add a &lt;em&gt;sort&lt;/em&gt; with a &lt;em&gt;localCompare fn&lt;/em&gt; to sort them alphabetically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// schemas/dynamicPage/sections/sections.ts
import {sectionOne} from "./sectionOne"
import {sectionTwo} from "./sectionTwo"
import {defineField} from "sanity"

const sectionsByName= [
  sectionOne,
  sectionTwo
].sort((a, b) =&amp;gt; a.title!.localeCompare(b.title!))

export const sections = () =&amp;gt;
  defineField({
    title: "Sections",
    name: "sections",
    type: "array",
    of: sectionsByName,
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A dynamic page is comprised of one or more sections in any particular order, and does also include a title for ease of reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// schemas/dynamicPage/dynamicPage.ts
import {sections} from "./sections/sections"

export const dynamicPage = defineField({
  title: "Dynamic Pages",
  name: "dynamicPage",
  type: "document",
  fields: [
      defineField({
        title: "Title",
        name: "title",
        type: "string"
      }),
     sections()
    ]
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;dynamic route&lt;/em&gt; will contain all our application routes with references to the dynamic page(s), and lives within the application document, which outlies the structure of the entire application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// objects/dynamicRoute
export const dynamicRoute = defineField({
  title: "Dynamic Route",
  name: "dynamicRoute",
  type: "object",
  fields: [
     defineField({
        title: "Path",
        name: "path",
        type: "string",
        description:
          "The path this route should match. Supports all route expressions that expressjs supports.",
        validation: (Rule) =&amp;gt;
          Rule.required().custom((path: string | undefined) =&amp;gt;
            (path || "").startsWith("/") ? true : "Path must start with /"
          ),
      }),
      defineField({
        title: "Properties",
        name: "properties",
        type: "object",
        fields: [
          defineField({
            title: "Reference page",
            name: "page",
            type: "reference",
            to: [{type: "dynamicPage"}],
          })
        ]
      }),
  ]
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// schemas/documents/application.ts
export const application = defineField({
    name: "application",
    type: "document",
    description: "Describes the central configuration for an application",
    fields: [
        defineField({
          title: "Application name",
          name: "name",
          type: "string",
          validation: (Rule) =&amp;gt; Rule.required()
        }),
        defineField({
          title: "Description",
          name: "description",
          type: "string",
          fieldset: "info",
          description: "A short description of this application.",
        }),
        defineField({
          title: "Routes",
          name: "routes",
          type: "array",
          of: [dynamicRoute],
          options: {
            layout: "list",
          },
        })
    ]
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we need to register our documents among the &lt;em&gt;schemaTypes&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// schemaTypes/index.ts
import { application } from "../schemas/documents/application";
import { dynamicPage } from "../schemas/dynamicPage/dynamicPage";

export const schemaTypes = [application, dynamicPage]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding Content
&lt;/h2&gt;

&lt;p&gt;Having the CMS instance up and running is as well and good, but we still need to add some content.&lt;/p&gt;

&lt;p&gt;Start with adding a dynamic page document with two sections — &lt;em&gt;SectionOne&lt;/em&gt; and &lt;em&gt;SectionTwo&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Then we need to create an application and add a route with a reference to the dynamic page.&lt;/p&gt;

&lt;p&gt;Finally, publish the changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Node Server with GROQ Queries
&lt;/h2&gt;

&lt;p&gt;Now that our Sanity instance is finished, we need to consume the content. Here, we’ll use a Node Server with a Sanity client to perform GROQ queries towards the instance. Then we’ll expose the content to the client through endpoints created with the &lt;em&gt;Express framework&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Start with &lt;a href="https://philna.sh/blog/2019/01/10/how-to-start-a-node-js-project/" rel="noopener noreferrer"&gt;initializing a new Node application&lt;/a&gt; and install the following packages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init -y

npm install express @sanity/client cors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we want to use TypeScript here as well, we need to configure it within the application through the following command, along with installing the necessary types.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx tsc --init

npm i -D typescript ts-node @types/node @types/express @types/cors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Continue with building up the following structure on the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;|-- queries
|    |- SanityDynamicRoutesQuery.ts
|    |- SanityDynamicPageQuery.ts
|- start.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;start.ts&lt;/em&gt; file will be the entry point of the server and contain our endpoints along with CORS configurations for the client-origin.&lt;/p&gt;

&lt;p&gt;In order to create workable Sanity queries, we need some additional configurations, in order to establish a connection towards our instance. This is achieved by adding the desired configurations inside the &lt;em&gt;createClient&lt;/em&gt; method, and is all available in Sanity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// start.ts
import {createClient} from '@sanity/client'
import { SanityDynamicPageQuery } from './queries/SanityDynamicPageQuery'
import { SanityDynamicRoutesQuery } from './queries/SanityDynamicRoutesQuery'
import { Request, Response } from 'express'
import cors from "cors";

export const client = createClient({
  projectId: 'your-project-id',
  dataset: 'your-dataset',
  useCdn: false, // set to `false` to bypass the edge cache
  apiVersion: '2024-02-29', // use current date (YYYY-MM-DD) to target the latest API version
})

var express = require('express')
var app = express()

const allowedOrigins = ['https://localhost:5173']; // standard Vite port

const options: cors.CorsOptions = {
  origin: allowedOrigins
};

app.use(cors(options));

app.get('/api/v1/dynamicRoutes', async function  (req: Request, res: Response) {
  var dynamicRoutes = await SanityDynamicRoutesQuery();

  return res.send(dynamicRoutes);
})

app.get('/api/v1/dynamicPage/:id', async function  (req: Request, res: Response) {
  var dynamicPage = await SanityDynamicPageQuery(req.params.id)

  return res.send(dynamicPage);
})

app.listen(3000, () =&amp;gt; {
  console.log(`[server]: Server is running at http://localhost:3000`);
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting with the &lt;em&gt;SanityDynamicRouteQuery&lt;/em&gt;, we need to perform a GROQ query to retrieve the data from Sanity. You can experiment with these queries yourself using the &lt;a href="https://www.sanity.io/docs/the-vision-plugin" rel="noopener noreferrer"&gt;Sanity Vision Plugin&lt;/a&gt; in the CMS instance.&lt;/p&gt;

&lt;p&gt;The query below will return all published dynamic routes of the application in question, excluding draft documents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// queries/SanityDynamicRoutesQuery.ts
export const SanityDynamicRoutesQuery = async () =&amp;gt; {
  const groqQuery = `
    *[!(_id in path('drafts.**')) &amp;amp;&amp;amp; _type == "application"][0]{routes}`;

  const data = await client.fetch(groqQuery);

  return data
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the &lt;em&gt;SanityDynamicPageQuery&lt;/em&gt; file. This will query the dynamic page using the reference id from the output in the &lt;em&gt;SanityDynamicRoutesQuery&lt;/em&gt;, and return a list sections with it’s associated properties.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// queries/SanityDynamicPageQuery.ts
export const SanityDynamicPageQuery = async (id: string) =&amp;gt; {
  const groqQuery = `*[
  _type == "dynamicPage"
  &amp;amp;&amp;amp; (_id == '${id}')]{
   id{current},
   sections{
     _type,
     properties,
   },
   ...
  }[0]`

  const data = await client.fetch(groqQuery);

  return data;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the server and call both of these endpoints with &lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt; or a similar tool to verify that everything works as intended.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx ts-node start.ts

[server]: Server is running at http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building The Dynamic UI
&lt;/h2&gt;

&lt;p&gt;Starting with the recent stable version of Node, we’ll use the following command to initialize our application with &lt;em&gt;Vite&lt;/em&gt;, &lt;em&gt;React&lt;/em&gt; and &lt;em&gt;TypesScript&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm create vite@latest

&amp;gt; React
&amp;gt; TypeScript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the command is finished, verify everything works by initializing the application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will initialize a local dev-server on http. In order to handle local certificates with Vite, use the &lt;a href="https://github.com/liuweiGL/vite-plugin-mkcert" rel="noopener noreferrer"&gt;vite-plugin-mkcert&lt;/a&gt; with the following configuration options.&lt;/p&gt;

&lt;p&gt;We also utilize this opportunity to install the &lt;em&gt;react-router-dom&lt;/em&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i react-router-dom
npm i -D vite-plugin-mkcert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// vite.config.ts
import {defineConfig} from'vite'
import mkcert from 'vite-plugin-mkcert'

export default defineConfig({
  plugins: [react(), mkcert()]
  server: {
    https: true
  },
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bare bone application should now be up and running on https after initialization, and we can start adding the following structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;|-- features
      |--  DynamicPage
            |- DynamicPageWrapper.tsx
            |- DynamicPage.tsx
            |- SectionErrorBoundary.tsx
      |-- Sections
            |-- SectionOne
                  |- SectionOne.tsx
                  |- SectionOne.module.scss
                  |- index.ts
            |-- SectionTwo
                  |- SectionTwo.tsx
                  |- SectionTwo.module.scss
                  |- index.ts
      |-- Layout
            |- MainContainer.tsx
            |- Footer.tsx
            |- Header.tsx
            |- NotFound.tsx
      |- index.ts
|-- routes
      |- routesConfig.ts
|-- interfaces
      |- IDynamicSection.ts
|- App.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting with the interfaces, we need to add several of them to handle the dynamic content. The &lt;em&gt;DynamicSectionFinder&lt;/em&gt; is an interface for a mapper function, and will be explain in further details later on. The &lt;em&gt;IDynamicRoute&lt;/em&gt; is the interface we’ll use for the dynamic routes, and includes a path and properties. The &lt;em&gt;IDynamicSectionProps&lt;/em&gt;, &lt;em&gt;IDynamicSection&lt;/em&gt; and &lt;em&gt;IDynamicSectionValues&lt;/em&gt; are all interfaces related to sections within a dynamic page, and will be made clearer as we move on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// interfaces/IDynamicSectionFinder
export type DynamicSectionFinder = 
  &amp;lt;T&amp;gt;(name: string) =&amp;gt; IDynamicSectionValues&amp;lt;T&amp;gt; | undefined;

// interfaces/IDynamicRoute
export interface IDynamicRoute&amp;lt;Properties = any&amp;gt; {
    path: string;
    properties: Properties;
}

// interfaces/IDynamicSection
export interface IDynamicSectionProps&amp;lt;T&amp;gt; {
    properties: T;
}

// interfaces/IDynamicSection
export interface IDynamicSection&amp;lt;Properties&amp;gt; {
    name: string;
    displayComponent: React.ComponentType&amp;lt;IDynamicSectionProps&amp;lt;Properties&amp;gt;&amp;gt;;
}

// interfaces/IDynamicSectionValues
export interface IDynamicSectionValues&amp;lt;Properties = any&amp;gt; {
    _type: string;
    properties: Properties;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving on to the &lt;em&gt;routesConfig.ts&lt;/em&gt; file, add the following content inside.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {NotFound} from "../features/NotFound/NotFound.tsx"

export const routesConfig = [
  {
    path: "*",
    element: &amp;lt;NotFound /&amp;gt;
  },
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, only one route for now, as the other ones will be added dynamically.&lt;/p&gt;

&lt;p&gt;Then we move over to the deepest level of our dynamic structure, the sections. Starting with &lt;em&gt;SectionOne&lt;/em&gt;- and &lt;em&gt;Two&lt;/em&gt;, add the following content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// features/sections/SectionOne/SectionOne.tsx
import { IDynamicSectionProps } from "../../../interfaces/IDynamicSection";

export interface ISectionOne {
  heading: string;
}

export const SectionOne = ({
  properties,
}: IDynamicSectionProps&amp;lt;ISectionOne&amp;gt;) =&amp;gt; {
  const { heading } = properties;
  return &amp;lt;h1&amp;gt;{heading}&amp;lt;/h1&amp;gt;;
};

// features/sections/SectionTwo/SectionTwo.tsx
import { IDynamicSectionProps } from "../../../interfaces/IDynamicSection";

export interface ISectionTwo {
  heading: string;
}

export const SectionTwo = ({
  properties,
}: IDynamicSectionProps&amp;lt;ISectionTwo&amp;gt;) =&amp;gt; {
  const { heading } = properties;
  return &amp;lt;h1&amp;gt;{heading}&amp;lt;/h1&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we’ve added our sections, we’ll move over to the &lt;em&gt;index file&lt;/em&gt; of each section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// features/Sections/SectionOne/index.ts
import { IDynamicSection } from "../../../interfaces/IDynamicSection"
import {SectionOne, ISectionOne} from "./SectionOne"

const sectionOne: IDynamicSection&amp;lt;ISectionOne&amp;gt; = {
  name: "sectionOne",
  displayComponent: SectionOne
}

export default sectionOne


// features/Sections/SectionTwo/index.ts
import { IDynamicSection } from "../../../interfaces/IDynamicSection"
import {SectionTwo, ISectionTwo} from "./SectionTwo"

const sectionTwo: IDynamicSection&amp;lt;ISectionTwo&amp;gt; = {
  name: "sectionTwo",
  displayComponent: SectionTwo
}

export default sectionTwo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that we’ve defined a name for the section along with the &lt;em&gt;displayComponent&lt;/em&gt; property which are referencing the component.&lt;/p&gt;

&lt;p&gt;Then we need to create a mapper function, to be able to retrieve the section depending on the name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// features/Sections/index.ts
import {IDynamicSection} from "../../interfaces/IDynamicSection"

import sectionOne from "./SectionOne"
import sectionTwo from "./SectionTwo"

export const appSections: {[key: string]: IDynamicSection&amp;lt;any&amp;gt;} = {
  - [sectionOne.name]: sectionOne,
  - [sectionTwo.name]: sectionTwo
}

export const findSectionByName = (name: string) =&amp;gt; appSections[name];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we move on to the &lt;em&gt;DynamicPage&lt;/em&gt; component, responsible of rendering each section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// features/DynamicPage/DynamicPage.tsx
import React from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { IDynamicSectionValues } from "../../interfaces/IDynamicSection";
import { IDynamicPage } from "../../interfaces/IDynamicPage";
import { SectionErrorBoundary } from "./SectionErrorBoundary";
import { findSectionByName } from "../Sections";

interface RenderSectionErrorProps {
  title: string;
  message: string;
  section: IDynamicSectionValues;
}

const RenderSectionError = (props: RenderSectionErrorProps) =&amp;gt; (
  &amp;lt;div&amp;gt;
    &amp;lt;h2&amp;gt;{props.title}&amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;{props.message}&amp;lt;/p&amp;gt;
    &amp;lt;h3&amp;gt;Section&amp;lt;/h3&amp;gt;
    &amp;lt;pre&amp;gt;
      &amp;lt;code&amp;gt;{JSON.stringify(props.section, null, 2)}&amp;lt;/code&amp;gt;
    &amp;lt;/pre&amp;gt;
  &amp;lt;/div&amp;gt;
);

export interface IDynamicPageProps {
  dynamicPage: IDynamicPage;
  showHandlerNotFoundError?: boolean;
  showGenericSectionErrors?: boolean;
}

export const DynamicPage = (props: IDynamicPageProps) =&amp;gt; {
  const { showHandlerNotFoundError = true, showGenericSectionErrors = true } =
    props;

  const navigate = useNavigate();
  const location = useLocation();
  const params = useParams();

  const RenderSection = (section: IDynamicSectionValues) =&amp;gt; {
    try {
      const sectionHandler = findSectionByName(section._type);

      if (!sectionHandler) {
        console.error("SectionHandler not found for", section._type);
        return showHandlerNotFoundError ? (
          &amp;lt;RenderSectionError
            title="Not found"
            message="The section was not found"
            section={section}
          /&amp;gt;
        ) : null;
      }

      const sectionProps = {
        navigate,
        location,
        params,
        properties: section.properties || {},
      };

      const SectionHandler =
        sectionHandler.displayComponent as React.ElementType;

      return (
        &amp;lt;SectionErrorBoundary&amp;gt;
          &amp;lt;SectionHandler {...sectionProps} /&amp;gt;
        &amp;lt;/SectionErrorBoundary&amp;gt;
      );
    } catch (error) {
      console.error("Error rendering DynamicPage section", section, error);

      return showGenericSectionErrors &amp;amp;&amp;amp; error instanceof Error ? (
        &amp;lt;RenderSectionError
          section={section}
          title="Render error (check console)"
          message={error.message}
        /&amp;gt;
      ) : null;
    }
  };

  return (
    &amp;lt;article data-page-id={props.dynamicPage.id}&amp;gt;
      {(props.dynamicPage.sections || []).map((section, idx) =&amp;gt; (
        &amp;lt;React.Fragment key={idx}&amp;gt;
          &amp;lt;RenderSection {...section} /&amp;gt;
        &amp;lt;/React.Fragment&amp;gt;
      ))}
    &amp;lt;/article&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start out by adding the &lt;em&gt;RenderSectionError&lt;/em&gt; component, which is a component we’ll display when there’s a problem rendering a section. This component will contain some information about which section has trouble rendering, along with the associated JSON output for debugging purposes.&lt;/p&gt;

&lt;p&gt;So the &lt;em&gt;DynamicPage&lt;/em&gt; component have a component within - &lt;em&gt;RenderSection&lt;/em&gt;. The &lt;em&gt;RenderSection&lt;/em&gt; component is, as the name states, responsible of rendering a section, and accepts a section-data retrieved from Sanity. Then, within a try-catch block, we use the &lt;em&gt;sectionFinder mapping fn&lt;/em&gt; to find the specific section.&lt;/p&gt;

&lt;p&gt;In addition to the props we forward for each section, we include some additional meta information for each page. The &lt;em&gt;displayComponent&lt;/em&gt; is then retrieved from the sectionHandler and returned.&lt;/p&gt;

&lt;p&gt;One level above the &lt;em&gt;DynamicPage&lt;/em&gt; we’ve now created is the &lt;em&gt;DynamicPageWrapper&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// features/DynamicPage/DynamicPageWrapper.tsx
import { useEffect, useState } from "react";
import { IDynamicRoute } from "../../interfaces/IDynamicRoute";
import { DynamicPage, IDynamicPageProps } from "./DynamicPage";
import { NotFound } from "../Layout/NotFound";

interface IDynamicPageWrapperProps {
  route: IDynamicRoute;
}
const DynamicPageWrapper = (props: IDynamicPageWrapperProps) =&amp;gt; {
  const pageId: string = props.route.properties.page._ref;
  const [dynamicPage, setDynamicPage] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  async function fetchDynamicPage(id: string) {
    return fetch(`http://localhost:3000/api/v1/dynamicPage/${id}`)
      .then((response) =&amp;gt; response.json())
      .then((data) =&amp;gt; {
        setDynamicPage(data);
      })
      .finally(() =&amp;gt; setIsLoading(false));
  }

  useEffect(() =&amp;gt; {
    if (!dynamicPage) {
      fetchDynamicPage(pageId);
    } else {
      setIsLoading(false);
    }
  }, []);

  const RenderPage = () =&amp;gt; {
    if (isLoading) {
      return &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;;
    }

    if (!dynamicPage) {
      return &amp;lt;NotFound /&amp;gt;;
    }

    const newProps: IDynamicPageProps = {
      dynamicPage,
    };

    return &amp;lt;DynamicPage {...newProps} /&amp;gt;;
  };

  return &amp;lt;RenderPage /&amp;gt;;
};

export default DynamicPageWrapper;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This component has a reference to the page being rendered, which is used to retrieve it’s content through a fetch request. The result of this request along with the &lt;em&gt;sectionFinder fn&lt;/em&gt; is then passed down to the &lt;em&gt;DynamicPage&lt;/em&gt; as props.&lt;/p&gt;

&lt;p&gt;The content of the &lt;em&gt;Layout&lt;/em&gt; folder will act as the static wrapper of our dynamic content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// features/Layout/Footer.tsx
export const Footer = () =&amp;gt; {
  return &amp;lt;footer&amp;gt;Static Footer&amp;lt;/footer&amp;gt;
}

// features/Layout/Header.tsx
export const Header = () =&amp;gt; {
  return &amp;lt;header&amp;gt;Static Header&amp;lt;/header&amp;gt;
}

// features/Layout/MainContainer.tsx
export const MainContainer = () =&amp;gt; {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Header /&amp;gt;
      &amp;lt;Outlet /&amp;gt;
      &amp;lt;Footer /&amp;gt;
    &amp;lt;/&amp;gt;  
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to finish it all up is the &lt;em&gt;App component&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// App.tsx
import { useEffect, useState } from "react";
import "./App.css";
import { IDynamicRoute } from "./interfaces/IDynamicRoute";
import DynamicPageWrapper from "./features/DynamicPage/DynamicPageWrapper";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { MainContainer } from "./features/Layout/MainContainer";
import { routesConfig } from "./routes/routesConfig";

interface IRouteHandlerProps {
  route: IDynamicRoute;
}

async function fetchRoutes() {
  return fetch("http://localhost:3000/api/v1/dynamicRoutes")
    .then((response) =&amp;gt; response.json())
    .then((data) =&amp;gt; data)
    .catch((error) =&amp;gt; console.error("Error:", error));
}

function App() {
  const [routes, setRoutes] = useState([]);

  const renderRoute =
    (route: IDynamicRoute): React.ElementType =&amp;gt;
    (routeProps: IRouteHandlerProps) =&amp;gt; {
      return &amp;lt;DynamicPageWrapper {...routeProps} route={route} /&amp;gt;;
    };

  const RenderDynamicRoutes = routes.map((route: IDynamicRoute) =&amp;gt; {
    const currentRoute = route.path.toString();
    const RenderedRoute = renderRoute(route);

    return {
      path: currentRoute,
      element: &amp;lt;RenderedRoute /&amp;gt;,
    };
  });

  useEffect(() =&amp;gt; {
    const fetchData = async () =&amp;gt; {
      const data = await fetchRoutes();
      setRoutes(data.routes);
    };

    fetchData();
  }, []);

  return (
    &amp;lt;RouterProvider
      router={createBrowserRouter([
        {
          path: "/",
          element: &amp;lt;MainContainer /&amp;gt;,
          children: [...RenderDynamicRoutes, ...routesConfig],
          errorElement: &amp;lt;div&amp;gt;ERROR&amp;lt;/div&amp;gt;,
        },
      ])}
      fallbackElement={
        &amp;lt;div&amp;gt;
          &amp;lt;span&amp;gt;Loading...&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
      }
    /&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, we retrieve the routes through a fetch request, which is then added to the component state. Then we have the &lt;em&gt;RouteProvider&lt;/em&gt; from React Router which uses the &lt;em&gt;createBrowserRouter&lt;/em&gt; method to construct our dynamic routes with the &lt;em&gt;RenderDynamicRoutes&lt;/em&gt; mapper.&lt;/p&gt;




&lt;p&gt;My name is Mathis Garberg, and I am a consultant at Dfind Consulting in Oslo, Norway.&lt;/p&gt;

</description>
      <category>sanity</category>
      <category>node</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How To Read Code?</title>
      <dc:creator>Mathis Garberg</dc:creator>
      <pubDate>Sat, 03 Aug 2019 11:20:47 +0000</pubDate>
      <link>https://dev.to/mathgarb/how-to-read-code-43hh</link>
      <guid>https://dev.to/mathgarb/how-to-read-code-43hh</guid>
      <description>&lt;h2&gt;Background&lt;/h2&gt;

&lt;p&gt;As developers, we spend the majority of our time reading and trying to understand someone else's code. Specially in the consultant industry, you're introduced to several enterprise solutions with a large existing code-base, where you're now the maintainer, expected to provide deliverables on a timeline of code you didn't write. This can be challenging for even the most experienced developers, and you will need good code-browsing skills to make sense of the code.&lt;/p&gt;

&lt;p&gt;By utilizing a few popular code-browsing techniques I'll explain throughout this article, you'll soon know everything you need to approach a large existing code-base.&lt;/p&gt;

&lt;h2&gt;The Problem&lt;/h2&gt;

&lt;p&gt;In ideal world, all code would be well written, documented, structured and comprehensibly tested, both with automatic tools such as unit tests and end-to-end tests. &lt;/p&gt;

&lt;p&gt;However, we don't live in an ideal world. We live in the real world, where code never dies and programmers come and go, which leads to people spending an enourmous amount of time reading code. This makes understanding unfamiliar code all the more important. Probably more important than writing code. Unfortunately, this skill is widely undertaught in school and not that well-recognized in the industry, which leads to people reading programs like they were books, trying to connect every piece of the puzzle. This is not the right frame of mind. You don't want to think of the scope to which you don't understand something, but instead, gradually start connecting the pieces until they fit.&lt;/p&gt;

&lt;h2&gt;The Solution&lt;/h2&gt;

&lt;p&gt;So, what are you going to do when you're faced with a brand new codebase? Requirement gathering is usually the first stage in any big project, and talking with the prospective users of the system is always an important part of that. In order to fix the program, you first need to understand the problems it's trying to solve. There's usually a reason for why things are done a certain way. Remember. Dealing with other people's code is the norm, not the exception, and becoming adept and figuring out how the other programmers worked are an important part of that. By adopting their  style when continuing their work, you'll also make it easier for the next developer to follow.&lt;/p&gt;

&lt;p&gt;When we now something about the purpose and style the application is developed in, it's time to start breaking things down. We need to narrow things down to something we can actually process. By understanding the various components in the software, and how they relate to each other, you'll start figuring out how different parts are connected and start developing an understanding of the codebase as a whole.&lt;/p&gt;

&lt;p&gt;So, how do we start breaking things down? One method the community strongly suggests is to set out breakpoint on methods that triggers behaviours that concerns you. If changing a line of code causes unexpected changes on other places, put a breakpoint on that line, and see what happens. You have a lot of information on the runtime state once the breakpoint hits. Breakpoints stops the execution of the code on a certain line, and from there, you can see from the stacktrace how the code got there. Use the step into and step out functions to see what the different methods does and how events are propogated in the application. This will teach you something about the structure of the codebase.&lt;/p&gt;

&lt;p&gt;One other thing I also need to mention is the importance of testing. Cause although they are rare, they're a great resource for learning what a certain unit of code is meant to do. By reading and writing tests, you'll start understanding the expected behavior of the program, and can then compare that with the actual result. The more of those you build, the more confidence you'll have that your added code won't break something else.&lt;/p&gt;

&lt;h2&gt;Summary&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Start by gathering information about which problems the program should solve and which code style to adapt.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Don't spend all your time figuring out how everything works at the beginning. Narrow things down to something you can actually process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Step through behavior that concerns you with a debugger. The stack trace have a lot of information about how events are propogated in the application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Although testing is rare, it acts as a wonderful documentation tool, and is a good introduction to a new codebase.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a final note, I really would like you to share you're best methods for approaching unfamiliar code. This is a widely discussed topic, and there is no blueprint here. &lt;/p&gt;

&lt;p&gt;Just don't spend all your time explaining your problems to a rubber duck. People will think you're insane 😉&lt;/p&gt;

</description>
      <category>codequality</category>
      <category>techtalks</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
