As coding is often self taught, one aspect of design that doesn’t have a direct doctrine is code. Layouts of good projects are designed to be easy to read for another coder, with clear and concise documentation supporting the project allowing anybody with the appropriate coding language skills to pick up and use or adapt the code as required.
I personally code using Next.js for the majority of my projects. Next.js is based on the React library for JavaScript. And largely works on combining HTML with JavaScript in objects referred to as components.
While there are many guides on how to create projects, there is less information on how to structure said projects, indeed there is also no real consensus on a correct way to organise projects. WebDev Simplified has a comprehensive guide Two structuring react projects for beginners, intermediates, and advanced users. And while I like it I do feel it becomes a bit complicated. React developers themselves offer suggestions but no real consensus of what to do beyond don’t make it too complicated.
Clean Code is a book written by Robert C. Martin based on his experiences as a software developer. It ascribes to coding standards when designing code structure that allows for readability, changeability maintenance and extensibility.
Clean Code principles are adaptable and can work in a number of coding languages and frameworks. There are many guides for implementing clean code with React, but I find that they often lack a fully detailed approach.
Here I will scribe my approach to clean code when creating a Next.js project using the App router.
Next.js has a basic folder structure that changed in Next13 with the introduction of the Apps router. Here we have the builder folder .next and node_modules that aren’t really interacted with by front end developers.
The public folder can be used for static resources such as images that are to be sourced by various components in the Apps folder. The app folder in src folder contains the majority of the general code for Next.js applications.
Next.js has routing logic that allows you to store various folders in the app folder that will not generate routes as long as a page.jsx file is not present in the folder. While this is great for ease of use, it can very quickly make the folder unclear, with difficulty at first glance discerning page routes from other folders. For example if I was to have an api specific folder for api calls, a component folder for React components, a hooks folder for custom react hooks and 3 page folders, About, Blog, Shopping we would have a folder structure like this:
Even with capitalization of route folders, they’re spread out between other folders, making it hard to glance at the pages. Using Clean Code principles I’d say that this structure lacks readability, although it has changeability, maintenance and extensibility.
I never really found a structure I was happy with for about half a year, before I came across this postby Mahmoud Taghinia on the mighty stack overflow about what he’d settled on for code structure. Immediately without even reading his post it was inherently obvious to me what his folder structure was attempting to achieve, the readability was vastly improved, while the folder structural complexity was barely altered.
For my example, we’ll rename the api, components and hooks folders to have an underscore (“_”) before their name to put them to the top of the folder list and make them private. As described in the Next.js folder this helps separate the UI logic from routing logic. Then we’ll put all pages in a (routes) folder using the route group functionality. This route folder has no impact on routing structure, it’s purely for our perspective, and putting all pages in this folder just adds a visual clarity about what’s a page folder, and what’s not.
The final folder structure looks like:
Using features native to Next.js it’s possible to organise a project folder in a manner that greatly improves readability, while maintaining a flat folder structure ensures maintenance remains simple, it also promotes changeability and extensibility as new hooks and pages can be placed in their respective folders. I feel that this folder structure follows the tenants of clean code.
One of the key features of the App folder is that pages are now organised in folders. The core page is stored in the page.jsx, replacing the previous method of the page folder with the files name also serving as its route. For me this makes folder structure easier to interpret and has the advantage of various pages being able to be far more self-contained, effectively trying to bridge the multi repository microservices popularised by architecture patterns such as JAMstack into a monolithic single-repository approach.
I recognize that this approach might prove challenging to satisfy everyone's preferences. Personally, I lean towards compartmentalisation, and the app folder aligns well with this perspective.
React is generally attributed to providing a single page approach to coding, with the potential for all the code, be it HTML, JavaScript or CSS to be contained within a single file. However, that can rapidly make the file huge and unreadable.
It’s therefore not uncommon for React projects to have multiple files that import components into the main.js file which is the central file in any React component (the page.jsx in next.js).
I try to specifically layer React components into a three layer approach summarised as:
This layer manages the overall structure of the application and its user interface, manages routing, global state, and high-level components. The core responsibility of this layer is controlling the application's layout, navigation, and interaction flow.
Thus I keep the first page as a UI layout and state management layer. Due to React's state management layer, some states need to be kept in the highest component layer, but not always. While additional layers of code could be implemented to create truer single responsibility, it would start to create Deep Nesting issues, as such I see this as an acceptable compromise.
Here is an example of my preferred page.jsx setup, this example is for this blog:
import "src/app/blog.css"
import BlogNavBar from "src/app/_components/BlogNavBar";
import BlogAuthor from "src/app/_components/BlogAuthor";
import BlogHeader from "src/app/_components/BlogHeader";
import BlogList from "src/app/_components/BlogList";
import BlogSideBar from "src/app/_components/BlogSideBar";
function App() {
const url = "https://script.google.com/macros/s/AKfycbyEGQAcQLaDqj1NW48B9jMk_br8Otl1jjRjd-lgXkKAoXyJxw166HSYwGuFIY0lQfRx/exec";
return (
<>
<BlogNavBar />
<div className="blog-grid">
<BlogHeader />
<BlogList url={url} />
<BlogSideBar url={url} />
<BlogAuthor />
</div>
</>
);
}
export default App;
In this case I devised that the blog has 5 components, a Navbar that sits above the rest of the UI design and then a Header with a hero image, a List of blogs which requires an API call to a url, a sidebar that uses the same API call, and an Author about section.
This setup allows for this layer to just focus on page layout and a centralised repository for the url as two components require it. Due to how Next.js handles data Fetching by collating all duplicate fetches into a single request, just pass the url. If this was to be a React project I would make the fetch request here and pass the results down in props.
This layer consists of individual components responsible for rendering specific UI elements and behaviours. Components in this layer focus on presentation and UI rendering, often using props to receive data and callbacks, or using a third layer to derive required data..
An example of this is this blog's sidebar feature:
import { use } from "react";
import googleWebFetch from "../../apis/googleWebFetch";
import Link from "next/link";
export default function BlogSideBar(props) {
const url = props.url;
const data = use(googleWebFetch(url));
const blogs = [];
for (let blog in data) {
const date = data[blog].fileDate.toString().slice(0, 10);
const title = data[blog].title;
const fileId = data[blog].fileId;
let returnValue = (
<Link
href={`blog/${encodeURIComponent(fileId)}`}
className="sidebar-wrapper"
key={`blog-${blog}`}
>
<p className="sidebar-title sidebar-text">{title}</p>
<p className="sidebar-date sidebar-text">{date}</p>
</Link>
);
blogs.push(returnValue);
}
return (
<div className="blog-sidebar">
<div className="blog-sidebar-header">
<h1>Index</h1>
</div>
<div>{blogs}</div>
</div>
);
}
The components name, BlogSideBar, indicates its purpose clearly. A React functional component, its primary responsibility is to render a list of blog posts in a sidebar layout. It handles specific UI elements and uses a 3rd layer for data fetching via a customised url prop passed from the parent component. Data is presented conditionally based on the fetch result.
While single responsibility principles would preferentially take the data mapping into a separate function in the service layer, I personally don’t see a readability issue due to the small size of the code. Should this component be larger or more complex, then I would employ a greater level of subfunctions and put the mapping function into its own function.
This layer contains custom hooks, utility functions, such as RESTful data fetches, and shared functions. An example of this layer is the Google API fetch in my blog:
export default async function googleWebFetch(url) {
const response = await fetch(url, { cache: 'no-store' });
const data = await response.json();
return data;
}
This function is a single responsibility principle
Modern day react utilises functional components, and in many ways has several features that makes JavaScript operate like a functional programming language. React has been using functional based hooks for a while now, and they have largely replaced class based components due to their more concise syntax improving readability and usability. When developing React Components I try to ascribe to the following principles.
This is one of those statements that is easy to understand, but actually can be quite hard to do because the single responsibility principle can lead to the need for multiple sub functions.
My general approach is to have the return value of the function to be one of the first declared variables, even if it’s initially an empty variable. In long procedural functions, with many temporary variables and subfunctions I like to use the variable name returnValue as the return value, for example in my blog fetch code I have a sub function called getData in which I setup the return value as an empty array in my variables at the start of the code.
The function itself is named logically in camelCase which is a standard for javascript variables, note this isn’t actually a React Component, rather a Node.js function, however I think it’s the best real world instance I have to hand.
function getData(fileId) {
try {
const gdoc = Docs.Documents.get(fileId);
const body = gdoc.body;
const listOfContent = body.content;
const returnValue = [];
// Parse data from the paragraphs of the doc
for (i = 0; i < listOfContent.length; i++) {
if (listOfContent[i].table) {
const table = getDataFromTable(listOfContent[i].table)
returnValue.push(table);
}
if (listOfContent[i].paragraph) {
if (listOfContent[i].paragraph.elements) {
let paragraph = getDataFromParagraph(listOfContent[i].paragraph, gdoc)
if (paragraph != null) {
returnValue.push(paragraph);
}
}
}
}
return returnValue
}
catch {
let noBlog = [{
style: "HEADING_1",
className: ["blog-text"],
content: ["Error, blog not found!"]
}];
returnValue = noBlog;
return returnValue;
}
}
The single responsibility principle states that a function should only have a single responsibility, the function above in the truest form of single responsibility would not pass. Technically the function gathers data from google apps via the first variables - this uses a sub function specific to Google Apps Script, Doc.Documents.get() function. Then it parses the data, and if there's no data handles a catch response. In atomic functions, it would be proper to split the parse and error handling into a separate function.
function getData(fileId) {
try {
const gdoc = Docs.Documents.get(fileId);
const body = gdoc.body;
const listOfContent = body.content;
const returnValue = parseDocument(listOfContent, gdoc);
return returnValue;
} catch {
const returnValue = noBlog();
return returnValue;
}
}
function noBlog() {
return [
{
style: "HEADING_1",
className: ["blog-text"],
content: ["Error, blog not found!"],
},
];
}
function parseDocument(listOfContent, gdoc) {
const returnValue = [];
for (let i = 0; i < listOfContent.length; i++) {
if (listOfContent[i].table) {
const table = getDataFromTable(listOfContent[i].table);
returnValue.push(table);
}
if (listOfContent[i].paragraph) {
if (listOfContent[i].paragraph.elements) {
const paragraph = getDataFromParagraph(
listOfContent[i].paragraph,
gdoc
);
if (paragraph != null) {
returnValue.push(paragraph);
}
}
}
}
return returnValue;
}
This refactor has a few advantages, firstly the separation of layers ensures that the getData function becomes a pure service layer, the separation of error message could be used in multiple functions, adhering to DRY principles.
React props is a great catch all bucket you can throw everything you need down to child components, you can directly access props via props.foo and in small numbers this can make the code more concise. However it’s definitely more complicated to look at a react component and understand what’s going on without destructing the props. So nowadays, I will always destruct props just prior to declaring state variables and other such variables, such as in my form builder app.
export default function Form(props) {
/* props destructuring */
const { formName, formNumber } = props;
/* state management */
const [hasSubmited, setHasSubmitted] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [formData, setFormData] = useState();
const [pageNumber, setPageNumber] = useState(1);
const [modalActive, setModalActive] = useState(false);
const [modalText, setModalText] = useState("");
So comments in code are definitely a divisive topic, and I don’t really want to weigh in on one opinion or another, but as you can see in my code snippets I do use comments.
I think comments in code can add to clarity of code, while I may not need to have a comment for a state management area or props destructuring I prefer to layout my code this way.
I also feel that sometimes when a complex chain of functions is needed, commenting can further help clarify what can be an impenetrable network of code.
No further comments.
While I will never claim mastery of code like Robert C. Martin, there are things that I simply don’t do because I find it unhelpful.
While I understand the concept of using Polymorphism is a paradigm of clean code, with React switching to functional components, it has moved away from polymorphic class based components, and I find very few used cases where polymorphic code leads to easier readability in modern React, which itself leans into custom hooks, render props and higher-order components for composition and reusability.
With a background in medical research I’m used to blending learned studying with experimental testing to further my understanding. My journey in coding has been a wild ride of experience, reading, testing, learning. It’s been an incredible journey so far and my desire to continue to expand my skills hasn’t diminished. Utilising what I learned from my previous career about blended learning I’d offer the following advice.
I strongly feel that the best way to gain an understanding of clean code, and produce clean code, is to first read the documentation for feel, but not desperately try to follow dogmatically. Then with this in mind build code, invariably this code may be wrong, even if it works, my code often has sprawling functions, occasional repetition, and messy layers, making it harder to follow, and more importantly continue to maintain and expand the code to add new features.
This is not a hit out at coding videos, coding websites etc, it’s just in my personal experience, I find I get the most understanding out of building something that works (just) then looking at my code, tearing it apart and rebuilding it with the improved knowledge.
It has sometimes taken me 2 to 3 times as long to refactor code that already works, to work and be more in line with clean coding principles. But I have learned far more by doing the refactoring than the original build. This means when I do another project similar in scope, I’ve made the project quicker, and better.
While refactoring is great, there is always the need to continue on with new projects and new learning objectives. Working with commercial projects there is a time limit where you’re essentially forced off to new projects, but in self learning you could review and refactor for a long time, and there is a degree of diminishing returns. It’s good to self-reflect, but not for too long.