Day 28 & 29: Refactoring the Hell

Recently I hustled 21 days to create my school's website. Today, I pay what I have done and regret what have I failed to do ๐Ÿ˜‘.

ยท

4 min read

Play this article

TLDR;

I made so many code smells due to time constraints, in this article I go over how I refactored/santify the repo for further development. I highlight few areas and my approach. If you find better approach, comment your voice ๐Ÿ‘.

Short log is published previously:

Day 26: Filling the Gap

Day 27: Filling Gap II


Impure React Components

Originally I had the page hero image components like this...

export default function HeroImage({src}) {
    const imgSrc = sanitizeImageURL(src);
    // --snip--
    return (<img src={imgSrc} .../>);
}

Any React veteran knows what's wrong here. NextJs wise, it is terrible.

I happen to know this is bad, because I got server output doesn't match the client error.

This is because const imgSrc = sanitizeImageURL(src); runs in client (browser) but <img src={imgSrc} .../> runs in server and served. Somehow this produced unstable DOM, sometimes rendering what server gave and sometime rendering client-side result.

This behavior is because I didn't follow React guidelines. The component should be pure. Pure as in Function Programming.

A Pure Function

First of all, there's no such a thing as Pure function, what it really means in Functional Programming is, 1-to-1 function.

My HeroImage function is not injuective. Because in server src is put to img without any alteration. But in browser, src is put into sanitizeImageURL and img with sanitized URL is returned. Here you see, for same src value, depending on the context, my function points to 2 different outputs. This is prohibited in Functional Programming.

I still don't know why JavaScript skips the const... part in server though, after all I am just Backend Engineer, not Frondend (yet ๐Ÿ˜‰)

The React Way

For these reasones, react provides useEffect hook, that is use to run clientside codes and make our component injective. But, in my case, I can simply shift the snippet to getStaticProps and be done with it. Why?

  • since this is run in bundling time, now page can just load the preprocessed <img/> tag. A performance win ๐ŸŽ‰.

So, now the page looks like...

export default function HeroImage({src}) {
    return (<img src={src} .../>);
}

export function getStaticProps({slug}) {
    // --snip--
    page.map(p => p.feature_image = sanitizeImageURL(p.feature_image));
    // --snip--
}

DRY Misc Pages

The Page for academia, sports, clubs etc. are of same structure. They have

  • Title

  • Subtitle html content

  • Hero Image

  • Main body

And these are encapsulated by the Header & Footer. Only different being the URL pattern.

PREFIX/sectionType/[slug]

The [slug] is gained from the pointing URL but the sectionType is from directory. The structure of the directory tree is

But the underlying Page shares same logical structure, but each are independent. This ain't good for future developement. I should DRY this.

Extracting Pre-logic

The loading logic was to use the URL-friendly version of the text displayed in the Nav-Menu. So, first of all I extracted them to a separate file.

This has another added benefit. Changing the Nav-Menu is as just easy as commenting/adding new pages under the correct category. ๐Ÿ‘.

Then in each getStaticPaths for each section, I can just...

// pages/features/[feature].tsx
export function getStaticPaths() {
  return {
    paths: FEATURES.map((f) => ({
      params: { feature: parseURL(f) },
    })),
    fallback: false,
  };
}

And the helper function is just replacing whitespaces with hyphens and upper โžก left. Even though this could be just url.replaceAll(' ', '-').toLowerCase() but that didn't work out for me (idk why though ๐Ÿคทโ€โ™‚๏ธ). So, hand-craft it is ๐Ÿงค.

// misc/utils.ts
export function parseURL(url: string) {
  let newUrl = "";
  url = url.toLowerCase();

  for (let index = 0; index < url.length; index++) {
    const c = url[index];
    if (c === " ") {
      newUrl = newUrl + "-";
    } else {
      newUrl = newUrl + c;
    }
  }

  return newUrl;
}

The only changing part of this approach is just to change the FEATURES with the corresponding group and { feature: parseURL(f)} with relevant parameter. E.g. {section: parseURL(a)} for academia group.

Now the getStaticProps is

export async function getStaticProps({ params }: any) {
  const page = await fetchAndDecode(`pages/slug/${params.feature}`);
  const sanitizedPage = sanitizePage(page);

  return { props: { page: sanitizedPage } };
}

Only changing part is ${params.feature}, this can also be generalized as slug or key but, this is more descriptive than them. Here, sanitizePage just parsing the img.src and img.srcset for the aforementioned bug.

I know, that page:any part right? ๐Ÿ˜….

Then I created a Page custom component as

Then in each section I just

export default function Features(props: any) {
  return <Page props={{ ...props, category: "Features" }} />;
}

Only the category changes as per need. Now, it is DRY ๐Ÿ˜Ž.


Extro

That's all I can write without telling any much about private stuff. Hope this has been somewhat useful. Till we meet again it's me the BE, signing off ๐Ÿ‘‹.


ย