Coding Best Practice's for REACT, NODE and GIT
Software development is a combination of art and science, a blend of creativity and discipline. The tools we use and the way we employ them can greatly impact the efficiency of our workflow and the quality of our output. As a developer, it's not just about what code you write; it's about how you write it, how you manage it, and how you collaborate with others on it.
By following best practices in these areas, you can write cleaner, more efficient code, avoid common mistakes, and make your projects more maintainable and scalable. Whether you're a seasoned developer looking to polish your skills or a beginner seeking to create good habits from the start, this guide is for you.
General Coding Practices
Understandable and Maintainable Code
Write clear and concise code. Use meaningful variables, functions, and class names. The following JavaScript code is clear and concise, with meaningful variables, functions, and class names.
// Good code
function calculateArea(radius) {
const PI = 3.14;
let area = PI * Math.pow(radius, 2);
return area;
}
let circleArea = calculateArea(5);
console.log(circleArea);
Commenting and Documentation
Write useful comments where necessary. Maintain documentation for complex sections of code and overall project structure.
/**
* Function to calculate the area of a circle.
*
* @param {number} radius - The radius of the circle.
* @returns {number} The calculated area of the circle.
*/
function calculateArea(radius) {
const PI = 3.14;
let area = PI * Math.pow(radius, 2);
return area;
}
Consistent Coding Style
Follow a consistent coding style across the team. This may include rules for indentation, use of spaces or tabs, brace styles, etc.
if (radius > 0) {
let area = calculateArea(radius);
console.log(area);
} else {
console.log('Invalid radius');
}
Code Reviews
Establish a code review process. All code should be reviewed by at least one other developer before being merged. For example, in a GitHub pull request, a developer could provide feedback like: "In the calculateArea
function, please replace the hardcoded PI
value with Math.PI
."
Testing
Write unit tests, integration tests, and end-to-end tests where appropriate. Aim for high test coverage.
const calculateArea = require('./calculateArea');
test('calculates the area of a circle with radius 5', () => {
expect(calculateArea(5)).toBeCloseTo(78.5, 1);
});
Error Handling
Use proper error-handling techniques. Do not leave empty catch blocks.
try {
let circleArea = calculateArea(-5);
console.log(circleArea);
} catch(error) {
console.error('An error occurred: ', error);
}
Version Control
Use a version control system effectively. Commit often with meaningful commit messages.
git add calculateArea.js
git commit -m "Add function to calculate area of a circle"
React Specific Practices
Component Design
Keep your components small and focused. Each component should ideally do just one thing.
// A focused component that just displays a button
function ActionButton({ onClick, label }) {
return (
<button onClick={onClick}>{label}</button>
);
}
State Management
Be thoughtful with your state management. If a state is being used by multiple components, it might be a good idea to lift the state up.
// Parent component managing the state
function ParentComponent() {
const [count, setCount] = useState(0);
return (
<div>
<ChildComponent count={count} />
<button onClick={() => setCount(count + 1)}>Increase count</button>
</div>
);
}
// Child component receiving the state as a prop
function ChildComponent({ count }) {
return (
<p>The count is {count}</p>
);
}
Prop Types
Use Prop Types for type-checking in all components.
import PropTypes from 'prop-types';
function ActionButton({ onClick, label }) {
return (
<button onClick={onClick}>{label}</button>
);
}
ActionButton.propTypes = {
onClick: PropTypes.func.isRequired,
label: PropTypes.string.isRequired
};
Lifecycle Methods
Understand and use lifecycle methods properly. If you're using hooks, understand how useEffect replaces lifecycle methods.
import { useEffect } from 'react';
function App() {
useEffect(() => {
// This code runs after render, replacing componentDidMount and componentDidUpdate
document.title = 'Hello, world!';
// This code runs before unmounting, replacing componentWillUnmount
return () => {
document.title = 'React App';
};
});
return (
<div>Hello, world!</div>
);
}
CSS-in-JS (Depends on your choice)
Decide on a consistent way to handle CSS. CSS-in-JS libraries like styled-components can be a good choice.
import styled from 'styled-components';
// This creates a styled div element
const StyledDiv = styled.div`
background-color: blue;
color: white;
padding: 10px;
margin: 10px;
`;
function App() {
return (
<StyledDiv>Hello, world!</StyledDiv>
);
}
Node.js Specific Practices
Asynchronous Handling
Understand and properly handle asynchronous operations using callback functions, promises, or async/await.
// Callback
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
// Promise
let promise = new Promise((resolve, reject) => {
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
promise.then(console.log).catch(console.error);
// Async/Await
async function read() {
let data = await fs.promises.readFile('file.txt', 'utf8');
console.log(data);
}
read().catch(console.error);
Error Handling
Make use of try/catch blocks for error handling and always handle promise rejections.
// With promises
promise.then(console.log).catch(error => {
console.error('An error occurred:', error);
});
// With async/await
async function read() {
try {
let data = await fs.promises.readFile('file.txt', 'utf8');
console.log(data);
} catch(error) {
console.error('An error occurred:', error);
}
}
read();
Security
Use a helmet, avoid eval(), check dependencies for vulnerabilities, and use other practices to secure your Node.js application. It helps to protect Node. js Express apps from common security threats such as Cross-Site Scripting (XSS) and click-jacking attacks.
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet());
Environment Variables
Make use of environment variables to manage settings between development, staging, and production environments.
require('dotenv').config();
console.log(process.env.SECRET);
Code Structure
Follow MVC or other appropriate design patterns. Separate your routes, controllers, and database operations.
/project
/models
user.js
/views
index.html
/controllers
userController.js
server.js
Your userController.js
file might look like this:
const User = require('../models/user');
exports.getUser = async (req, res) => {
try {
const user = await User.findById(req.params.id);
res.send(user);
} catch(error) {
res.status(500).send(error);
}
}
Git Practices
Commit Messages
Write clear and meaningful commit messages. A good commit message is composed of a short, concise summary (50 characters or less), followed by a blank line and a more complete description, if necessary.
git commit -m "Add login feature
This commit includes the necessary files and changes to implement the user login feature. The feature includes input validation, error handling, and user authentication."
Commit Often
Commit often instead of making large commits with many changes. This will make it easier to identify the cause if a problem arises.
git add login.js
git commit -m "Implement user authentication for login"
Branching
Make use of branches extensively.
The Master/Main branch should be the most stable, deployable branch.
Develop branch is used to integrate different features planned for an upcoming release.
Feature branches are used to develop new features.
Hotfix branches are used to quickly patch production releases.
# Creating and switching to a new feature branch git checkout -b feature/user-registration # Switching to the develop branch to integrate features git checkout develop # Creating a hotfix branch for an urgent bug git checkout -b hotfix/login-bug
Pull Requests
Use pull requests for merging new features or bug fixes. This allows for code review and discussion before changes are added to the main codebase.
Typically you would create a new branch, push your changes to that branch, and then go to the GitHub or GitLab interface to open a new pull request.
Fetch Regularly
Regularly fetch updates from the remote repository to stay updated with the work done by other developers.
git fetch origin
Avoid Git Force Push
Never use git force push unless it's absolutely necessary. It can overwrite other people's changes and disrupt their work.
git push origin feature/user-registration
But you should avoid force pushing:
# Don't do this unless necessary
git push -f origin feature/user-registration
Rebase Wisely
Use git rebase to keep your feature branch up to date with the latest code from the main branch, but be careful not to rebase code that has been shared with others.
git checkout feature/user-registration
git rebase main
Stashing
Use git stash to save changes that you want to keep but not commit immediately.
git stash save "Work in progress for user registration feature"
Tagging
Use tags to mark release points (v1.0, and so on).
git tag v1.0
.gitignore
Always add a .gitignore file at the root of your repository. This file should list all files and directories that Git should ignore.
React:
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
Node.js:
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build / generate output
.nuxt
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# Firebase cache directories
.firebase/
.firestore/
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
The specifics of your .gitignore
file might depend on your specific project setup, so you may need to add or remove some lines based on your needs. For example, .next
and .cache
are included for projects that use Next.js and Gatsby, respectively. If you're not using those tools, you can remove those lines.
Atomic Commits
Make atomic commits, which means each commit should contain exactly one change. This makes it easier to understand what a commit does and to roll it back if necessary.
git add user.js
git commit -m "Add user registration feature"