<aside> ✅ ScandiPWA follows a strict JavaScript style guide for maintainability and consistency
</aside>
Code style recommendations in ScandiPWA consist of two main categories: functional programming, which is enforced to make the codebase easier to maintain, and ScandiPWA best practices, which have been implemented to guarantee that code is extensible, both by overriding the theme and writing plugins.
We strongly recommend you use ESlint to check your code style. This article was written to help you understand the code style rules we enforce and write better code.https://www.notion.so/scandiweb/What-is-the-directory-structure-793001824a7f4e4cbb5beeccfc40ffee
Functions should do only one thing, and do it well. If you notice that a function has become longer than necessary, consider breaking it up into parts. Not only will this make your codebase easier to navigate and manage, but it will also make your theme easier to extend via plugins and theme overrides.
This is especially relevant when writing functions that return JSX. Breaking them down into multiple functions can reduce nesting, improve readability, and make them easier to extend.
<aside> 🚨 Avoid writing long functions such as this:
Issues:
<aside> ✅ Instead, try breaking your code into smaller functions:
Advantages:
Containers should be responsible for business logic, and components should be responsible for presentation logic. Making this distinction will make it easier to structure your code.
Destructuring enables you to "unpack" certain values from an object such as the state or props.
const { product: { name, sku } = {} } = this.props;
// you can now use name instead of this.props.product.name
// and sku instead of this.props.product.sku
// if this.props.product is undefined, it will get the default value {}.
// name and sku will be undefined, which you can easily check...
// but at least the page won't crash for accessing the property of an
// undefined value
ScandiPWA prefers destructuring all required variables at the beginning of a function over direct field access, as it offers several benefits:
To make code easier to understand, avoid generic names such as x
or abbreviations such as prdct
. Give meaningful names that describe what the variable/function/class is for.
<aside> 🚨 It can be tempting to pass a hard-coded literal value to a function:
CSS.setVariable(
this.draggableRef,
'animation-speed',
`${ Math.abs(distance * 300) }ms`
);
However, the purpose of the number 300
is not clear, and might confuse a developer looking at this for the first time.
</aside>
<aside> ✅ Instead, consider creating a constant to describe the meaning of the value:
//component/Slider/Slider.component.js (simplified excerpt)
export const ANIMATION_DURATION = 300;
CSS.setVariable(
this.draggableRef,
'animation-speed',
`${ Math.abs(distance * ANIMATION_DURATION) }ms`
);
Advantages:
ANIMATION_DURATION
can be easily reused if neededBetter yet, use a .config.js
file for that( How to create a .config.js file?).
</aside>
Functional programming aims to make code easier to reason about and maintain by avoiding mutable values. It can make your codebase easier to navigate as well as more concise and elegant.
let
, prefer using const
Use const
for every variable and do not reassign values.
<aside>
🚨 Avoid let
and reassignments:
let price = 4.5;
price = '$' + price;
price = `This product costs ${ price }.`
Potential problems that can occur as the codebase grows and price
is used more times:
price
can have multiple meanings; a developer looking at line 1 might miss the reassignment and assume price
is a numberprice
can change and can get hard to guess<aside> ✅ Instead prefer:
const price = 4.5;
const formattedPrice = '$' + price;
const message = `This product costs ${ formattedPrice }.`
Benefits:
In functional programming, loops are discouraged in favor of iterative functions that signal intent better. In addition, they are often elegant and concise.
If you need to iterate over the array to produce a single value, such as the sum, maximum value, or even an object containing some of the array's values, you can use reduce
.
<aside> 🚨 Avoid using a loop to combine the array's elements:
const items = [2, 4, 24, 42];
let sum = 0;
for (let i = 0; i < items.length; i++) {
sum += items[i];
}
</aside>
<aside>
✅ It is preferable to use reduce
.:
const items = [2, 4, 24, 42];
const sum = items.reduce((sum, item) => sum + item);
Advantages:
If you see this for the first time, it might seem counter-intuitive. Perhaps MDN's docs will help!
</aside>
If you need to transform the values of the array into different values, use map
<aside> 🚨 Avoid using a loop to transform an array:
const items = [2, 4, 24, 42];
const newItems = []
for (let i = 0; i < items.length; i++) {
newItems.push(`Item ID: ${ items[i] }`);
}
</aside>
<aside>
✅ Instead, use map
:
const items = [2, 4, 24, 42];
const newItems = items.map(item => `Item ID: ${ item }`);
Advantages:
map
</aside>In general, you should be able to write code in JavaScript without needing to use loops at all. Following this practice will result in cleaner and more maintainable code.
For code to be easier to reason about, you should avoid mutating arrays. Instead, it is preferred to create new arrays with the values you want. This will often be more concise, and make the data flow easier to follow. In addition, it is consistent with how React and Redux handle state - instead of giving you access to modify the state directly, you are expected to provide new values for the state.
MDN Web Docs is a great reference resource for JavaScript. Here you can find a summary of how array functions can be used to write functional-programming-style code, with links to the MDN documentation.
map
: calls the function for each value and returns the array of results
<aside>
➡️ map
is often used in JSX when you need to render an array of items. You can "map" or transform the array into an array of react elements by calling map
with a function that accepts each item and returns JSX. Example:
// component/ProductCustomizableOptions/ProductCustomizableOptions.component.js (excerpt)
return options.map((option, key) => (
<ProductCustomizableOption
option={ option }
key={ key }
/>
));
</aside>
reduce
: combines all elements into 1 new value, according to the specified reducing function
some
: returns true if and only if the specified function returns true for at least 1 element in the array
every
: returns true if and only if the specified function returns true for all elements
filter
: returns a copy of the array with only those items that meet the specified condition
slice
: returns a portion of the array specified by indices
find
: returns the first element that meets the specified condition
includes
: returns true if and only if the specified element is in the array
sort
: sorts the array according to the specified ordering function
reverse
: reverses the items of the array
<aside>
🚨 While sort
and reverse
return the re-ordered array, they also change the original array. For this reason, they are not ideal from the functional programming perspective.
Most of the time, mutating the original array might be acceptable. If you do not wish to mutate the array, create a copy of it and re-order the copy instead.
</aside>
Instead of mutating the array, you can create a new array with the additional value:
const newItem = 42;
const array = [1, 2, 3];
const newArray = [newItem, ...array];
Instead of mutating the array, you can create a new array with the value removed, using filter
:
const itemToRemove = 42;
const array = [42, 1, 2, 3];
const newArray = array.filter(item => item !== itemToRemove);
// can also filter by index (the second parameter in the filter function)
In addition to using functional programming, ScandiPWA also recommends that you follow certain guidelines to ensure that your code can be easily extended by plugins and theme overrides.
If a plugin or theme override were to extend your code, they would need to have access to your classes and functions. A theme override might want to base its code on your class without needing to copy-paste it. For this reason, it is strongly recommended that you export all top-level classes, functions, and values that you define.
Plugins can only affect values that have namespaces. For this reason, it is highly recommended that you add a namespace to all functions and classes:
/** @namespace Route/Checkout/Container/mapStateToProps */
export const mapStateToProps = (state) => ({
totals: state.CartReducer.cartTotals,
customer: state.MyAccountReducer.customer
});
/** @namespace Route/Checkout/Container/mapDispatchToProps */
export const mapDispatchToProps = (dispatch) => ({
updateMeta: (meta) => dispatch(updateMeta(meta)),
resetCart: () => CartDispatcher.then(
({ default: dispatcher }) => dispatcher.updateInitialCartData(dispatch)
),
});
/** @namespace Route/Checkout/Container */
export class CheckoutContainer extends PureComponent {
// [...]
saveGuestEmail() {
const { email } = this.state;
const { updateEmail } = this.props;
const guestCartId = BrowserDatabase.getItem(GUEST_QUOTE_ID);
const mutation = CheckoutQuery.getSaveGuestEmailMutation(email, guestCartId);
updateEmail(email);
// even dynamically created functions should have namespaces
return fetchMutation(mutation).then(
/** @namespace Route/Checkout/Container/saveGuestEmailFetchMutationThen */
({ setGuestEmailOnCart: data }) => data,
this._handleError
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CheckoutContainer);
Namespaces should consist of:
ScandiPWA has a specific file structure: the source directory contains 7 sub-directories, such as component
, route
, store
, etc. To keep code organized, it is advised to avoid deviating from this file structure. This means that you are allowed to create new components, routes, etc, but you should not add any new "main" directories.
Each file should define at most one class. Adding additional classes can make the codebase harder to navigate.
select * from customer_address_entity_text order by entity_id desc ;
select * from customer_address_entity_text order by entity_id desc ;
select * from customer_address_entity_text order by entity_id desc ;