Best practices for Javascript projects
Planning with development strategy is lowering the probability of delays or project failures. Examine some of the best practices that can help you deliver solid code with reduced effort and maintenance costs.
Know the language
The latest Javascript specification introduced useful syntax. It is recommended to use the default syntax over the previous unpure methods. Today you do not need Lodash, Underscore or Ramda most of the time.
- Prefer JavaScript documentation on MDN (Mozilla developer network) over other sources
Full Javascript course with examples, tips, and best practices
const, exceptionally let variable declaration
- Will not pollute the upper or global scope, and const is signaling that the value will never change.
map, filter, reduce array methods
- Do not need intermediate variables, can be chained.
Object and array destructuring, renaming variables and default values
- Makes functions with an object as an argument simple and useful
Arrow functions — expression is a syntactically compact alternative to a regular function expression
const fn = () => {};
will prevent function name collisions- An arrow function does not have its own
this
Arrow functions are alternatively called lambda expressions.
Async function — the
declaration defines an asynchronous function
- async code is written in synchronous style no more
then
chaining, and creating and returning of promises
const fn = async () => { return setTimeout(() => 42), 1000};
- the async keyword is automatically transforming the output of the function into a
new Promise
const fn = () => {
return new Promise((resolve) => { resolve(42); }, 1000)
};
Code newer lies comments and documentation sometimes do
Clear Intention without comments
The code should be written like plain English communicating its intentions. The focus is to make code readable for the next customer your colleague developer.
Meaningful variable names
Correctly used variable names do not need comments and explanations that can get out of sync with the code rather quickly. The code is written and should be readable for humans.
The code is read more often than it is written.
// 🚨 badconst time = 60;// 👍 goodconst timeInMinutes = 60;// 🚨 badconst interest = 2.5;// 👍 goodconst yearlyInterestInPercents = 2.5;
Boolean values with the prefix “is” and “has”
Using variables with prefixes is
and has
will communicate clearly that the variable is a Boolean
. The code is read more often than it is written.
Prefer using named variables over comments
Explain conditions with variable names rather than comments
// 🚨 badif (height > 100) {...}// 👍 goodconst minimutHeightInCm = 100;
const hasMinimumHeight = heightInCm > minimutHeightInCm;
if (hasMinimumHeight) {...}
Named variables can be reused and combined.
const isPositiveNumber = x > 0;
const isEvenNumber = x % 2;
const isOddNumber = !isEvenNumber;
const isPositiveEvenNumber = isPositive && isEven;
Readability over questionable performance improvements
Prefer array methods
It is recommended to use a functional approach without intermediate variables.
The base Javascript for cycle can be more performant in some browsers but the benefit can be measured only by iterating over millions of items. It is job of compiler and runtime to remove penalty of using new array methods.
// 🚨 badlet str = "";for (var i = 0; i < 10; i++) {
str = `${str}${i}`;
}console.log(str);
// expected output: "0123456789"// 👍 goodconsole.log(
[...new Array(10).keys()].join("")
);
// expected output: "0123456789"// 🚨 badlet sum = 0;for (var i = 0; i < 10; i++) {
sum = sum + i;
}console.log(sum);
// expected output: 45// 👍 goodconsole.log(
[...new Array(10).keys()]
.reduce((acc, key) => acc + key, 0)
);
// expected output: 45
We should ignore critics and switch to new syntax because it is shorter, more readable, and can be parallelized in the future.
The problem when the function takes more parameters
The function should have a maximum of one required and one optional parameter. Most of the time use object as a parameter/argument.
Creating and merging objects
It is not free to create an object on every call of the function. But the benefit of readability of the object argument is more significant than some preemptive micro-optimization.
Understand the benefits of testing
Tests are saving development time, and maintenance cost.
When tests are required developers are motivated to write smaller testable functions. Testing simple pure functions do not demand to mock large parts of the app.
Pure functions
A pure function is a function where the return value is only determined by its input values, without observable side effects.
Unit testing is better than debugging, whole app every time.
Separation of concerns
A Function should do one testable thing only. It is recommended to keep layers of the app separate from the other.
The function should inject constants and other functions using the object parameter with reasonable defaults that will open future overrides and ease testing.
UI rendering function should not have access to whole state or state management. Inject only necessary parts of the state and provide only necessary action creators/functions.
Component groups separation to allow testing
- following a few simple rules will promote reusability and testability of components
- Atoms simple component with styles
- Molecules groups of simple components with styles
- Organisms components with minimal details for rendering can call functions to handle specific events. Can have style adjustments for specific business use cases
3*. Connector or state provider for Organisms provides or selects state for a component
4. Templates place components into a page layout that can be used in pages
5. Pages are specific instances of templates that show what a UI looks like with real representative content in place
Static typing
Confidence added by static typing will compensate for extended effort of writing types. Static typing with modern IDE is rapidly improving the developer experience with autocomplete and error highlighting.
Types can unveil the entire class of bugs that even testing can not indicate.
- Typescript is preferred by the library authors
- Flow in some cases has some benefits for React projects
React and Typescript | Example
- The goal is to create reusable components
- A component can have other components in its default props
- Default props used in render props pattern can be statically typed
type Props = {} & typeof defaultProps;type Children = (props: Props) => JSX.Element;export const Header =
({children, ...props}: {children: Children} & Props) =>
children(props);
- the default render function can be provided for styleguide
Header.defaultProps = {
...defaultProps,
children: ({Wrapper, Group}: Props) => (
<Wrapper>
<Group>Left</Group>
<Group>Center</Group>
<Group>Right</Group>
</Wrapper>
),
};
Component rendered with default children function
Header variant “space-between”
<Header />
Override component using props
Header variant “space-around”
const App = ({CustomHeaderWrapper}) => (
<Header Wrapper={CustomHeaderWrapper} />
);App.defaultProps = {
CustomHeaderWrapper: styled(Header.defaultProps.Wrapper)`
justify-content: space-around;
`,
};
Override component using props and provide render function
Header variant replacing children
<Header Wrapper={CustomHeaderWrapper}>
{({Wrapper, Group}) => (
<Wrapper>
<Group>Center</Group>
</Wrapper>
)}
</Header>
Type safety when you use an undefined prop
Data management
selectors decouple shape of application state with shape of the component props
normalization and denormalization treat the state of the application as a relational database.
- duplicities in application state produce bugs when updates do not address all the duplicities
- loading and storing nested entities significantly enlarge rest payloads and processing
- prefer the use projects like normalizr and normalization of nested JSON according to a schema
Invest in tooling that can save time
An example can be found in https://github.com/ableneo/modules and eslint config in the eslint-config-ableneo.
Config installation guide.
precommit should
- pretty print
- lint the code, type checking
- run all the tests related to changed files in git
prepush
- lint the code, type checking
- run all the unit test
CD/CI
- try to merge
- run prepush checks again
- start build
Code generators
There are two good generator libraries plopjs and hygen. A short hygen tutorial is available in an article with an example.
Another example of hygen is initializer hygen-eslint-config-ableneo for our eslint-config-ableneo
Style guides
Boost developer experience with a styleguide. Developers and designers can iterate faster on generalized components.
List of living style guide projects worth mentioning
- styleguidist for multiple frameworks
- storybook for React only
- docz for React only
Example of public documentation/styleguide using docz. https://ableneo.github.io/modules/
Conclusion
The main goal is to enable fast and reliable development supporting the business needs of the customer. Use tools that fit the project, developers and designers. Try to remove repeatable tasks by automatization from the workflow.
👏Clap, 👂follow for more awesome 💟#Javascript and ⚛️#automatization content.