- Introduction
- Naming Conventions
- Writing Functions
- React Components
- CSS and Tailwind
- Formatting
- Testing
- Asset Optimization
- Packages and Libraries
My accumulated list of how-tos, guides, and cheatsheets from my experience working with different companies
❌ BAD:
let FunctionName;
let variable_name;
let UsEr;
âś… GOOD:
let functionName;
let variableName;
let user;
❌ BAD:
interface userProps;
type Cool_types;
enum Direction {
Up,
Down,
Left,
Right
}
âś… GOOD:
interface IUserProps;
type TCoolTypes;
enum EDirection {
Up,
Down,
Left,
Right
}
❌ BAD:
const imageCfg = {...image_configuration} as const;
const carouselData = [Object, Object]
enum MY_ENUM
âś… GOOD:
const IMAGE_CONFIGURATION = {...image_configuration} as const;
const CAROUSEL_CONTENT = [Object, Object]
enum EMyEnum
Choosing descriptive names for variables and functions enhances code readability and reduces the need for explanatory comments. The variable name 'data' should be replaced with something explanatory
❌ BAD:
const user = {
userAccountName: 'jerome',
userAccountId: 'jerome123',
userAccountAddress: 'Canada On',
}
âś… GOOD:
const user = {
name: 'jerome',
id: 'jerome123',
address: 'Canada On',
}
❌ BAD:
const parsedHTMLString = parserFunction(someString);
const userID = 'Jerome123'
âś… GOOD:
const parsedHtmlString = parserFunction(someString);
const userId = 'Jerome123'
Using the 'is/has/can/was' prefix solves a common problem of choosing bad boolean names like status or flag
❌ BAD:
const active = true;
const valid = true;
const access = true;
const permission = true;
const exit = true;
const deleted = true;
âś… GOOD:
const isActive = true;
const isValid = true;
const hasAccess = true;
const hasPermission = true;
const canExit = true;
const wasDeleted = true;
❌ BAD:
const toggleNow = () => {};
const nameIt = () => {};
const whatIsMyMaxValuePlease = () => {};
âś… GOOD:
const handleToggle = () => {};
const getName = () => {};
const computeMaxValue = () => {};
❌ BAD:
const asset = [image, video, svg];
const parsedAssetArray = asset.map((ast) => `cool-${ast}`);
âś… GOOD:
const assets = [image, video, svg];
const parsedAssets = assets.map((asset) => `cool-${asset}`);
Exception: Iteration variables don't have to be descriptive when used in small well-encapsulated functions and methods
Choosing descriptive names for variables and functions enhances code readability and reduces the need for explanatory comments. The variable name 'data' should be replaced with something explanatory
❌ BAD:
const total = (data) => {
let total = 0;
for (let i = 0; i < data.length; i++) {
total += data[i].value;
}
return total;
}
âś… GOOD:
const getAccountsTotalBalance = (accounts) => {
let totalBalance = 0;
for (let accountIndex = 0; accountIndex < accounts.length; accountIndex++) {
totalBalance += accounts[accountIndex].balance;
}
return totalBalance;
}
Exception: Iteration variables don't have to be descriptive when used in small well-encapsulated functions and methods
Avoid using slang
❌ BAD:
process.whack()
âś… GOOD:
process.kill()
When naming booleans, you should avoid choosing variable names that include negations. It’s better to name the variable without the negation and flip the value. This requires the person reading the code to perform a logical negation twice.
❌ BAD:
const hasNoAccess = accounts.length === 0;
âś… GOOD:
const hasAccess = accounts.length > 0;
Arrow functions do not bind their own this
, instead, they inherit the one from the parent scope, which is called "lexical scoping". Arrow functions should ideally be always used when writing a functional component
âś… EXAMPLE 1:
const myObject = {
myMethod: function () {
console.log('function')
}
};
âś… EXAMPLE 2:
const myFunction = () => {console.log('Arrow function')}
Reducing the number of function parameters is of great significance as it simplifies testing. Exceeding three parameters results in a complex web of test cases, adding to the testing workload.
❌ BAD:
const handleModal = (title: string, body: string, subtext: string, isActive: boolean) => { ... };
âś… GOOD:
const handleModalContent = (title: string, body: string, subtext: string) => { ... };
const handleModalToggle = (isActive: boolean) => { ... };
Write small and simple functions that avoid side effects. Functions that always return the same result are easier to test
❌ BAD:
const addParameter = 3;
const getSumPlusParameter = (a, b) => {
return a + b + addParameter;
};
âś… GOOD:
const getSum = (a, b) => {
return a + b;
};
Functions should do one thing. When a function does more than one thing it makes reading and testing harder.
❌ BAD:
const handleCarousel = (title: string, image: string, isActive: boolean, carouselIndex: number) => { ... };
âś… GOOD:
const handleCarouselContent = (title: string, image: string) => { ... };
const handleCarouselToggle = (isActive: boolean, carouselIndex: number) => { ... };
- Functions that affect only logic and are not coupled to a UI should be descriptive of that purpose e.g.
incrementCarouselIndex
,computeTotalValue
,getMaxWidth
. - For functions that are tied to a UI, it should be named by what the UI does e.g.
advanceCarouselImage
,hideCarouselButton
,disableButton
.
If a value can be undefined make the arg optional
❌ BAD:
const incrementCarouselIndex = (carouselIndex: number) => {
if(!carouselIndex) {
carouselIndex = 1
}
};
âś… GOOD:
const incrementCarouselIndex = (carouselIndex: number = 1) => {
...
};
NOTE: this is a contrived example. Don't mutate external variables to avoid side-effects
A consistent folder structure helps team members collaborate more efficiently by providing a shared understanding of where files are located. This makes it easier to work on the same files and avoid conflicts.
âś… EXAMPLE 1:
src/
|- components/
|- ComponentNameA/
|- ComponentNameA.tsx
|- component-name-a.scss
|- ComponentNameA.test.tsx
|- index.ts
|- NestedComponent/ (A component used only in ComponentA)
|- NestedComponent.tsx
|- Nested-component-name.scss
|- NestedComponent.test.tsx
|- index.ts
|- ComponentNameB/
|- ComponentNameB.tsx
|- component-name-b.scss
|- ComponentNameB.test.tsx
|- index.ts
- Components should be Pascal case
- Component names should be meaningful
- Test files should be camel case
- External stylesheets should be kebab case
❌ BAD:
|- nModal/
|- nModal.tsx
|- modalStyles.scss
|- nModal.test.tsx
|- index.ts
âś… GOOD:
|- NotificationModal/
|- NotificationModal.tsx
|- notification-modal.scss
|- NotificationModal.test.tsx
|- index.ts
Keeping components small and focused on a single responsibility makes it easier to test and debug code. Smaller components are easier to reuse across the application, making it easier to maintain and scale the codebase. Breaking down components into smaller parts allows you to separate ths of your application.
❌ BAD:
export const Payment = ({ amount }: { amount: number }) => {
const [paymentMethods, setPaymentMethods] = useState<LocalPaymentMethod[]>(
[]
);
useEffect(() => {
const fetchPaymentMethods = async () => {
const url = "https://online-ordering.com/api/payment-methods";
const response = await fetch(url);
const methods: RemotePaymentMethod[] = await response.json();
if (methods.length > 0) {
const extended: LocalPaymentMethod[] = methods.map((method) => ({
provider: method.name,
label: `Pay with ${method.name}`,
}));
extended.push({ provider: "cash", label: "Pay in cash" });
setPaymentMethods(extended);
} else {
setPaymentMethods([]);
}
};
fetchPaymentMethods();
}, []);
return (
<div>
<h3>Payment</h3>
<div>
{paymentMethods.map((method) => (
<label key={method.provider}>
<input
type="radio"
name="payment"
value={method.provider}
defaultChecked={method.provider === "cash"}
/>
<span>{method.label}</span>
</label>
))}
</div>
<button>${amount}</button>
</div>
);
};
âś… GOOD:
// useFetchPaymentMethods.ts
export const useFetchPaymentMethods = () => {
const [paymentMethods, setPaymentMethods] = useState<IPaymentMethods[]>([]);
useEffect(() => {
(async () => {
const response = await fetch('https://api.com/api/methods');
const json = await response.json();
setMethod(json);
})();
return { paymentMethods };
});
};
// Title.tsx
export const Title = ({ children }: { children: JSX.Element }) => {
return <h3>{children}</h3>;
};
// Button.tsx
export const Button = ({ children, type }: { children: JSX.Element; type: TButtonTypes;
}) => {
return <button>{children}</button>;
};
// PaymentMethodSelectBoxes.tsx
export const PaymentMethodSelectBoxes = ({
paymentMethods,
}: {
paymentMethods: PaymentMethods[];
}) => {
return (
<div>
{paymentMethods.map((method) => (
<label key={method.provider}>
<input
type='radio'
name='payment'
value={method.provider}
defaultChecked={method.provider === 'cash'}
/>
<span>{method.label}</span>
</label>
))}
</div>
);
};
// Payment.tsx
export const Payment = ({ amount }: { amount: number }) => {
const { paymentMethods } = useFetchPaymentMethods();
return (
<div>
<Title>Payment</Title>
<PaymentMethodSelectBoxes paymentMethods={paymentMethods} />
<Button>${amount}</Button>
</div>
);
};
NOTE: this is a contrived example. The BAD example at the top is perfectly fine because it is small and simple. But for larger complex components we should break them down into small modular pieces
❌ BAD:
<div className="ml-2 mr-2"></div>
<div className="pt-4 lg:pt-8 pr-4 pb-4 pl-4"></div>
âś… GOOD:
<div className="mx-2"></div>
<div className="p-4 lg:pt-8"></div>
Do not prematurely use @apply
to abstract component classes where a proper framework/template-based component is more appropriate.
❌ BAD:
Component1.tsx
<div className="flex-col flex items-start gap-5 mb-5 landscape:mobile-landscape:mb-3 sm:mb-5 md:mb-10 h-full"></div>
Component2.tsx
<div className="flex-col flex items-start gap-5 mb-5 landscape:mobile-landscape:mb-3 sm:mb-5 md:mb-10 h-[500px]"></div>
âś… GOOD:
Component1.tsx
<div className="block-component h-full"></div>
Component2.tsx
<div className="block-component h-[500px]"></div>
global.scss
block-component {
@apply flex-col flex items-start gap-5 mb-5 landscape:mobile-landscape:mb-3 sm:mb-5 md:mb-10
}
NOTE: The ideal scenario would be to keep the components modular where Component1 and Component2 are just one component and the height would be adjusted with a parameter. Modular components would also mean modular styles and we would no longer need to use @apply
When writing a string of multiple utility classes, always do so in an order with meaning. 1. positioning/visibility 2. box model 3. borders 4. backgrounds 5. typography 6. Other visual adjustments. Keeping a familiar pattern helps us read and modify the classes quickly
Classnames of a specific category should be in the order of general to specific e.g. flex flex-col
is better than flex-col flex
❌ BAD:
<div className="text-lg bg-black flex-col flex mb-5 sm:mb-5 md:mb-10 h-full"></div>
âś… GOOD:
<div className="flex flex-col mb-5 sm:mb-5 md:mb-10 h-full bg-black text-lg"></div>
Use the @layer
directive to tell Tailwind which “bucket” a set of custom styles belong to. Valid layers are base
, components
, and utilities
.
âś… EXAMPLE:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base { h1 { @apply text-2xl; } h2 { @apply text-xl; } }
@layer components { .btn-blue { @apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded; } }
@layer utilities { .filter-none { filter: none; } .filter-grayscale { filter: grayscale(100%); } }
Spacing by Flex/Grid is only added once on the parent elements versus creating multiple classes on the child elements
❌ BAD:
<div>
<p class="mr-2">Paragraph</p>
<p class="mr-2">Paragraph</p>
<p class="mr-2">Paragraph</p>
<p class="mr-2 last:mr-0">Paragraph</p>
</div>
âś… GOOD:
<div class="flex gap-2>
<p>Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
</div>
Improve CSS style readability by grouping utility classes with the Classnames plugin
âś… EXAMPLE:
import { default as cn } from 'classnames';
const buttonStyle = cn(
//1. positioning/visibility
'flex justify-center align-center',
// 2. box model
'mx-5 my-10 px-5',
// 3. borders
'border border-transparent rounded-md'
// 4. backgrounds
'bg-black hover:bg-black/80'
// 5. Typography
'text-sm lg:text-base xl:text-1xl'
<button className={buttonStyle} />
{
"arrowParens": "always",
"semi": true,
"singleQuote": false,
"printWidth": 100,
"tabWidth": 2,
"proseWrap": "always",
"bracketSpacing": true,
"useTabs": false,
"jsxSingleQuote": true,
"bracketSameLine": false,
"trailingComma": "none"
}
âś… EXAMPLE:
function filterByTerm(inputArr, searchTerm) {
return inputArr.filter(function(arrayElement) {
return arrayElement.url.match(searchTerm);
});
}
describe("Filter function", () => {
test("it should filter by a search term (link)", () => {
const input = [
{ id: 1, url: "https://www.url1.dev" },
{ id: 2, url: "https://www.url2.dev" },
{ id: 3, url: "https://www.link3.dev" }
];
const output = [{ id: 3, url: "https://www.link3.dev" }];
expect(filterByTerm(input, "link")).toEqual(output);
});
});
âś… EXAMPLE:
import React from "react";
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import { Header } from "./Header";
describe("Header", () => {
it("Should render the the header component", async () => {
// Setup
render(<Header location={window.location} />);
// Test
expect(screen.getAllByText("Home")[0]).toBeInTheDocument();
expect(screen.getAllByText("About")[0]).toBeInTheDocument();
expect(screen.getAllByText("Careers")[0]).toBeInTheDocument();
});
});
The following guidelines will help keep the video file size small:
- Keep videos short
- Frame rate should be 25 or below
- Videos should be perfect loops to not look jarring
- Serve multiple video formats because not all browsers support the same video format
- Make sure to include an image (preferably JPEG which is widely supported) as a video fallback
<video controls poster="/images/w3html5.gif">
<source src="movie.mp4" type="video/mp4">
<source src="movie.ogg" type="video/ogg">
Your browser does not support the video tag.
</video>
Note: The first frame of the video will be displayed if you don't use the poster attribute
<video controls poster="/images/frame.gif">
<source src="movie.mp4" type="video/mp4">
<source src="movie.ogg" type="video/ogg">
<img src="/fallback.jpeg" alt="video did not load" />
</video>
FFMPEG script for best browser compatibility:
ffmpeg -an -i bg-home-section-c.mp4 -vcodec libx264 -pix_fmt yuv420p -profile:v baseline -level 3 -s hd720 -crf 30 ../bg-home-section-c.mp4
Note: The above command line outputs an MP4 which has a CRF (Constant rate factor) of 30. CRF is an option available in the libx264
encoder to set our desired output quality. Max CRF value is 51. The lower the value the better the quality and the bigger the file size.
JPEG image from the first frame of a video
ffmpeg -i inputfile.mkv -vf "select=eq(n\,0)" -q:v 3 output_image.jpg
JPEG image from the 10th frame of a video
ffmpeg -i inputfile.mkv -vf "select=eq(n\,9)" -q:v 3 output_image.jpg
for x in $(find .); do echo && echo "[*] creating fallback"; ffmpeg -i $x -vf "select=eq(n\,5)" -q:v 3 $x.jpg ; done
WebP is a modern image format that provides superior lossless and lossy compression for images on the web. It creates an image with a smaller file size while maintaining better quality than other formats
Further reading: https://developers.google.com/speed/webp
Command line Installation Instructions: https://developers.google.com/speed/webp/docs/precompiled
https://developers.google.com/speed/webp/docs/cwebp
Generate WebP from a JPEG with 90% quality
cwebp -q 90 sample.jpg -o ../sample.webp
<picture>
<source srcset="img/asset.webp" type="image/webp" media="(min-width: 2800px)">
<img src="img/fallback.png">
</picture>
SVG files store images more efficiently than common raster formats if the image isn’t too detailed. SVG files contain enough information to display vectors at any scale, whereas bitmaps require larger files for scaled-up versions of images — more pixels use more file space
Note: Don't use SVG for raster images
ImageOptim is a user-friendly asset optimization tool that will also strip out your asset's EXIF metadata. Ideally, you will use this tool after you have finalized the quality and target resolution for your images e.g 4k, 2k, 1080p (for responsive web images)
These image formats should be used based on how the images are utilized on your website.
- SVGs - for logos and icons
- PNGs - Raster images with transparency
- JPEGs/WEBP - Raster images without transparency
- GIFs - Animated images
SVGR handles all type of SVG and transforms it into a React component.
import { ReactComponent as TwitterIcon } from '@Icons/twitter.svg';
<TwitterIcon className='fill-black w-10 h-10' />
Let's say you use a library in your code that handles analytics tracking. There are hundreds of these tracking calls all over your code base. If for some reason the library decided to upgrade its package and rename the tracking call to something else, this would mean you will have to grep your entire codebase to replace this call.
To avoid this tedious process we should wrap third-party libraries with an abstraction layer. So when there is a need to update a package it will only be done within the abstraction layer and not on each individual file that uses the library.
âś… EXAMPLE:
Abstraction layer: useTracker.tsx
import tracker from 'thirdparty/script';
export const useTracker = () => {
const trackButton = () => {
tracker('track this button')
}
return { trackButton }
}
Components
import { useTracker } from './useTracker';
const Component = () => {
const { trackButton } = useTracker();
...
return <Button onClick={() => { trackButton() }}>
}