ScandiPWA
Create Magento AppCreate ScandiPWA AppUser ManualGitHub
  • Why Scandi
  • πŸš€Quick-start Guide
  • πŸ—ΊοΈRoadmap
  • Introduction to the Stack
    • CMA, CSA, and ScandiPWA
    • Challenges
  • Setting up Scandi
    • Storefront Mode Setup
      • Proxying requests to server
    • Magento Mode Setup
    • Existing Magento 2 setup
    • Magento Commerce Cloud setup
    • Updating to new releases
      • Storefront mode upgrade
      • Magento mode upgrade
      • CMA upgrade
      • CSA upgrade
      • Custom ScandiPWA composer dependency update
      • Local ScandiPWA Composer Package Setup
    • Docker Setup [deprecated]
      • Legacy Docker setup
      • Migrating to CMA & CSA
  • Developing with Scandi
    • Override Mechanism
      • Overriding JavaScript
        • Overriding classes
        • Overriding non-classes
      • Overriding Styles
      • Overriding the HTML / PHP
      • Parent Themes
    • Extensions
      • Creating an extension
      • Installing an extension
      • Migrating from 3.x to 4.x
      • Publishing an extension
      • Extension Terminology
    • Working With Magento
      • Magento troubleshooting
      • Working with Magento modules
      • Working with GraphQL
      • GraphQL Security
      • Working with "granular cache"
    • Developer Tools
      • Debugging in VSCode
      • ScandiPWA CLI
      • Configuring ESLint
      • CSA Commands
    • Deploying Your App
      • Build & Deploy Android app
      • Build & Deploy iOS app
  • Structure
    • Directory Structure
    • Building Blocks
      • Components
        • Styling Components
      • Routes
      • Redux Stores
      • GraphQL Queries
      • Global Styles
      • The Util Directory
      • Type Checking
    • Application assets
    • Code Style
      • JavaScript Code Style
      • SCSS Code Style
  • Tutorials
    • Customizing Your Theme
      • Styling
        • Customizing the Global Styles
        • Adding a New Font
        • Overriding a Components Styles
        • Extending a Component's Styles
      • Customizing JavaScript
        • Customizing the Footer Copyright
        • Adding a New Page
        • Adding a Section in My Account
        • Adding a Tab on the Product Page
        • Creating a New Redux Store
    • Payment Method Integration
      • Setting Up for Development
      • Redirecting to the Payment Provider
      • Handling the Customer's Return
    • Creating a Custom Widget
      • Scandi CMS System Overview
      • Creating a Magento Widget
      • Implementing the Rendering
    • Video Tutorials
      • #1 Setting up and talking theory
      • #2 Templating in React
      • #3 Overriding a file
      • #4 Styling the application
      • #5 Patterns of ScandiPWA
    • Dark Mode Extension
    • Deploying Native Apps
    • Product 3D Model Extension
      • Part 1: Magento 3D Model Uploads
      • Part 2: GraphQL API
      • Part 3: Scandi Frontend
    • Social Share, Full Extension Development
      • STEP-1 and 2 Creating Magento 2 Module
      • STEP-3 Backend Configurations Settings
      • STEP-4 Simple GraphQl and Resolver
      • STEP-5 Creating Extension, Base Redux Store
      • STEP-6 Extension plugins
      • STEP-7 GraphQL types, Helpers
      • STEP-8 Query Field and FieldList
      • STEP-9 render Plugins and MSTP Plugin, Component creation
      • STEP-10 SocialShare Component Development
      • STEP-11 SocialShare for CategoryPage
      • TASK-1 Changing LinkedIn to Twitter
      • STEP-12 Comments for Admin Users
      • STEP-13 Final, bugfixes
    • Accessing Magento 2 Controllers
      • STEP-1 Creating Magento 2 Module
      • STEP-2 - Create Magento 2 Frontend Route and Basic Controller
      • STEP-3 Accessing Magento 2 Controller, Bypassing ScandiPWA frontend
      • STEP-4 Creating ScandiPWA Extension with additional dependencies
      • STEP-5 Creating Plugin and Axios request
  • About
    • Support
    • Release notes
    • Technical Information
    • Data Analytics
    • Contributing
      • Installation from Fork
      • Repository structure
      • Code contribution process
      • Submitting an Issue
      • Publishing ScandiPWA
