Components

ScandiPWA React components are reusable pieces of UI logic

The ScandiPWA theme uses React class components to implement the user interface; these are defined in the component directory. To ensure consistency, all components follow the same structure.

Component names are always UpperCamelCase (also known as PascalCase). A component named <Component> will be defined in the directory component/<Component> containing the following files:

  • <Component>.component.js - exports a class named <Component> that implements rendering the component to the UI

  • <Component>.container.js (optional) - a class named <Component>Container that implements business logic of the component

  • CategoryFilterOverlay.config.js (optional) - defines any values needed for the component

  • <Component>.style.scss (optional) - defines the component's style in SCSS using the BEM methodology

  • index.js - exposes the "public" api of the component to make it easy to import

Feel free to browse the component directory of ScandiPWA as you read this article! You can also read this article about container components, a pattern ScandiPWA uses.

The .component File

This file is responsible only for the UI rendering implementation via JSX. Here's a simplified and annotated version of component/ProductPrice/ProductPrice.component.js

// no need to import React - it is automatically imported

// import order should be kept consistent
// library imports first
import PropTypes from 'prop-types';
import { PureComponent } from 'react';

// absolute imports from other directories are second
import TextPlaceholder from 'Component/TextPlaceholder';
import { PriceType } from 'Type/ProductList';

// relative imports from the same directory are last
import './ProductPrice.style';
// ^ the .component file is responsible for importing the stylesheet

// namespaces are necessary for the plugin mechanism to work
/** @namespace Component/ProductPrice/Component */
export class ProductPrice extends PureComponent {
    // ^ note that we exported the component's class in a named export.
    // only the default export will actually be used when rendering
    // the component, but we always export the class itself so that
    // it can be used when extending the component in a child theme.
    // this becomes important if the default export is wrapped in a HOC
    // such as withRouter, making it impossible to extend as a class

    renderPlaceholder() {
        return (
            <p block="ProductPrice" aria-label="Product Price">
                <TextPlaceholder length="custom" />
            </p>
        );
    }

    renderCurrentPrice() {...}

    renderOldPrice() {...}

    renderSchema() {...}

    render() {
        // [...]

        if (!final_price || !regular_price) {
            return this.renderPlaceholder();
        }

        return (
            <p block="ProductPrice">
                { this.renderCurrentPrice() }
                { this.renderOldPrice() }
                { this.renderSchema() }
            </p>
        );
    }
}

export default ProductPrice;

A Common .component Pattern: Render Maps

Ocasionally, a component needs to render different things depending on its state. Additionally, this state might affect an aspect of the component that is common between some states. To understand, consider an example:

The Checkout component has several steps to guide the user through checkout: shipping, billing, and the success step. Some of these have features in common - during both shipping and billing steps, the customer needs to see the cart items and totals, but we hide this at the success step. All 3 steps render a page title, but the title is different for each step. All 3 steps are associated with an URI, but it is different for each step.

Of course, we could handle these changes with several if and switch statements, but ScandiPWA offers a better approach: treating the possible steps as data, stored in a field named stepMap:

(Oversimplified and annotated code, taken from Checkout.component.js)

Similar map objects are used throughout ScandiPWA, with similar uses.

The .container File

The container file is responsible for:

  • Using Higher Order Components (such as connect to get global state from Redux)

  • Performing all data fetching, mutations, and other business logic

  • Optionally manipulating data it receives and passing it on to the .component so that it needs to do as little work as possible

A Common .container Pattern: containerProps

Usually a container needs to pass on certain values to its corresponding component. These are all passed on in the render method, where the .component is given all the props it needs. However, for a shorter render method and better-organized code, it is a good practice to define a separate function for the values you want to pass (additionally, this is easier to extend in child themes and plugins). Then the render method merely needs to call this function, which will return some props coming from the containter, hence the name. Check the example to see how this works.

A Common .container Pattern: containerFunctions

Similarly, if a container implements certain business logic for its component, it may wish to pass this implementation as a prop so that it can be called from the .component. These functions need to be defined in the .container, then you can bind them to this so that they have access to the instance of the container, which they will need if they access this.props or this.state. The example below demonstrates how this works.

Example .container

Here's a simplified and annotated version of component/CartOverlay/CartOverlay.container.js:

The .config File

Due to Webpack optimization limitations, it is more efficient to define constants you use in .component or .container in a separate file, and import them when you need them. This is what .config files are for. Example:

The .style File

The .style file defines styles for the component, in SCSS. It is imported in the .component file ScandiPWA uses the BEM methodology to define styles.

If you are overriding a parent theme, there may also be an .override.style file (see Overriding Styles).

The index.js File

When you need to import some components, you can use...

...instead of...

Not only is the first option more concise, but you also don't need to worry about internal component details, such as wether you need to import the container or component (if no container is defined).

index.js is the file that enables this aliasing. When a directory such as Component/Image is imported, it resolves to the Component/Image/index.js file. Hence, the index has control over the "API" the component exposes to the other components.

The contents of the index.js file are very simple. If you need to wrap the component in a container, export the container:

Otherwise, you can export the component directly:

Last updated

Was this helpful?