Photo by No Revisions on Unsplash
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 ๐.
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:
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 ๐.