Powered by GitBook
On this page
  • Writing Maintainable Code
  • Keep Functions Short
  • Separate Concerns
  • Use Destructuring
  • Use Meaningful Names
  • Avoid Magic Numbers
  • Functional Programming
  • Avoid let; Use const
  • Avoid Loops
  • Working with Arrays
  • Extensibility Best Practices
  • Export Everything
  • Add Namespaces
  • ScandiPWA Conventions
  • Follow the File Structure
  • One Class Per File

Was this helpful?

  1. Structure
  2. Code Style

JavaScript Code Style

ScandiPWA follows a strict JavaScript style guide for maintainability and consistency

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.

Writing Maintainable Code

Keep Functions Short

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.

Avoid writing long functions such as this:

component/MyAccountOverlay/MyAccountOverlay.component.js (renderCreateAccount function)
    renderCreateAccount() {
        const {
            state,
            onCreateAccountAttempt,
            onCreateAccountSuccess,
            handleSignIn
        } = this.props;

        return (
            <>
                <Form
                  key="create-account"
                  onSubmit={ onCreateAccountAttempt }
                  onSubmitSuccess={ onCreateAccountSuccess }
                  onSubmitError={ onCreateAccountAttempt }
                >
                    <fieldset block="MyAccountOverlay" elem="Legend">
                        <legend>{ __('Personal Information') }</legend>
                        <Field
                          type="text"
                          label={ __('First Name') }
                          id="firstname"
                          name="firstname"
                          autocomplete="given-name"
                          validation={ ['notEmpty'] }
                        />
                        <Field
                          type="text"
                          label={ __('Last Name') }
                          id="lastname"
                          name="lastname"
                          autocomplete="family-name"
                          validation={ ['notEmpty'] }
                        />
                        <Field
                          type="checkbox"
                          value="is_subscribed"
                          label={ __('Subscribe to newsletter') }
                          id="is_subscribed"
                          mix={ { block: 'MyAccountOverlay', elem: 'Checkbox' } }
                          name="is_subscribed"
                        />
                    </fieldset>
                    <fieldset block="MyAccountOverlay" elem="Legend">
                        <legend>{ __('Sign-Up Information') }</legend>
                        <Field
                          type="text"
                          label={ __('Email') }
                          id="email"
                          name="email"
                          autocomplete="email"
                          validation={ ['notEmpty', 'email'] }
                        />
                        <Field
                          type="password"
                          label={ __('Password') }
                          id="password"
                          name="password"
                          autocomplete="new-password"
                          validation={ ['notEmpty', 'password'] }
                        />
                        <Field
                          type="password"
                          label={ __('Confirm password') }
                          id="confirm_password"
                          name="confirm_password"
                          autocomplete="new-password"
                          validation={ ['notEmpty', 'password', 'password_match'] }
                        />
                    </fieldset>
                    <div block="MyAccountOverlay" elem="Buttons">
                        <button
                          block="Button"
                          type="submit"
                        >
                            { __('Sign up') }
                        </button>
                    </div>
                </Form>
                <article block="MyAccountOverlay" elem="Additional" mods={ { state } }>
                    <section>
                        <h4>{ __('Already have an account?') }</h4>
                        <button
                          block="Button"
                          mods={ { likeLink: true } }
                          onClick={ handleSignIn }
                        >
                            { __('Sign in here') }
                        </button>
                    </section>
                </article>
            </>
        );
    }

Issues:

  • Code is harder to understand and navigate

  • Re-ordering or re-using sub-components requires a lot of changes

