# Components

The ScandiPWA theme uses [React](https://reactjs.org/) 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

{% hint style="info" %}
Feel free to [browse the component directory](https://github.com/scandipwa/scandipwa/tree/master/packages/scandipwa/src/component) of ScandiPWA as you read this article! You can also read [this article about container components](https://medium.com/@learnreact/container-components-c0e67432e005), a pattern ScandiPWA uses.
{% endhint %}

## 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`](https://github.com/scandipwa/scandipwa/blob/master/packages/scandipwa/src/component/ProductPrice/ProductPrice.component.js)

```jsx
// 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;
```

{% hint style="success" %}
Note that this component is broken down by defining one function for each part. This is better than writing one long `render` method for several reasons:

* Code is easier to understand, and sometimes more concise thanks to reusable functions
* The class is easy to maintain - re-ordering these parts only affects 2 lines
* The component is easier to extend via overrides or plugins (important if your theme will be used as a parent theme)
  {% endhint %}

{% hint style="warning" %}
The `.component` file shouldn't be responsible for any business logic, such as fetching or manipulating data. For better separation of concerns, move all business logic to the `.container` file. (However, it is allowed maintain basic UI state e.g. to keep track of wether an accordion is open)
{% endhint %}

### 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](https://github.com/scandipwa/scandipwa/blob/master/packages/scandipwa/src/route/Checkout/Checkout.component.js))

```jsx
// [...] copyright, imports

/** @namespace Route/Checkout/Component */
export class Checkout extends PureComponent {
    // (1) first, each step is configured with the desired functionality
    stepMap = {
        [SHIPPING_STEP]: {
            title: __('Shipping step'),
            render: this.renderShippingStep.bind(this),
            areTotalsVisible: true
        },
        [BILLING_STEP]: {
            title: __('Billing step'),
            render: this.renderBillingStep.bind(this),
            areTotalsVisible: true
        },
        [DETAILS_STEP]: {
            title: __('Thank you for your purchase!'),
            render: this.renderDetailsStep.bind(this),
            areTotalsVisible: false
        }
    };

    // render ui specific to shipping, billing, and success details
    renderShippingStep() {...}
    renderBillingStep() {...}
    renderDetailsStep() {...}

    // (2) then, everything below this line uses the current step + stepMap
    // to determine what to do

    // React calls this after every render but the first
    // update the title and URL based on the current step data
    componentDidUpdate(prevProps) {
        const { checkoutStep } = this.props;
        const { checkoutStep: prevCheckoutStep } = prevProps;

        if (checkoutStep !== prevCheckoutStep) {
            this.updateHeader();
        }
    }

    // updates the page title based on the current step
    updateHeader() {
        const {
            setHeaderState, // function to update the state of the header
            checkoutStep, // one of SHIPPING_STEP, BILLING_STEP, DETAILS_STEP
        } = this.props;
        const { title = '' } = this.stepMap[checkoutStep];
        setHeaderState({ title });
    }

    renderTitle() {
        const { checkoutStep } = this.props;
        const { title = '' } = this.stepMap[checkoutStep];

        // same rendering logic for all steps
        return (
            <h1 block="Checkout" elem="Title">
                { title }
            </h1>
        );
    }

    renderStep() {
        const { checkoutStep } = this.props;
        const { render } = this.stepMap[checkoutStep];
        
        // call appropriate render function based on current step
        return render();
    }

    // this only renders something if areTotalsVisible is
    // true for the current step
    renderSummary() {...}

    render() {
        return (
            <main block="Checkout">
                <div block="Checkout" elem="Step">
                    { this.renderTitle() }
                    { this.renderStep() }
                </div>
                { this.renderSummary() }
            </main>
        );
    }
}

```

{% hint style="success" %}
By configuring the different steps in a JavaScript object, we avoid duplicating code everywhere that needs to switch functionality depending on the current step. We also make it easier for plugins and child themes to augment the default functionality by simply changing the `stepMap` values.

In addition, we can treat the steps as data, and easily find what the next or previous step should be, without additional data.
{% endhint %}

&#x20;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](#example-container) 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](#example-container) demonstrates how this works.

### Example `.container`

Here's a simplified and annotated version of [`component/CartOverlay/CartOverlay.container.js`](https://github.com/scandipwa/scandipwa/blob/master/packages/scandipwa/src/component/CartOverlay/CartOverlay.container.js):

```jsx
// [...] copyright, imports
import CartOverlay from './CartOverlay.component';

export const CartDispatcher = import('Store/Cart/Cart.dispatcher');

// mapStateToProps is a function that receives a global `state` object
// from redux, and passes on some selected values from the state
// which will end up being received as the container's props.
// mapStateToProps needs a @namespace declaration as well for plugins to work
/** @namespace Component/CartOverlay/Container/mapStateToProps */
export const mapStateToProps = (state) => ({
    totals: state.CartReducer.cartTotals,
    device: state.ConfigReducer.device,
    currencyCode: state.CartReducer.cartTotals.quote_currency_code,
    activeOverlay: state.OverlayReducer.activeOverlay
});

// mapDispatchToProps accepts a dispatch function from redux
// and enables the container to make certain (async) updates
/** @namespace Component/CartOverlay/Container/mapDispatchToProps */
export const mapDispatchToProps = (dispatch) => ({
    updateTotals: (options) => CartDispatcher.then(
        ({ default: dispatcher }) => dispatcher.updateTotals(dispatch, options)
    ),
    showOverlay: (overlayKey) => dispatch(toggleOverlayByKey(overlayKey)),
    showNotification: (type, message) => dispatch(showNotification(type, message)),
});

/** @namespace Component/CartOverlay/Container */
export class CartOverlayContainer extends PureComponent {
    // we declare which values we expect to receive from redux,
    // as well as the parent component.
    // this helps catch bugs with warning messages
    static propTypes = {
        totals: TotalsType.isRequired,
        showOverlay: PropTypes.func.isRequired,
        showNotification: PropTypes.func.isRequired,
        hideActiveOverlay: PropTypes.func.isRequired
    };

    // a function that returns values we want to pass to the component
    containerProps = () => {
        const { totals } = this.props;

        return {
            hasOutOfStockProductsInCart: hasOutOfStockProductsInCartItems(totals.items)
        };
    };

    // an object (initialized when constructing class) specifying functions we want
    // the component to be able to call. we `bind` them to `this` instance, so that
    // all of these functions can use `this` (container) instance. by default it would be
    // null, and we wouldn't be able to access values such as `this.props`
    containerFunctions = {
        handleCheckoutClick: this.handleCheckoutClick.bind(this)
    };

    // this functionality is implemented in the container and passed as a prop via
    // containerFunctions so that the component doesn't have to worry about business logic
    handleCheckoutClick(e) {
        const {
            showNotification,
            totals
        } = this.props;

        const hasOutOfStockProductsInCart = hasOutOfStockProductsInCartItems(totals.items);

        if (hasOutOfStockProductsInCart) {
            showNotification('error', 'Cannot proceed to checkout. Remove out of stock products first.');
            return;
        }

        hideActiveOverlay();
        history.push({ pathname: appendWithStoreCode(CHECKOUT_URL) });
    }

    render() {
        // the CartOverlay component will take care of all the rendering
        // we just need to pass on certain values and functions to it as props
        return (
            <CartOverlay
                { ...this.props }
                { ...this.containerFunctions }
                { ...this.containerProps() }
            />
        );
        // the three-dot syntax is the JavaScript spread operator
        // https://stackoverflow.com/a/31049016
        // it can be used to pass on all the values of an object to a function, another object, or JSX
        // in this case, it is equivalent to going though every key-value pair in the object
        // and passing [value] as a prop value for [key].
    }
}

// `connect` is a React-Redux HOC that takes (mapStateToProps, mapDispatchToProps)
// as defined below, and enables them to receive values from the global state,
// and passes their return values as props to the CartOverlayContainer
export default connect(mapStateToProps, mapDispatchToProps)(CartOverlayContainer);
// https://react-redux.js.org/api/connect

```

## 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:

```javascript
// component/Image/Image.config.js


export const IMAGE_LOADING = 0;
export const IMAGE_LOADED = 1;
export const IMAGE_NOT_FOUND = 2;
export const IMAGE_NOT_SPECIFIED = 3;
```

## 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](https://docs.scandipwa.com/developing-with-scandi/override-mechanism/extending-styles#partially-overriding-a-components-styles)).

## The `index.js` File

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

```javascript
import Image from 'Component/Image';
import Link from 'Component/Link';
import CategoryPaginationLink from 'Component/CategoryPaginationLink';
```

...instead of...

```javascript
import Image from 'Component/Image/Image.container.js';
import Link from 'Component/Link/Link.container.js';
import CategoryPaginationLink
  from 'Component/CategoryPaginationLink/CategoryPaginationLink.component.js';
```

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:

```javascript
// component/Image/index.js
export { default } from './Image.container';
```

Otherwise, you can export the component directly:

```javascript
// component/CategoryPaginationLink/index.js
export { default } from './CategoryPaginationLink.component';
```

{% hint style="warning" %}
Avoid implementing anything in the `index.js` file. It is meant to be used only for exporting values defined elsewhere in the component.
{% endhint %}