Instead, try breaking your code into smaller functions:

    // define helper functions here...
    
    renderCreateAccount() {
        const {
            state,
            onCreateAccountAttempt,
            onCreateAccountSuccess
        } = this.props;

        return (
            <>
                <Form
                  key="create-account"
                  onSubmit={ onCreateAccountAttempt }
                  onSubmitSuccess={ onCreateAccountSuccess }
                  onSubmitError={ onCreateAccountAttempt }
                >
                    { this.renderPersonalInformationFieldset() }
                    { this.renderCredentialFieldset() }
                    <div block="MyAccountOverlay" elem="Buttons">
                        <button
                          block="Button"
                          type="submit"
                        >
                            { __('Sign up') }
                        </button>
                    </div>
                </Form>
                <article block="MyAccountOverlay" elem="Additional" mods={ { state } }>
                    { this.renderSignInLink() }
                </article>
            </>
        );
    }

Advantages:

  • Each function has a clear responsibility and is easy to change

  • The structure of the resulting JSX is more clear

  • The component is more extensible via plugins and overrides

Separate Concerns

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.

Use Destructuring

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:

  • More concise code with reduced repetition if the same value is used multiple times

  • Ability to provide default values

  • By moving destructuring to the first line of each function, the dependencies of that function are clear

Use Meaningful Names

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.

Avoid Magic Numbers

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.

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:

  • The intention of the code is clear, and the math makes sense

  • ANIMATION_DURATION can be easily reused if needed

  • The duration can be adjusted without worrying about breaking something

Functional Programming

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.

Avoid let; Use const

Use const for every variable and do not reassign values.

Avoid let and reassigments:

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 number

  • The type of price can change and can get hard to guess

  • Any code needing the original value of price can't get it

Instead prefer:

const price = 4.5;
const formattedPrice = '$' + price;
const message = `This product costs ${ formattedPrice }.`

Benefits:

  • You are forced to give a meaningful name to each variable, making your intentions more clear

  • The values are immutable and easier to reason about

  • Any previous value can be re-used if necessary

Avoid Loops

In functional programming, loops are discouraged in favor of iterative functions that signal intent better. In addition, they are often elegant and concise.

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];
}

Instead prefer reduce:

const items = [2, 4, 24, 42];
const sum = items.reduce((sum, item) => sum + item);

Advantages:

  • More concise and elegant

  • Avoids an imperative loop and a mutable value

If you need to transform the values of the array into different values, use map

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] }`);
}
const items = [2, 4, 24, 42];
const newItems = items.map(item => `Item ID: ${ item }`);

Advantages:

  • More concise and elegant

  • Intentions are clear - this is a typical use of map

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.

Working with Arrays

Creating a new array by transforming each item

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 }
            />
        ));

Reducing the array to 1 value

Copying part of the array

Finding an item in the array

Reordering the array

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.

Adding an item to the array

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];

Removing an item from an 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)

Extensibility Best Practices

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.

Export Everything

Add Namespaces

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:

  • The alias of the directory (Component/Store/Route, etc)

  • The name of this component (Checkout in this case)

  • The current file's responsibility, if applicable (Component/Container for components, Dispatcher/Reducer for stores)

  • The name of the target function/class, or some other meaningful name if it is anonymous

ScandiPWA Conventions

Follow the File Structure

One Class Per File

Each file should define at most one class. Adding additional classes can make the codebase harder to navigate.

PreviousCode StyleNextSCSS Code Style

Last updated 4 years ago

Was this helpful?

enables you to "unpack" certain values from an object such as the state or props.

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 .

If you see this for the first time, it might seem counter-intuitive. Perhaps will help!

Instead, use :

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 and handles state - instead of giving you access to modify the state directly, you are expected to provide new values for the state.

are 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.

: calls the function for each value and returns the array of results

: combines all elements into 1 new value, according to the specified reducing function

: returns true if and only if the specified function returns true for at least 1 element in the array

: returns true if and only if the specified function returns true for all elements

: returns a copy of the array with only those items that meet the specified condition

: returns a portion of the array specified by indices

: returns the first element that meets the specified condition

: returns true if and only if the specified element is in the array

: sorts the array according to the specified ordering function

: reverses the items of the array

If a plugin or 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.

ScandiPWA has a specific file structure: the source directory , 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.

Destructuring
reduce
MDN's docs
map
React
Redux
MDN Web Docs
map
reduce
some
every
filter
slice
find
includes
sort
reverse
theme override
contains 7 sub-directories